⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7333458486435987494 「radient」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

在实际的业务开发中,我们经常会碰到BO、PO、DTO等对象属性之间的赋值,当属性较多的时候我们使用get,set的方式进行赋值的工作量相对较大,因此很多人会选择使用spring提供的拷贝工具BeanUtils的copyProperties方法完成对象之间属性的拷贝。

通过这种方式可以很大程度上降低我们手动编写对象属性赋值代码的工作量,既然它那么方便为什么还不建议使用呢?

我总结为以下几点:

  • 属性类型不一致导致拷贝失败

在实际开发中,很可能会出现同一字段在不同的类中定义的类型不一致,例如Id,可能在A类中定义的类型为Long,在B类中定义的类型为String,此时如果使用BeanUtils.copyProperties进行拷贝,就会出现拷贝失败的现象,导致对应的字段为null

  • 同一字段分别使用包装类型和基本类型

如果同一个字段分别使用包装类和基本类型,在没有传递实际值的时候,会出现异常

注意,如果一个布尔类型的属性分别使用了基本类型和包装类型,且属性名如果使用is开头,例如isSuccess,也会导致拷贝失败

  • null值覆盖导致数据异常

在业务开发时,我们可能会有部分字段拷贝的需求,被拷贝的数据里面如果某些字段有null值存在,但是对应的需要被拷贝过去的数据的相同字段的值并不为null,如果直接使用 BeanUtils.copyProperties 进行数据拷贝,就会出现被拷贝数据的null值覆盖拷贝目标数据的字段,导致原有的数据失效

简单举个例子,我有一个BO(已经存在了A属性且有值),需要拷贝DTO中除了A属性的其他属性(DTO中的A属性为null),此时使用BeanUtils.copyProperties就会导致BO中的A属性的值被DTO中A的null给覆盖了

虽然可以使用 BeanUtils.copyProperties 的重载方法,配合自定义的 ConvertUtilsBean 来实现部分字段的拷贝,但是这么做本身也比较复杂,也就失去了使用BeanUtils.copyProperties 拷贝数据的意义,因此也不推荐这么做。

  • 底层实现为反射拷贝效率低
  • 导包错误导致拷贝数据异常

为什么使用MapperStruct

为什么使用MapperStruct? —> 快,对就是因为很快,性能比BeanUtils.copyProperties快很多很多

放张参考图给大家参考

那么为什么MapperStruct比BeanUtils.copyProperties快?

  • 避免了反射操作的性能开销
  • 预编译的高效代码执行更快 (看到这里就懵了,预编译?咋回事啊到底)

什么是预编译? — 在项目启动的时候,自动帮你生成对象拷贝的代码,拒绝使用的时候才去通过反射调用get/set…

具体的可以往下看

@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2024-02-07T00:50:05+0800",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_211 (Oracle Corporation)"
)
public class XXXDTOConverterImpl implements XXXDTOConverter {

@Override
public XXXBO convertDtoToBo(XXXDTO XXXDTO) {
if ( XXXDTO == null ) {
return null;
}

XXXBO XXXBO = new XXXBO();

XXXBO.setId( XXXDTO.getId() );
XXXBO.setLabelName( XXXDTO.getLabelName() );
XXXBO.setSortNum( XXXDTO.getSortNum() );
XXXBO.setCategoryId( XXXDTO.getCategoryId() );

return XXXBO;
}
}

怎么使用MapperStruct

Maven引入依赖

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</dependency>

编写代码

@Data
public class SubjectLabelDTO implements Serializable {
private String labelName;
}
@Data
public class SubjectLabelBO implements Serializable {
private String labelName;
}
@Mapper
public interface SubjectLabelDTOConverter {

SubjectLabelDTOConverter INSTANCE = Mappers.getMapper(SubjectLabelDTOConverter.class);

SubjectLabelBO convertDtoToBo(SubjectLabelDTO subjectLabelDTO);
}
public Result<Boolean> add( SubjectLabelDTO subjectLabelDTO){
SubjectLabelBO subjectLabelBO = SubjectLabelDTOConverter.INSTANCE
.convertDtoToBo(subjectLabelDTO);
}

总结

Mapper-struct同样也是浅拷贝,需要进行深拷贝,就写多个Converter把需要深拷贝的再转一次吧,如果要配置深拷贝太麻烦了

Mapper-struct也存在一点小问题,跟lombok在配合的时候会出现问题,如果mapperStruct的依赖放在lombok的依赖上面的话就会出现在对象复制的时候,会将原有数据全变为null的情况,所以一定要将lombok的依赖放在mapperStruct上面

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</dependency>
文章目录
  1. 1. 为什么使用MapperStruct
  2. 2. 怎么使用MapperStruct
  3. 3. 总结