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 面试题和参考答案都整理好了,在公众号后台回复关键字 "面试" 进行刷题。
最后,觉得我的文章对你用收获的话,动动小手,给个在看、转发,原创不易,栈长需要你的鼓励。