1. 声明:
  2. 1DO(业务实体对象),DTO(数据传输对象)。
  3. 2、我的代码中用到了 Lombok ,不了解的可以自行了解一下,了解的忽略这条就好。

在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之后,DO 一般不会让外部依赖,这时候需要在提供对外接口的模块里放 DTO 用于对象传输,也即是 DO 对象对内,DTO对象对外,DTO 可以根据业务需要变更,并不需要映射 DO 的全部属性。

这种 对象与对象之间的互相转换,就需要有一个专门用来解决转换问题的工具,毕竟每一个字段都 get/set 会很麻烦。

MapStruct 就是这样的一个属性映射工具,只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。MapStruct官网地址: http://mapstruct.org/

工程中引入 maven 依赖

  1. <properties>
  2. <mapstruct.version>1.2.0.Final</mapstruct.version>
  3. </properties>
  4. <dependencies>
  5. <dependency>
  6. <groupId>org.mapstruct</groupId>
  7. <artifactId>mapstruct-jdk8</artifactId>
  8. <version>${mapstruct.version}</version>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.mapstruct</groupId>
  12. <artifactId>mapstruct-processor</artifactId>
  13. <version>${mapstruct.version}</version>
  14. </dependency>
  15. </dependencies>

基本映射

这里定义两个 DO 对象 Person 和 User,其中 user 是 Person 的一个属性 ,一个 DTO 对象 PersonDTO

  1. @NoArgsConstructor
  2. @AllArgsConstructor
  3. @Data
  4. public class Person {
  5. private Long id;
  6. private String name;
  7. private String email;
  8. private Date birthday;
  9. private User user;
  10. }
  11. @NoArgsConstructor
  12. @AllArgsConstructor
  13. @Data
  14. public class User {
  15. private Integer age;
  16. }
  17. @NoArgsConstructor
  18. @AllArgsConstructor
  19. @Data
  20. public class PersonDTO {
  21. private Long id;
  22. private String name;
  23. /**
  24. * 对应 Person.user.age
  25. */
  26. private Integer age;
  27. private String email;
  28. /**
  29. * 与 DO 里面的字段名称(birthDay)不一致
  30. */
  31. private Date birth;
  32. /**
  33. * 对 DO 里面的字段(birthDay)进行拓展,dateFormat 的形式
  34. */
  35. private String birthDateFormat;
  36. /**
  37. * 对 DO 里面的字段(birthDay)进行拓展,expression 的形式
  38. */
  39. private String birthExpressionFormat;
  40. }

写一个 Mapper 接口 PersonConverter,其中两个方法,一个是单实体映射,另一个是List映射

若源对象属性与目标对象属性名字一致,会自动映射对应属性,不一样的需要指定,也可以用 format 转成自己想要的类型,也支持表达式的方式,可以看到像 id、name、email这些名词一致的我并没有指定 source-target,而birthday-birth指定了,转换格式的 birthDateFormat 加了dateFormat 或者 birthExpressionFormat 加了 expression,如果某个属性你不想映射,可以加个 ignore=true

  1. @Mapper
  2. public interface PersonConverter {
  3. PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
  4. @Mappings({
  5. @Mapping(source = "birthday", target = "birth"),
  6. @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
  7. @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
  8. @Mapping(source = "user.age", target = "age"),
  9. @Mapping(target = "email", ignore = true)
  10. })
  11. PersonDTO domain2dto(Person person);
  12. List<PersonDTO> domain2dto(List<Person> people);
  13. }

编译MapStruct之后,手工编译或者启动 IDE 的时候 IDE 也会帮我们编译, 会自动在 target/classes 下生成对应的实现类

  1. 手工编译命令
  2. mvn compile

