• 别再写满屏的 get & set 了,太 Low!试试 MapStruct 高级玩法!


    接上篇,如果你还不知道 MapStruct 是什么的,建议你看下栈长之前分享的《干掉 BeanUtils!试试这款 Bean 自动映射工具,真心强大!!》你就清楚了。

    上篇介绍了 MapStruct 的基本概念,以及单个对象、对象列表的映射实践,栈长看了上篇有一些留言,当然,萝卜白菜各有所爱,喜欢就用,不喜欢就不用,没必要争执,工具好不好,不一定适合所有人,大家开心就好。

    这篇来几个高级点的映射玩法,别再写满屏的 get-set 了,太 Low!MapStruct 高级玩法,这篇栈长带你上正道!

    1、自定义映射

    当我们映射 DTO 的时候,如果某些参数的值 MapStruct 的映射配置不能满足要求,可以使用自定义方法。

    新增两个 DTO 类:

    UserCustomDTO 类里面包含了 UserExtDTO 对象。

    /**
     * 微信公众号:Java技术栈
     * @author 栈长
     */
    @Data
    public class UserCustomDTO {
    
        private String name;
    
        private int sex;
    
        private boolean married;
    
        private String birthday;
    
        private String regDate;
    
        private UserExtDTO userExtDTO;
    
        private String memo;
    
    
    }
    
    /**
     * 微信公众号:Java技术栈
     * @author 栈长
     */
    @Data
    public class UserExtDTO {
    
        private String regSource;
    
        private String favorite;
    
        private String school;
    
        private int kids;
    
        private String memo;
    
    }
    

    自定义映射:

    如果 UserExtDTO 对象不想使用默认的映射,可以添加一个该参数的自定义映射方法。

    /**
     * 微信公众号:Java技术栈
     * @author 栈长
     */
    @Mapper(componentModel = "spring")
    public interface UserCustomStruct {
    
        @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd")
        @Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))")
        @Mapping(source = "userExtDO", target = "userExtDTO")
        @Mapping(target = "memo", ignore = true)
        UserCustomDTO toUserCustomDTO(UserDO userDO);
    
        default UserExtDTO toUserExtDTO(UserExtDO userExtDO) {
            UserExtDTO userExtDTO = new UserExtDTO();
            userExtDTO.setKids(userExtDO.getKids());
            userExtDTO.setFavorite(userExtDO.getFavorite());
    
            // 覆盖这两个值
            userExtDTO.setRegSource("默认来源");
            userExtDTO.setSchool("默认学校");
    
            return userExtDTO;
        }
    
    }
    

    当映射 UserExtDTO 对象的时候,会自动调用该接口中的自定义 toUserExtDTO 方法,完成自定义映射。

    来看下生成的实现类源码:

    @Component
    public class UserCustomStructImpl implements UserCustomStruct {
        public UserCustomStructImpl() {
        }
    
        public UserCustomDTO toUserCustomDTO(UserDO userDO) {
            if (userDO == null) {
                return null;
            } else {
                UserCustomDTO userCustomDTO = new UserCustomDTO();
                if (userDO.getBirthday() != null) {
                    userCustomDTO.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(userDO.getBirthday()));
                }
    
                userCustomDTO.setUserExtDTO(this.toUserExtDTO(userDO.getUserExtDO()));
                userCustomDTO.setName(userDO.getName());
                userCustomDTO.setSex(userDO.getSex());
                userCustomDTO.setMarried(userDO.isMarried());
                userCustomDTO.setRegDate(DateFormatUtils.format(userDO.getRegDate(), "yyyy-MM-dd HH:mm:ss"));
                return userCustomDTO;
            }
        }
    }
    

    没错,setUserExtDTO 方法调用了 this.toUserExtDTO 自定义方法映射。

    Spring Boot 基础这篇就不介绍了,系列基础教程和示例源码可以看这里:https://github.com/javastacks/spring-boot-best-practice

    测试一下:

    /**
     * 微信公众号:Java技术栈
     * @author 栈长
     */
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserCustomStructTest {
    
        @Autowired
        private UserCustomStruct userCustomStruct;
    
        @Test
        public void test1() {
            UserExtDO userExtDO = new UserExtDO();
            userExtDO.setRegSource("公众号:Java技术栈");
            userExtDO.setFavorite("写代码");
            userExtDO.setSchool("社会大学");
            userExtDO.setKids(1);
    
            UserDO userDO = new UserDO();
            userDO.setName("栈长自定义方法");
            userDO.setSex(1);
            userDO.setAge(18);
            userDO.setBirthday(new Date());
            userDO.setPhone("18888888888");
            userDO.setMarried(true);
            userDO.setRegDate(new Date());
            userDO.setMemo("666");
            userDO.setUserExtDO(userExtDO);
    
            UserCustomDTO userCustomDTO = userCustomStruct.toUserCustomDTO(userDO);
            System.out.println("=====自定义方法=====");
            System.out.println(userCustomDTO);
        }
    }
    

    输出结果:

    可以看到自定义方法覆盖的两个值,结果验证成功。

    2、多参数映射

    之前介绍的映射方法中只有一个参数,如果有多个参数映射成一个 DTO,该怎么弄呢?

    比如:有两具单独的 DO,UserDO、UserAddressDO 映射成 UserMultiDTO。

    直接上代码:

    /**
     * 微信公众号:Java技术栈
     * @author 栈长
     */
    @Mapper(componentModel = "spring")
    public interface UserMultiStruct {
    
        @Mapping(source = "userDO.birthday", target = "birthday", dateFormat = "yyyy-MM-dd")
        @Mapping(target = "userDO.regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(user.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))")
        @Mapping(source = "userAddressDO.postcode", target = "postcode")
        @Mapping(source = "userAddressDO.address", target = "address")
        @Mapping(target = "memo", ignore = true)
        UserMultiDTO toUserMultiDTO(UserDO userDO, UserAddressDO userAddressDO);
    
    }
    

    直接使用指定的 对象名.属性名 形式映射即可。

    来看下生成的实现类源码:

    @Component
    public class UserMultiStructImpl implements UserMultiStruct {
        public UserMultiStructImpl() {
        }
    
        public UserMultiDTO toUserMultiDTO(UserDO userDO, UserAddressDO userAddressDO) {
            if (userDO == null && userAddressDO == null) {
                return null;
            } else {
                UserMultiDTO userMultiDTO = new UserMultiDTO();
                if (userDO != null) {
                    if (userDO.getBirthday() != null) {
                        userMultiDTO.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(userDO.getBirthday()));
                    }
    
                    userMultiDTO.setName(userDO.getName());
                    userMultiDTO.setSex(userDO.getSex());
                    userMultiDTO.setMarried(userDO.isMarried());
                    if (userDO.getRegDate() != null) {
                        userMultiDTO.setRegDate((new SimpleDateFormat()).format(userDO.getRegDate()));
                    }
                }
    
                if (userAddressDO != null) {
                    userMultiDTO.setPostcode(userAddressDO.getPostcode());
                    userMultiDTO.setAddress(userAddressDO.getAddress());
                }
    
                return userMultiDTO;
            }
        }
    }
    

    测试一下:

    /**
     * 微信公众号:Java技术栈
     * @author 栈长
     */
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserMultiStructTest {
    
        @Autowired
        private UserMultiStruct userMultiStruct;
    
        @Test
        public void test1() {
            UserDO userDO = new UserDO();
            userDO.setName("多参数映射");
            userDO.setSex(1);
            userDO.setAge(18);
            userDO.setBirthday(new Date());
            userDO.setPhone("18888888888");
            userDO.setMarried(true);
            userDO.setRegDate(new Date());
            userDO.setMemo("666");
    
            UserAddressDO userAddressDO = new UserAddressDO();
            userAddressDO.setProvince("广东省");
            userAddressDO.setCity("深圳市");
            userAddressDO.setPostcode("666666");
            userAddressDO.setAddress("001号大街Java技术栈公众号");
            userAddressDO.setMemo("地址信息");
    
            UserMultiDTO userMultiDTO = userMultiStruct.toUserMultiDTO(userDO, userAddressDO);
            System.out.println("=====多参数映射=====");
            System.out.println(userMultiDTO);
        }
    }
    

    输出结果:

    个人信息和地址信息都输出来了,结果验证成功。

    本文实战源代码完整版已经上传:

    https://github.com/javastacks/spring-boot-best-practice

    3、嵌套映射

    如果一个 DTO 中的值都是从一个对象中的多个嵌套对象映射时,如果不想一个个写映射,目标可以用 . 表示。

    直接上代码:

    /** * 微信公众号:Java技术栈 * @author 栈长 */@Mapper(componentModel = "spring")public interface UserNestedStruct {    @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd")    @Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userNestedDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))")    @Mapping(source = "userAddressDO", target = ".")    @Mapping(source = "userExtDO", target = ".")    @Mapping(source = "userExtDO.memo", target = "memo")    UserNestedDTO toUserNestedDTO(UserNestedDO userNestedDO);}
    

    如果嵌套对象中出现重名的映射冲突,可以手动指定来源哪个嵌套对象。

    来看下生成的实现类源码:

    @Componentpublic class UserNestedStructImpl implements UserNestedStruct {    public UserNestedStructImpl() {    }    public UserNestedDTO toUserNestedDTO(UserNestedDO userNestedDO) {        if (userNestedDO == null) {            return null;        } else {            UserNestedDTO userNestedDTO = new UserNestedDTO();            if (userNestedDO.getBirthday() != null) {                userNestedDTO.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(userNestedDO.getBirthday()));            }            userNestedDTO.setMemo(this.userNestedDOUserExtDOMemo(userNestedDO));            userNestedDTO.setCity(this.userNestedDOUserAddressDOCity(userNestedDO));            userNestedDTO.setAddress(this.userNestedDOUserAddressDOAddress(userNestedDO));            userNestedDTO.setRegSource(this.userNestedDOUserExtDORegSource(userNestedDO));            userNestedDTO.setFavorite(this.userNestedDOUserExtDOFavorite(userNestedDO));            userNestedDTO.setSchool(this.userNestedDOUserExtDOSchool(userNestedDO));            userNestedDTO.setName(userNestedDO.getName());            userNestedDTO.setSex(userNestedDO.getSex());            userNestedDTO.setMarried(userNestedDO.isMarried());            userNestedDTO.setRegDate(DateFormatUtils.format(userNestedDO.getRegDate(), "yyyy-MM-dd HH:mm:ss"));            return userNestedDTO;        }    }    private String userNestedDOUserExtDOMemo(UserNestedDO userNestedDO) {        if (userNestedDO == null) {            return null;        } else {            UserExtDO userExtDO = userNestedDO.getUserExtDO();            if (userExtDO == null) {                return null;            } else {                String memo = userExtDO.getMemo();                return memo == null ? null : memo;            }        }    }    private String userNestedDOUserAddressDOCity(UserNestedDO userNestedDO) {        if (userNestedDO == null) {            return null;        } else {            UserAddressDO userAddressDO = userNestedDO.getUserAddressDO();            if (userAddressDO == null) {                return null;            } else {                String city = userAddressDO.getCity();                return city == null ? null : city;            }        }    }    private String userNestedDOUserAddressDOAddress(UserNestedDO userNestedDO) {        if (userNestedDO == null) {            return null;        } else {            UserAddressDO userAddressDO = userNestedDO.getUserAddressDO();            if (userAddressDO == null) {                return null;            } else {                String address = userAddressDO.getAddress();                return address == null ? null : address;            }        }    }    private String userNestedDOUserExtDORegSource(UserNestedDO userNestedDO) {        if (userNestedDO == null) {            return null;        } else {            UserExtDO userExtDO = userNestedDO.getUserExtDO();            if (userExtDO == null) {                return null;            } else {                String regSource = userExtDO.getRegSource();                return regSource == null ? null : regSource;            }        }    }    private String userNestedDOUserExtDOFavorite(UserNestedDO userNestedDO) {        if (userNestedDO == null) {            return null;        } else {            UserExtDO userExtDO = userNestedDO.getUserExtDO();            if (userExtDO == null) {                return null;            } else {                String favorite = userExtDO.getFavorite();                return favorite == null ? null : favorite;            }        }    }    private String userNestedDOUserExtDOSchool(UserNestedDO userNestedDO) {        if (userNestedDO == null) {            return null;        } else {            UserExtDO userExtDO = userNestedDO.getUserExtDO();            if (userExtDO == null) {                return null;            } else {                String school = userExtDO.getSchool();                return school == null ? null : school;            }        }    }}
    

    从源码可以看到,从嵌套对象来的值都会新增一个方法判断一下,以避免出现空指定。

    测试一下:

    /** * 微信公众号:Java技术栈 * @author 栈长 */@RunWith(SpringRunner.class)@SpringBootTestpublic class UserNestedStructTest {    @Autowired    private UserNestedStruct userNestedStruct;    @Test    public void test1() {        UserExtDO userExtDO = new UserExtDO();        userExtDO.setRegSource("公众号:Java技术栈");        userExtDO.setFavorite("写代码");        userExtDO.setSchool("社会大学");        userExtDO.setKids(1);        userExtDO.setMemo("扩展信息");        UserAddressDO userAddressDO = new UserAddressDO();        userAddressDO.setProvince("广东省");        userAddressDO.setCity("深圳市");        userAddressDO.setPostcode("666666");        userAddressDO.setAddress("001号大街Java技术栈公众号");        userAddressDO.setMemo("地址信息");        UserNestedDO userNestedDO = new UserNestedDO();        userNestedDO.setName("栈长嵌套映射");        userNestedDO.setSex(1);        userNestedDO.setAge(18);        userNestedDO.setBirthday(new Date());        userNestedDO.setPhone("18888888888");        userNestedDO.setMarried(true);        userNestedDO.setRegDate(new Date());        userNestedDO.setMemo("666");        userNestedDO.setUserExtDO(userExtDO);        userNestedDO.setUserAddressDO(userAddressDO);        UserNestedDTO userNestedDTO = userNestedStruct.toUserNestedDTO(userNestedDO);        System.out.println("=====嵌套映射=====");        System.out.println(userNestedDTO);    }}
    

    输出结果:

    可以看到嵌套对象值,并且 memo 也是从指定的嵌套对象来的,结果验证成功。

    4、映射现有实例

    以上介绍的都是映射并生成一个新的 DTO 实例,如果是已有的现有 DTO 实例呢,该怎么映射呢?

    直接上代码:

    /** * 微信公众号:Java技术栈 * @author 栈长 */@Mapper(componentModel = "spring")public interface UserExistStruct {    @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd")    @Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))")    @Mapping(source = "userExtDO.regSource", target = "registerSource")    @Mapping(source = "userExtDO.favorite", target = "favorite")    @Mapping(target = "name", ignore = true)    @Mapping(target = "memo", ignore = true)    void toUserShowDTO(@MappingTarget UserShowDTO userShowDTO, UserDO userDO);}
    

    在方法上新增 DTO 对象参数并使用 @MappingTarget 对象修饰,参数位置可以调换。

    来看下生成的实现类源码:

    @Componentpublic class UserExistStructImpl implements UserExistStruct {    public UserExistStructImpl() {    }    public void toUserShowDTO(UserShowDTO userShowDTO, UserDO userDO) {        if (userDO != null) {            if (userDO.getBirthday() != null) {                userShowDTO.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(userDO.getBirthday()));            } else {                userShowDTO.setBirthday((String)null);            }            userShowDTO.setRegisterSource(this.userDOUserExtDORegSource(userDO));            userShowDTO.setFavorite(this.userDOUserExtDOFavorite(userDO));            userShowDTO.setSex(userDO.getSex());            userShowDTO.setMarried(userDO.isMarried());            userShowDTO.setRegDate(DateFormatUtils.format(userDO.getRegDate(), "yyyy-MM-dd HH:mm:ss"));        }    }    private String userDOUserExtDORegSource(UserDO userDO) {        if (userDO == null) {            return null;        } else {            UserExtDO userExtDO = userDO.getUserExtDO();            if (userExtDO == null) {                return null;            } else {                String regSource = userExtDO.getRegSource();                return regSource == null ? null : regSource;            }        }    }    private String userDOUserExtDOFavorite(UserDO userDO) {        if (userDO == null) {            return null;        } else {            UserExtDO userExtDO = userDO.getUserExtDO();            if (userExtDO == null) {                return null;            } else {                String favorite = userExtDO.getFavorite();                return favorite == null ? null : favorite;            }        }    }}
    

    userShowDTO 是作为方法参数传入的,而不是新创建的。

    测试一下:

    /** * 微信公众号:Java技术栈 * @author 栈长 */@RunWith(SpringRunner.class)@SpringBootTestpublic class UserExistStructTest {    @Autowired    private UserExistStruct userExistStruct;    @Test    public void test1() {        UserExtDO userExtDO = new UserExtDO();        userExtDO.setRegSource("公众号:Java技术栈");        userExtDO.setFavorite("写代码");        userExtDO.setSchool("社会大学");        UserDO userDO = new UserDO();        userDO.setName("栈长");        userDO.setSex(1);        userDO.setAge(18);        userDO.setBirthday(new Date());        userDO.setPhone("18888888888");        userDO.setMarried(true);        userDO.setRegDate(new Date());        userDO.setMemo("666");        userDO.setUserExtDO(userExtDO);        System.out.println("=====映射现有实例前=====");        UserShowDTO userShowDTO = new UserShowDTO();        userShowDTO.setName("栈长NAME");        userShowDTO.setMemo("栈长MEMO");        System.out.println(userShowDTO);        System.out.println("=====映射现有实例后=====");        userExistStruct.toUserShowDTO(userShowDTO, userDO);        System.out.println(userShowDTO);    }}
    

    输出结果:

    可以看到已有 DTO 对象的值及新映射的值,结果验证成功。

    注意:默认是以覆盖原有值的方式映射的,如果要保留原有 XX 的值,使用 ignore 忽略即可

    总结

    本文栈长介绍了 MapStruct 的 4 个高级玩法,足以应对各种 Bean 类映射了,其实还有很多复杂的、个性化用法,一篇难以写完,栈长后面有时间会整理出来,陆续给大家分享。

    感兴趣的也可以参考官方文档:

    https://mapstruct.org/documentation/reference-guide/

    另外,栈长一直介绍的是 DO --> DTO 的映射,其实反过来 DTO --> DO、BO 也是一样的,只是对象名称不一样,映射的用法是一样的,这样在服务 A 接收到服务 B 过来的 DTO 数据时,可以再进行一次反射映射供业务使用。

    本文实战源代码完整版已经上传:

    https://github.com/javastacks/spring-boot-best-practice

    欢迎 Star 学习,后面 Spring Boot 示例都会在这上面提供!

    好了,今天的分享就到这里了,后面栈长会分享更多好玩的 Java 技术和最新的技术资讯,关注公众号Java技术栈第一时间推送,我也将主流 Java 面试题和参考答案都整理好了,在公众号后台回复关键字 "面试" 进行刷题。

    最后,觉得我的文章对你用收获的话,动动小手,给个在看、转发,原创不易,栈长需要你的鼓励。

    版权声明: 本文系公众号 "Java技术栈" 原创,原创实属不易,转载、引用本文内容请注明出处,抄袭者一律举报+投诉,并保留追究其法律责任的权利。

    近期热文推荐:

    1.1,000+ 道 Java面试题及答案整理(2021最新版)

    2.别在再满屏的 if/ else 了,试试策略模式,真香!!

    3.卧槽!Java 中的 xx ≠ null 是什么新语法?

    4.Spring Boot 2.5 重磅发布,黑暗模式太炸了!

    5.《Java开发手册(嵩山版)》最新发布,速速下载!

    觉得不错,别忘了随手点赞+转发哦!

  • 相关阅读:
    Linux 远程和本地的一些解决方式
    【Android界面实现】使用PagerTabStrip实现有滑动标签的Viewpager
    Elasticsearch
    Awk使用及站点日志分析
    我的软考之路(八)——三大原则学会数据流图
    BZOJ 3864 Hero meet devil DP套DP
    Android studio 自己定义打包APK名称
    C/C++——程序的内存分配
    剑指offer 高速排序
    HDU1069(还是dp基础)
  • 原文地址:https://www.cnblogs.com/javastack/p/15406541.html
Copyright © 2020-2023  润新知