哈喽,大家好,我是指北君
。
最近接手一个项目发现有些接口只是做了一些简单的单表查询业务,但是却耗时八百多毫秒,明显不太正常, 经排查发现时间都消耗在Apache的BeanUtils中对属性的拷贝上,整个流程使用了四次拷贝方法, 使得整个方法耗时急剧增加。指北君在这里求求大家不要再使用BeanUtils进行拷贝了,那使用什么呢?当然是今天的主角MapStruct。
如何引入
MapStruct引入相当简单。
如果你的项目使用Maven进行管理则添加以下配置:
<properties>
<org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
如果你的项目使用Gradle进行管理则添加以下配置:
plugins {
id 'net.ltgt.apt' version '0.20'
}
// 根据你的编译器选择
apply plugin: 'net.ltgt.apt-idea'
// or
apply plugin: 'net.ltgt.apt-eclipse'
dependencies {
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
// If you are using mapstruct in test code
testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
}
这样就引入完成了。这里有两种使用方式,可以结合Spring进行使用,也可以不依赖Spring。如下示例:
如何使用
不依赖Spring方式
@Mapper
public interface CarMapper {
//通过这个静态属性就能调用相应的方法
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
CarDto carToCarDto(Car car);
}
与Spring结合使用
@Mapper(componentModel = "spring")
public interface CarMapper {
CarDto carToCarDto(Car car);
}
通过componentModel参数指定使用Spring的依赖注入方式使用。
当然了如果结合Spring使用也可以统一配置
如何转换
接下来我们进行简单的使用。相同字段自动映射,如果两个实体字段名相同,MapStruct就会自动通过相应的Getter/Setter生成属性拷贝方法, 并且自动忽略不相同的字段。使用方式和BeanUtils相似。
@Mapper
public interface CarMapper {
CarDto carToCarDto(Car car);
}
MapStruct是在编译期对添加了@Mapper的接口进行生成的实现类,所以上面的代码会生成一个下面的实现类。是不是很简单呢,这样即保证了拷贝的性能,同时我们不需要在业务代码中写对应的拷贝方法,岂不美哉。
当然了,这时候你可能会想,那如果我两个实体映射时有些字段名不一致怎么办呢?
@Mapper
public interface CarMapper {
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDto(Car car);
}
我们可以通过在方法上添加@Mapping注解指定以下映射关系就可以了,来我们看下效果吧。可以看到我们指定的把numberOfSeats映射成seatCount也很简单的完成了。
MapStruct还有一个更加强大的功能,就是如果映射的类型不一样可以自动转换,常见的数据类型几乎都能支持。我们也一起来看一下吧。
从图中我们可以看到其实并不是很智能,而且转换可能并不是我们想要的,比如说我们要把一个 String类型的时间戳转换为Date类型,并且可能存在BUG,比如说我们想要把String类型的价格转换为BigDecimal类型, 如果这个Price是一个空字符串,那么这里就会报错。
那这怎么办呢?指北君在最后给大家准备了我演示的完整源码和教程。像集合的映射,实体反映射,自定义映射规则,填充默认值等等,教程都有写。
我们已经了解了如何使用,那么真正的效果如何呢?
效果如何
指北君在这里也给大家精心准备了各个拷贝方式的直观对比图。
编辑搜图
从图中可以看到,使用MapStruct提升速度的效果还是蛮显著的。
指北君有话说
当然了MapStruct的骚操作还是挺多的,几乎能满足90%的需要。剩下的10%很大可能是你设计有问题。但由于内容太多,全部展示出来有点啰嗦。如果你感兴趣可以按照下面提供的方式获取相关资源。如果有什么不明白的地方也可以在群里直接咨询我们。指北君也是知无不言言无不尽。
关注公众号 [程序员了不起]回复[eee113] 即可获取navicat绿版。