注意!!!下面这个 PersonConverterImpl 是自动生成的,不是自己写的!

  1. public class PersonConverterImpl implements PersonConverter {
  2. public PersonConverterImpl() {
  3. }
  4. public PersonDTO domain2dto(Person person) {
  5. if (person == null) {
  6. return null;
  7. } else {
  8. PersonDTO personDTO = new PersonDTO();
  9. personDTO.setBirth(person.getBirthday());
  10. if (person.getBirthday() != null) {
  11. personDTO.setBirthDateFormat((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(person.getBirthday()));
  12. }
  13. Integer age = this.personUserAge(person);
  14. if (age != null) {
  15. personDTO.setAge(age);
  16. }
  17. personDTO.setId(person.getId());
  18. personDTO.setName(person.getName());
  19. personDTO.setBirthExpressionFormat(DateFormatUtils.format(person.getBirthday(), "yyyy-MM-dd HH:mm:ss"));
  20. return personDTO;
  21. }
  22. }
  23. public List<PersonDTO> domain2dto(List<Person> people) {
  24. if (people == null) {
  25. return null;
  26. } else {
  27. List<PersonDTO> list = new ArrayList(people.size());
  28. Iterator var3 = people.iterator();
  29. while(var3.hasNext()) {
  30. Person person = (Person)var3.next();
  31. list.add(this.domain2dto(person));
  32. }
  33. return list;
  34. }
  35. }
  36. private Integer personUserAge(Person person) {
  37. if (person == null) {
  38. return null;
  39. } else {
  40. User user = person.getUser();
  41. if (user == null) {
  42. return null;
  43. } else {
  44. Integer age = user.getAge();
  45. return age == null ? null : age;
  46. }
  47. }
  48. }
  49. }

写一个单元测试类 PersonConverterTest 测试一下,看看效果

  1. public class PersonConverterTest {
  2. @Test
  3. public void test() {
  4. Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1));
  5. PersonDTO personDTO = PersonConverter.INSTANCE.domain2dto(person);
  6. assertNotNull(personDTO);
  7. assertEquals(personDTO.getId(), person.getId());
  8. assertEquals(personDTO.getName(), person.getName());
  9. assertEquals(personDTO.getBirth(), person.getBirthday());
  10. String format = DateFormatUtils.format(personDTO.getBirth(), "yyyy-MM-dd HH:mm:ss");
  11. assertEquals(personDTO.getBirthDateFormat(),format);
  12. assertEquals(personDTO.getBirthExpressionFormat(),format);
  13. List<Person> people = new ArrayList<>();
  14. people.add(person);
  15. List<PersonDTO> personDTOs = PersonConverter.INSTANCE.domain2dto(people);
  16. assertNotNull(personDTOs);
  17. }
  18. }

多对一

MapStruct 可以将几种类型的对象映射为另外一种类型,比如将多个 DO 对象转换为 DTO

例子

  • 两个 DO 对象 Item 和 Sku,一个 DTO 对象 SkuDTO
  1. @NoArgsConstructor
  2. @AllArgsConstructor
  3. @Data
  4. public class Item {
  5. private Long id;
  6. private String title;
  7. }
  8. @NoArgsConstructor
  9. @AllArgsConstructor
  10. @Data
  11. public class Sku {
  12. private Long id;
  13. private String code;
  14. private Integer price;
  15. }
  16. @NoArgsConstructor
  17. @AllArgsConstructor
  18. @Data
  19. public class SkuDTO {
  20. private Long skuId;
  21. private String skuCode;
  22. private Integer skuPrice;
  23. private Long itemId;
  24. private String itemName;
  25. }
  • 创建 ItemConverter(映射)接口,MapStruct 就会自动实现该接口
  1. @Mapper
  2. public interface ItemConverter {
  3. ItemConverter INSTANCE = Mappers.getMapper(ItemConverter.class);
  4. @Mappings({
  5. @Mapping(source = "sku.id",target = "skuId"),
  6. @Mapping(source = "sku.code",target = "skuCode"),
  7. @Mapping(source = "sku.price",target = "skuPrice"),
  8. @Mapping(source = "item.id",target = "itemId"),
  9. @Mapping(source = "item.title",target = "itemName")
  10. })
  11. SkuDTO domain2dto(Item item, Sku sku);
  12. }
  • 创建测试类,讲 Item 和 Sku 两个 DO对象,映射成一个 DTO 对象 SkuDTO
  1. public class ItemConverterTest {
  2. @Test
  3. public void test() {
  4. Item item = new Item(1L, "iPhone X");
  5. Sku sku = new Sku(2L, "phone12345", 1000000);
  6. SkuDTO skuDTO = ItemConverter.INSTANCE.domain2dto(item, sku);
  7. assertNotNull(skuDTO);
  8. assertEquals(skuDTO.getSkuId(),sku.getId());
  9. assertEquals(skuDTO.getSkuCode(),sku.getCode());
  10. assertEquals(skuDTO.getSkuPrice(),sku.getPrice());
  11. assertEquals(skuDTO.getItemId(),item.getId());
  12. assertEquals(skuDTO.getItemName(),item.getTitle());
  13. }
  14. }

