MapStruct官方文档
通过使用普通方法调用而不是反射来快速执行
编译时类型安全:只能映射相互映射的对象和属性,不会将订单实体意外映射到客户DTO等。
在构建时清除错误报告,如果
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.siayougroupId>
<artifactId>mapstruct-demoartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<org.mapstruct.version>1.4.1.Finalorg.mapstruct.version>
<org.projectlombok.version>1.18.12org.projectlombok.version>
properties>
<dependencies>
<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstructartifactId>
<version>${org.mapstruct.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${org.projectlombok.version}version>
<scope>providedscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${org.projectlombok.version}version>
path>
<path>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-processorartifactId>
<version>${org.mapstruct.version}version>
path>
annotationProcessorPaths>
configuration>
plugin>
plugins>
build>
project>
@Mapper
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
//mapper可以进行字段映射,改变字段类型,指定格式化的方式,包括一些日期的默认处理。
//无论date转string,还是string转date,都是用dateFormat
@Mapping(source = "gender.name", target = "gender")
@Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
StudentVO student2StudentVO(Student student);
}
// @Data 在编译时会自动添加 Getter、Setter、equals、canEqual、hasCode、toString 等方法,高效且代码非常简洁。
// @Builder 可代替需要的很多构造函数,解决了某个类有很多构造函数的情况。
// @AllArgsConstructor 在编译时会自动添加一个含有所有已声明字段的构造函数,不必再手动编写含有所有已声明字段的构造函数。
// @NoArgsConstructor 在编译时会自动添加一个无参的构造函数,不必再手动编写无参构造函数。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {
//姓名
private String name;
//年龄
private int age;
//性别
private GenderEnum gender;
//身高
private Double height;
//生日
private Date birthday;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO {
//姓名
private String name;
//年龄
private int age;
//性别
private String gender;
//身高
private Double height;
//生日
private String birthday;
}
public enum GenderEnum {
Male("1", "男"),
Female("0", "女");
private String code;
private String name;
public String getCode() {
return this.code;
}
public String getName() {
return this.name;
}
GenderEnum(String code, String name) {
this.code = code;
this.name = name;
}
}
public static void main(String[] args) {
Student student = Student.builder()
.name("张三")
.age(16)
.gender(GenderEnum.Male)
.height(174.3)
.birthday(new Date())
.build();
System.out.println(student);
StudentVO studentVO = StudentMapper.INSTANCE.student2StudentVO(student);
System.out.println(studentVO);
}
//测试结果
Student(name=张三, age=16, gender=Male, height=174.3, birthday=Fri Sep 22 16:22:38 CST 2023)
StudentVO(name=张三, age=16, gender=男, height=174.3, birthday=2023-09-22 16:22:38)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO {
//姓名
private String name;
//年龄
private int age;
//性别
private String gender;
//身高
private Double height;
//生日
private String birthday;
private ExtendDto extendDto;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {
//姓名
private String name;
//年龄
private int age;
//性别
private GenderEnum gender;
//身高
private Double height;
//生日
private Date birthday;
private ExtendDto extendDto;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ExtendDto {
//邮箱
private String email;
//地址
private String address;
//电话
private String phone;
}
@Mapper
public interface StudentMapper {
@Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(source = "email", target = "extendDto.email")
StudentVO studentTwoStudentVO(Student student,String email);
}
public static void main(String[] args) {
Student student = Student.builder()
.name("李四")
.age(16)
.gender(GenderEnum.Male)
.height(174.3)
.birthday(new Date())
.build();
System.out.println(student);
StudentVO studentVO = StudentMapper.INSTANCE.studentTwoStudentVO(student,"[email protected]");
System.out.println(studentVO);
}
//测试结果
Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:12:36 CST 2023, extendDto=null)
StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:12:36, extendDto=ExtendDto(email=dmjxsy@126.com, address=null, phone=null))
@Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(source = "extendDto", target = "extendDto")
StudentVO studentMapStudentVO(Student student,ExtendDto extendDto);
public static void main(String[] args) {
Student student = Student.builder()
.name("李四")
.age(16)
.gender(GenderEnum.Male)
.height(174.3)
.birthday(new Date())
.build();
ExtendDto extendDto = ExtendDto.builder()
.email("[email protected]")
.phone("119")
.address("陕西")
.build();
System.out.println(student);
System.out.println(extendDto);
StudentVO studentVO = StudentMapper.INSTANCE.studentMapStudentVO(student,extendDto);
System.out.println(studentVO);
}
//测试结果
Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:24:18 CST 2023, extendDto=null)
ExtendDto(email=dmjxsy@126.com, address=陕西, phone=119)
StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:24:18, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=119))
}
当配置了参数时,在配置对象属性关系映射时,也要显示的指明对象参数的名称。如@Mapping(source = “student.birthday”, target = “birthday”) 就显示指明了 source = “student.birthday” 。
@Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(source = "extendDto", target = ".")
StudentVO studentMapStudentVO(Student student);
public static void main(String[] args) {
Student student = Student.builder()
.name("李四")
.age(16)
.gender(GenderEnum.Male)
.height(174.3)
.birthday(new Date())
.extendDto( ExtendDto.builder()
.email("[email protected]")
.phone("110")
.address("陕西")
.build())
.build();
System.out.println(student);
StudentVO studentVO = StudentMapper.INSTANCE.studentMapStudentVO(student);
System.out.println(studentVO);
}
//测试结果
Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:31:14 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:31:14, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
使用 “ . ” ,将一个嵌套的bean的值合并到一个扁平化的对象中。
@Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(source = "extendDto", target = ".")
// @Mapping(source = "name",target = "name", ignore = true)
void updateStudent(Student student,@MappingTarget StudentVO studentVO);
@Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(source = "extendDto", target = ".")
@Mapping(source = "name",target = "name", ignore = true)
void updateStudent(Student student,@MappingTarget StudentVO studentVO);
public static void main(String[] args) {
Student student = Student.builder()
.name("李四")
.age(16)
.gender(GenderEnum.Male)
.height(174.3)
.birthday(new Date())
.extendDto( ExtendDto.builder()
.email("[email protected]")
.phone("110")
.address("陕西")
.build())
.build();
StudentVO studentVO = StudentVO.builder()
.name("王五")
.age(20)
.height(204.3)
.birthday("2020-01-01 12:11:11")
.extendDto( ExtendDto.builder()
.email("[email protected]")
.phone("110110119")
.address("陕西西安")
.build())
.build();
System.out.println("更新前 student:"+student.toString());
System.out.println("更新前 studentVO:"+studentVO.toString());
StudentMapper.INSTANCE.updateStudent(student,studentVO);
System.out.println("更新后 student:"+student.toString());
System.out.println("更新后 studentVO:"+studentVO.toString());
}
//测试结果
更新前 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:56:35 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新前 :StudentVO(name=王五, age=20, gender=null, height=204.3, birthday=2020-01-01 12:11:11, extendDto=ExtendDto(email=dmjxsy@163.com, address=陕西西安, phone=110110119))
更新后 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:56:35 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新后 :StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:56:35, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
//添加 @Mapping(source = "name",target = "name", ignore = true)
更新前 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:57:39 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新前 :StudentVO(name=王五, age=20, gender=null, height=204.3, birthday=2020-01-01 12:11:11, extendDto=ExtendDto(email=dmjxsy@163.com, address=陕西西安, phone=110110119))
更新后 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:57:39 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新后 :StudentVO(name=王五, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:57:39, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
@MappingTarget 使用改注解标记目标对象我们就可以更新该对象的值。如果想不更新某个值,可以给加一个ignore = true的标签来忽略。
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
for (int i = 0; i < 10; i++) {
Long start = System.currentTimeMillis();
for (int i1 = 0; i1 < 1000000; i1++) {
Student student = Student.builder()
.name("小明")
.age(6)
.gender(GenderEnum.Male)
.height(121.1)
.birthday(new Date())
.build();
StudentVO studentVO = new StudentVO();
org.apache.commons.beanutils.BeanUtils.copyProperties(studentVO, student);
}
System.out.println("org.apache.commons.beanutils.BeanUtils第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
Long start2 = System.currentTimeMillis();
for (int i1 = 0; i1 < 1000000; i1++) {
Student student = Student.builder()
.name("小明")
.age(6)
.gender(GenderEnum.Male)
.height(121.1)
.birthday(new Date())
.build();
StudentMapper.INSTANCE.student2StudentVO(student);
}
System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
}
}
//测试结果
org.apache.commons.beanutils.BeanUtils第0次执行---100W次转换耗时:11854
MapStruct第0次执行-----100W次转换耗时:1981
org.apache.commons.beanutils.BeanUtils第1次执行---100W次转换耗时:10828
MapStruct第1次执行-----100W次转换耗时:2080
org.apache.commons.beanutils.BeanUtils第2次执行---100W次转换耗时:9894
MapStruct第2次执行-----100W次转换耗时:1891
org.apache.commons.beanutils.BeanUtils第3次执行---100W次转换耗时:9543
MapStruct第3次执行-----100W次转换耗时:1735
org.apache.commons.beanutils.BeanUtils第4次执行---100W次转换耗时:6862
MapStruct第4次执行-----100W次转换耗时:1958
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
for (int i = 0; i < 5; i++) {
Long start = System.currentTimeMillis();
for (int i1 = 0; i1 < 1000000; i1++) {
Student student = Student.builder()
.name("小明")
.age(6)
.gender(GenderEnum.Male)
.height(121.1)
.birthday(new Date())
.build();
StudentVO studentVO = new StudentVO();
cn.hutool.core.bean.BeanUtil.copyProperties(studentVO,student,true);
}
System.out.println("cn.hutool.core.bean.BeanUtil第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
Long start2 = System.currentTimeMillis();
for (int i1 = 0; i1 < 1000000; i1++) {
Student student = Student.builder()
.name("小明")
.age(6)
.gender(GenderEnum.Male)
.height(121.1)
.birthday(new Date())
.build();
StudentMapper.INSTANCE.student2StudentVO(student);
}
System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
}
}
//测试结果
cn.hutool.core.bean.BeanUtil第0次执行---100W次转换耗时:7169
MapStruct第0次执行-----100W次转换耗时:2538
cn.hutool.core.bean.BeanUtil第1次执行---100W次转换耗时:4797
MapStruct第1次执行-----100W次转换耗时:1707
cn.hutool.core.bean.BeanUtil第2次执行---100W次转换耗时:5582
MapStruct第2次执行-----100W次转换耗时:1505
cn.hutool.core.bean.BeanUtil第3次执行---100W次转换耗时:4906
MapStruct第3次执行-----100W次转换耗时:1322
cn.hutool.core.bean.BeanUtil第4次执行---100W次转换耗时:3789
MapStruct第4次执行-----100W次转换耗时:1635
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
for (int i = 0; i < 5; i++) {
Long start = System.currentTimeMillis();
for (int i1 = 0; i1 < 1000000; i1++) {
Student student = Student.builder()
.name("小明")
.age(6)
.gender(GenderEnum.Male)
.height(121.1)
.birthday(new Date())
.build();
StudentVO studentVO = new StudentVO();
org.springframework.beans.BeanUtils.copyProperties(studentVO, student);
}
System.out.println("org.springframework.beans.BeanUtils第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
Long start2 = System.currentTimeMillis();
for (int i1 = 0; i1 < 1000000; i1++) {
Student student = Student.builder()
.name("小明")
.age(6)
.gender(GenderEnum.Male)
.height(121.1)
.birthday(new Date())
.build();
StudentMapper.INSTANCE.student2StudentVO(student);
}
System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
}
}
//测试结果
org.springframework.beans.BeanUtils第0次执行---100W次转换耗时:1156
MapStruct第0次执行-----100W次转换耗时:3376
org.springframework.beans.BeanUtils第1次执行---100W次转换耗时:290
MapStruct第1次执行-----100W次转换耗时:1367
org.springframework.beans.BeanUtils第2次执行---100W次转换耗时:506
MapStruct第2次执行-----100W次转换耗时:1586
org.springframework.beans.BeanUtils第3次执行---100W次转换耗时:318
MapStruct第3次执行-----100W次转换耗时:1319
org.springframework.beans.BeanUtils第4次执行---100W次转换耗时:304
MapStruct第4次执行-----100W次转换耗时:1226
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
for (int i = 0; i < 5; i++) {
Long start = System.currentTimeMillis();
for (int i1 = 0; i1 < 1000000; i1++) {
Student student = Student.builder()
.name("小明")
.age(6)
.gender(GenderEnum.Male)
.height(121.1)
.birthday(new Date())
.build();
StudentVO studentVO = new StudentVO();
org.springframework.cglib.beans.BeanCopier.create(StudentVO.class, Student.class, false);
}
System.out.println("org.springframework.cglib.beans.BeanCopier第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
Long start2 = System.currentTimeMillis();
for (int i1 = 0; i1 < 1000000; i1++) {
Student student = Student.builder()
.name("小明")
.age(6)
.gender(GenderEnum.Male)
.height(121.1)
.birthday(new Date())
.build();
StudentMapper.INSTANCE.student2StudentVO(student);
}
System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
}
}
//测试结果
org.springframework.cglib.beans.BeanCopier第0次执行---100W次转换耗时:725
MapStruct第0次执行-----100W次转换耗时:4524
org.springframework.cglib.beans.BeanCopier第1次执行---100W次转换耗时:210
MapStruct第1次执行-----100W次转换耗时:1663
org.springframework.cglib.beans.BeanCopier第2次执行---100W次转换耗时:72
MapStruct第2次执行-----100W次转换耗时:1446
org.springframework.cglib.beans.BeanCopier第3次执行---100W次转换耗时:80
MapStruct第3次执行-----100W次转换耗时:1482
org.springframework.cglib.beans.BeanCopier第4次执行---100W次转换耗时:84
MapStruct第4次执行-----100W次转换耗时:1441
工具类 | 执行1000 | 执行1w | 执行10w | 执行100w |
---|---|---|---|---|
Apache BeanUtils | 103.2 | 323.3 | 1105.8 | 7227.8 |
Hutool BeanUtil | 111 | 249.2 | 925.4 | 4420.6 |
Spring BeanUtils | 80 | 93.4 | 206.4 | 728.6 |
Cglib BeanCopier | 86.4 | 103.8 | 103.8 | 263 |
MapStruct | 53.6 | 137.8 | 447.8 | 2672.2 |