场景6:已有目标对象,将源对象属性覆盖到目标对象
覆盖目标对象属性时,一般null值不覆盖,所以需要在类上的@Mapper注解中添加属性:nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE 代表null值不进行赋值。
关键字:@MappingTarget注解、nullValuePropertyMappingStrategy
/** * 来源对象 */ @Data public class Demo { private Integer id; private String name; } /** * 目标对象 */ @Data public class DemoDto { private String id; private String name; } /** * 转换器 */ @Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) public interface DemoMapper { DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class); //将已有的目标对象当作一个参数传进来 DemoDto toDemoDto(Demo demo, @MappingTarget DemoDto dto); }
场景7:源对象两个属性合并为一个属性
这种情况可以使用@AfterMapping注解。
关键字:@AfterMapping注解、@MappingTarget注解
/** * 来源对象 */ @Data public class Demo { private Integer id; private String firstName; private String lastName; } /** * 目标对象 */ @Data public class DemoDto { private String id; private String name; } /** * 转换器 */ @Mapper public interface DemoMapper { DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class); DemoDto toDemoDto(Demo demo); //在转换完成后执行的方法,一般用到源对象两个属性合并为一个属性的场景 //需要将源对象、目标对象(@MappingTarget)都作为参数传进来, @AfterMapping static void afterToDemoDto(Demo demo, @MappingTarget DemoDto demoDto) { String name = demo.getFirstName() + demo.getLastName(); demoDto.setName(name); } }
本文介绍了对象转换工具 MapStruct 库,以安全、简洁、优雅的方式来优化我们的转换代码。
从文中的示例场景中可以看出,MapStruct 提供了大量的功能和配置,使我们可以快捷的创建出各种或简单或复杂的映射器。而这些,也只是 MapStruct 库的冰山一角,还有很多强大的功能文中没有提到,感兴趣的朋友可以自行查看官方文档。
4.0 扩展(网上项目参考)
pom 配置:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <org.mapstruct.version>1.4.1.Final</org.mapstruct.version> <org.projectlombok.version>1.18.12</org.projectlombok.version> </properties> <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> <!-- lombok dependencies should not end up on classpath --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${org.projectlombok.version}</version> <scope>provided</scope> </dependency> <!-- idea 2018.1.1 之前的版本需要添加下面的配置,后期的版本就不需要了,可以注释掉, 我自己用的2019.3 --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${org.projectlombok.version}</version> </path> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
关于lombok和mapstruct的版本兼容问题多说几句,maven插件要使用3.6.0版本以上、lombok使用1.16.16版本以上,另外编译的lombok mapstruct的插件不要忘了加上。否则会出现下面的错误:
No property named "aaa" exists in source parameter(s). Did you mean "null"?
这种异常就是lombok编译异常导致缺少get setter方法造成的。还有就是缺少构造函数也会抛异常。
实体类:
@Data @Builder @AllArgsConstructor @NoArgsConstructor public class Student { private String name; private int age; private GenderEnum gender; private Double height; private Date 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; } } @Data @Builder @AllArgsConstructor @NoArgsConstructor public class StudentVO { private String name; private int age; private String gender; private Double height; private String birthday; } @Mapper public interface StudentMapper { StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class); @Mapping(source = "gender.name", target = "gender") @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss") StudentVO student2StudentVO(Student student); }
实体类是开发过程少不了的,就算是用工具生成肯定也是要有的,需要手写的部分就是这个Mapper的接口,编译完成后会自动生成相应的实现类(targer)
然后就可以直接用mapper进行实体的转换了
public class Test { public static void main(String[] args) { Student student = Student.builder().name("小明").age(6).gender(GenderEnum.Male).height(121.1).birthday(new Date()).build(); System.out.println(student); //这行代码便是实际要用的代码 StudentVO studentVO = StudentMapper.INSTANCE.student2StudentVO(student); System.out.println(studentVO); } }
mapper可以进行字段映射,改变字段类型,指定格式化的方式,包括一些日期的默认处理。
可以手动指定格式化的方法:
@Mapper public interface StudentMapper { StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class); @Mapping(source = "gender", target = "gender") @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss") StudentVO student2StudentVO(Student student); default String getGenderName(GenderEnum gender) { return gender.getName(); } }
上面只是最简单的实体映射处理,下面介绍一些高级用法
- List 转换
属性映射基于上面的mapping配置
@Mapper public interface StudentMapper { StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class); @Mapping(source = "gender.name", target = "gender") @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss") StudentVO student2StudentVO(Student student); List<StudentVO> students2StudentVOs(List<Student> studentList); } public static void main(String[] args) { Student student = Student.builder().name("小明").age(6).gender(GenderEnum.Male).height(121.1).birthday(new Date()).build(); List<Student> list = new ArrayList<>(); list.add(student); List<StudentVO> result = StudentMapper.INSTANCE.students2StudentVOs(list); System.out.println(result); }
2.多对象转换到一个对象
@Data @Builder @AllArgsConstructor @NoArgsConstructor public class Student { private String name; private int age; private GenderEnum gender; private Double height; private Date birthday; } @Data @AllArgsConstructor @Builder @NoArgsConstructor public class Course { private String courseName; private int sortNo; private long id; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public class StudentVO { private String name; private int age; private String gender; private Double height; private String birthday; private String course; } @Mapper public interface StudentMapper { StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class); @Mapping(source = "student.gender.name", target = "gender") @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss") @Mapping(source = "course.courseName", target = "course") StudentVO studentAndCourse2StudentVO(Student student, Course course); } public class Test { public static void main(String[] args) { Student student = Student.builder().name("小明").age(6).gender(GenderEnum.Male).height(121.1).birthday(new Date()).build(); Course course = Course.builder().id(1L).courseName("语文").build(); StudentVO studentVO = StudentMapper.INSTANCE.studentAndCourse2StudentVO(student, course); System.out.println(studentVO); } }
3.默认值
@Mapper public interface StudentMapper { StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class); @Mapping(source = "student.gender.name", target = "gender") @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss") @Mapping(source = "course.courseName", target = "course") @Mapping(target = "name", source = "student.name", defaultValue = "张三") StudentVO studentAndCourse2StudentVO(Student student, Course course); }
4.1 扩展 dozer使用
<dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.5.1</version> </dependency>
然后分别创建一个UserVo和UserDto,并对他们做转换
首先让两个类的属性名称一样,如下
@Data public class UserVo { private String name; private int id; } @Data public class UserDto { private String name; private int id; }
接下来怎么转换呢,代码如下
Mapper dozerMapper = new DozerBeanMapper();
UserVo userVo = new UserVo(); userVo.setName("mg"); userVo.setId(10); InUserDto userDto = dozerMapper.map(userVo,UserDto.class); System.out.println(userDto.getName());
输出结果为
mg
UserVo转换为UserDto,这是属性完全相同的情况,不过通常属性名称都是不同的,那怎么办呢
修改UserVo 为
@Data public class UserVo { private String sname; private int sid; }
@Mapping(“实体类对应名字”) 这样就可以解决对应的实体类个别不一样的问题;
修改UserDto为 @Data public class UserDto { @Mapping("sname") private String name; @Mapping("sid") private int id; }
在执行下刚写的转换函数,试试是否能成功转换了