可以添加自定义方法

  1. // 形式如下
  2. default PersonDTO personToPersonDTO(Person person) {
  3. //hand-written mapping logic
  4. }
  5. // 比如在 PersonConverter 里面加入如下
  6. default Boolean convert2Bool(Integer value) {
  7. if (value == null || value < 1) {
  8. return Boolean.FALSE;
  9. } else {
  10. return Boolean.TRUE;
  11. }
  12. }
  13. default Integer convert2Int(Boolean value) {
  14. if (value == null) {
  15. return null;
  16. }
  17. if (Boolean.TRUE.equals(value)) {
  18. return 1;
  19. }
  20. return 0;
  21. }
  22. // 测试类 PersonConverterTest 加入
  23. assertTrue(PersonConverter.INSTANCE.convert2Bool(1));
  24. assertEquals((int)PersonConverter.INSTANCE.convert2Int(true),1);

#### 如果已经有了接收对象,更新目标对象

  1. // 比如在 PersonConverter 里面加入如下,@InheritConfiguration 用于继承刚才的配置
  2. @InheritConfiguration(name = "domain2dto")
  3. void update(Person person, @MappingTarget PersonDTO personDTO);
  4. // 测试类 PersonConverterTest 加入如下
  5. Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1));
  6. PersonDTO personDTO = PersonConverter.INSTANCE.domain2dto(person);
  7. assertEquals("zhige", personDTO.getName());
  8. person.setName("xiaozhi");
  9. PersonConverter.INSTANCE.update(person, personDTO);
  10. assertEquals("xiaozhi", personDTO.getName());

Spring 注入的方式

  1. // 刚才一直写的例子是默认的方式
  2. PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);

还有一种常用的方式,是和常用的框架 Spring 结合,在 @Mapper 后面加入 componentModel="spring"

  1. @Mapper(componentModel="spring")
  2. public interface PersonConverter {
  3. @Mappings({
  4. @Mapping(source = "birthday", target = "birth"),
  5. @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
  6. @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
  7. @Mapping(source = "user.age", target = "age"),
  8. @Mapping(target = "email", ignore = true)
  9. })
  10. PersonDTO domain2dto(Person person);
  11. }

这时候测试类改一下,我用的 spring boot 的形式

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest(classes = BaseTestConfiguration.class)
  3. public class PersonConverterTest {
  4. //这里把转换器装配进来
  5. @Autowired
  6. private PersonConverter personConverter;
  7. @Test
  8. public void test() {
  9. Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1));
  10. PersonDTO personDTO = personConverter.domain2dto(person);
  11. assertNotNull(personDTO);
  12. assertEquals(personDTO.getId(), person.getId());
  13. assertEquals(personDTO.getName(), person.getName());
  14. assertEquals(personDTO.getBirth(), person.getBirthday());
  15. String format = DateFormatUtils.format(personDTO.getBirth(), "yyyy-MM-dd HH:mm:ss");
  16. assertEquals(personDTO.getBirthDateFormat(),format);
  17. assertEquals(personDTO.getBirthExpressionFormat(),format);
  18. }
  19. }

我 test 路径下加入了一个配置类

  1. @EnableAutoConfiguration
  2. @Configuration
  3. @ComponentScan
  4. public class BaseTestConfiguration {
  5. }

MapStruct 注解的关键词

  1. @Mapper 只有在接口加上这个注解, MapStruct 才会去实现该接口
  2. @Mapper 里有个 componentModel 属性,主要是指定实现类的类型,一般用到两个
  3. default:默认,可以通过 Mappers.getMapper(Class) 方式获取实例对象
  4. spring:在接口的实现类上自动添加注解 @Component,可通过 @Autowired 方式注入
  5. @Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
  6. source:源属性
  7. target:目标属性
  8. dateFormatString Date 日期之间相互转换,通过 SimpleDateFormat,该值为 SimpleDateFormat 的日期格式
  9. ignore: 忽略这个字段
  10. @Mappings:配置多个@Mapping
  11. @MappingTarget 用于更新已有对象
  12. @InheritConfiguration 用于继承配置

本文只是写了一些常用的比较简单的一些功能,更详细的可以去阅读官方文档: http://mapstruct.org/documentation/stable/reference/html/

  1. </div>

原文地址:https://my.oschina.net/yongzhiwang/blog/1844141

推荐一个 Java 实体映射工具 MapStruct的更多相关文章

  1. Java实体映射工具MapStruct的使用

    官网地址:http://mapstruct.org/ MapStruct 是一个代码生成器,简化了不同的 Java Bean 之间映射的处理,所谓的映射指的就是从一个实体变化成一个实体.例如我们在实际 ...

  2. 【工具库】Java实体映射工具MapStruct

    一.什么是MapStruct? MapStruct是用于代码中JavaBean对象之间的转换,例如DO转换为DTO,DTO转换为VO,或Entity转换为VO等场景,虽然Spring库和 Apache ...

  3. Java实体映射工具MapStruct使用详解

    1.序 通常在后端开发中经常不直接返回实体Entity类,经过处理转换返回前端,前端提交过来的对象也需要经过转换Entity实体才做存储:通常使用的BeanUtils.copyProperties方法 ...

  4. 在线数据库表(sql语句)生成java实体类工具

    相信每个做java开发的读者,都接触过SQL建表语句,尤其是在项目开发初期,因为数据库是项目的基石. 在现代项目开发中,出现了许多ORM框架,通过简单的实体映射,即可实现与数据库的交互,然而我们最初设 ...

  5. 我写了一个java实体类,implements了Serializable接口,然后我如何让serialversionUID自动生成

    写了一个java实体类,implements了Serializable接口,让serialversionUID自动生成方法: 1.点击类旁边的警告符号: 2.选择Add generated seria ...

  6. 推荐一个web字体转换工具TTF转SVG

    推荐一个web字体转换工具:https://www.fontsquirrel.com/tools/webfont-generator

  7. 推荐一个 Java 里面比较牛逼的公众号!

    今天给大家推荐一个牛逼的纯 Java 技术公众号:Java技术栈,作者:栈长. Java程序员.Java爱好者扫码关注吧! 确实牛逼,几十万人关注了,原创文章350+,好友都 3000+ 关注了. 栈 ...

  8. 文章推荐一个Java程序员跟大家谈谈从业心得

    一个Java程序员跟大家谈谈从业心得 2017-10-21 java那些事 java那些事 java那些事 微信号 csh624366188 功能介绍 分享java开发中常用的技术,分享软件开发中各种 ...

  9. 推荐一个实用的css工具

    后台程序员整天在和数据打交道,天天的活就是抱着mysql抠数据,如果让他去写网站的样式,就让人感觉力不从心,所以推荐一个twitter的团队开发的东西,几乎囊括了网站所需的样式,http://www. ...

随机推荐

  1. html到计时特效(直接代码)

    <!DOCTYPE html> <head> <meta http-equiv="Content-Type" content="text/h ...

  2. 在WIN7下解决coursera视频无法播放问题

    https://blog.csdn.net/u012509485/article/details/78459584在WIN7下解决coursera视频无法播放问题2019/1/20 23:18 最近C ...

  3. 如何查看安装的java是32位的,还是64位的

    命令 java -d32 -version 或者 java -d64 -version

  4. linux下查看nginx配置文件地址

    which nginx/usr/sbin/nginx -t

  5. iOS UI异步更新:dispatch_async 与 dispatch_get_global_queue 的使用方法

    GCD (Grand Central Dispatch) 是Apple公司开发的一种技术,它旨在优化多核环境中的并发操作并取代传统多线程的编程模式. 在Mac OS X 10.6和IOS 4.0之后开 ...

  6. Android(java)学习笔记173:服务(service)之绑定服务的细节

    绑定服务的细节 1. 如果onbind方法返回值是null,onServiceConnect方法就不会被调用: 2. 绑定的服务,在系统设置界面,正在运行条目是看不到的: 3. 绑定的服务,不求同时生 ...

  7. Gym - 100676G Training Camp (状压dp)

    G. Training Camp[ Color: Yellow ]Montaser is planning to train very hard for ACM JCPC 2015; he has p ...

  8. XtraBackUp 热备份工具

    是一款强大的在线热备份工具 备份的过程中,不锁表 使用percona-xtrabackup-24-2.4.7-1.el7.x86_64.rpm yum源安装: 1.安装Percona的库:       ...

  9. web测试需要注意点

  10. Python 循环结构语句

    1.for循环:计次循环 2.while循环:条件循环 3.嵌套循环 4.跳转语句 一.for循环的使用 1.进行数值循环 利用数值循环输出三次‘你好’: >>> for i in ...