注:
- 没有提供对应的对象,自己实现,提高认识
- 学习方式:最好是对class进行一个反编译,看看他生成的代码。如果发现一些类型没设置成功也可以通过反编译查看。
- 反编译工具:java-decompiler
<!--导入的版本,参考官网-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
映射器定义
// 测试方法,后续都是这样测试
@Test
void test() {
BasicMapper instance = BasicMapper.INSTANCE;
BasicUserDTO convert = instance.convert(user);
}
基本映射
- 如果两个字段名称相同会自动映射。
- 如果两个字段名称不相同则需要使用
@Mapping
进行映射,参考 - 从多个源对象映射
@Mapper
public interface BasicMapper {
// 使用入口
BasicMapper INSTANCE = Mappers.getMapper(BasicMapper.class);
BasicUserDTO convert(BasicUser user);
}
自定义映射方法
// 接口方式
@Mapper
public interface BasicMapper {
BasicMapper INSTANCE = Mappers.getMapper(BasicMapper.class);
BasicUserDTO convert(BasicUser user);
default PersonDTO convertCustom(BasicUser user) {
return PersonDTO
.builder()
.id(String.valueOf(user.getId()))
.firstName(user.getName().substring(0, user.getName().indexOf(" ")))
.lastName(user.getName().substring(user.getName().indexOf(" ") + 1))
.build();
}
}
// 抽象类方式,好处:可以直接在映射器类中声明附加字段
@Mapper
public abstract class BasicMapper {
public abstract BasicUserDTO convert(BasicUser user);
public PersonDTO convertCustom(BasicUser user) {
return PersonDTO
.builder()
.id(String.valueOf(user.getId()))
.firstName(user.getName().substring(0, user.getName().indexOf(" ")))
.lastName(user.getName().substring(user.getName().indexOf(" ") + 1))
.build();
}
}
从多个源对象映射
- 当入参和返回的参数不匹配时,或者 有多个入参对象时,可以通过该方式指定需要映射到哪个字段
- source:传入进来的参数
- target:返回的参数
@Mapping(source = "user.id", target = "id")
@Mapping(source = "user.name", target = "firstName")
@Mapping(source = "education.degreeName", target = "educationalQualification")
@Mapping(source = "address.city", target = "residentialCity")
@Mapping(source = "address.country", target = "residentialCountry")
PersonDTO convert(BasicUser user, Education education, Address address);
映射嵌套对象
-
当有嵌套对象时,可以采用@Mapper的uses指定一个嵌套对象对应的映射类
@Data @Builder @ToString public class BasicUser { private int id; private String name; // 嵌套对象 private List<Manager> managerList; } // 指定一个嵌套对象的映射类 @Mapper(uses = {ManagerMapper.class}) public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); @Mapping(source = "user.id", target = "id") @Mapping(source = "user.name", target = "firstName") @Mapping(source = "education.degreeName", target = "educationalQualification") @Mapping(source = "address.city", target = "residentialCity") @Mapping(source = "address.country", target = "residentialCountry") PersonDTO convert(BasicUser user, Education education, Address address); }
-
在生成方法时,会通过指定的嵌套类对这个嵌套对象进行映射,如下所示
// 生成了一个managerListToManagerDTOList对嵌套类进行解析
更新现有实例
- 使用映射更新现有的 DTO
- 对需要更新的映射添加一个
@MappingTarget
注解,就会对其进行更新
@Mapping(source = "address.city", target = "residentialCity")
@Mapping(source = "address.country", target = "residentialCountry")
void updateExisting(Address address, @MappingTarget PersonDTO personDTO);
// 生成了一个DTO
PersonDTO personDTO = UserMapper.INSTANCE.convert(address);
// 对这个personDTO进行了更新
UserMapper.INSTANCE.updateExisting(address,personDTO);
继承配置
- 对于重复的配置,使用
@InheritConfiguration
,MapStruct 会查找已配置的方法,并且进行应用
@Mapper
public interface ManagerMapper {
ManagerMapper INSTANCE = Mappers.getMapper(ManagerMapper.class);
// 故意吧这两个顺序弄反,在测试看是否生效
@Mapping(source = "address.city", target = "residentialCountry")
@Mapping(source = "address.country", target = "residentialCity")
ManagerDTO convert(Manager manager);
@InheritConfiguration
void updateExisting(Manager manager, @MappingTarget ManagerDTO managerDTO);
}
逆映射
-
想定义一个双向映射
如:
-
Entity 映射 DTO
-
DTO 映射 Entity
-
-
使用
@InheritInverseConfiguration
会自动的反转配置@Mapper public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); // mapping的配置效果,也会被下面的反转映射所使用 BasicUserDTO convert(BasicUser user); @InheritInverseConfiguration // 反转映射 BasicUser convert(BasicUserDTO userDTO); }
映射期间的异常处理
-
自定义校检规则,映射期间如果发现跟校检的要求不匹配,则抛出异常(自定义)
-
步骤:
-
自定义异常
public class ValidationException extends RuntimeException { public ValidationException(String message, Throwable cause) { super(message, cause); } public ValidationException(String message) { super(message); } }
-
自定义校检规则
-
方法名要求:validate字段名(字段类型)
-
注意事项:校检的字段类型要和形参以及异常进行匹配,否则的话匹配不到则不会生效
public class Validator { public int validateId(int id) throws ValidationException { if(id < 0){ throw new ValidationException("Invalid ID value"); } return id; } }
-
-
使用
// 导入校检规则 @Mapper(uses = { Validator.class}) public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); // 抛出对应的异常 BasicUserDTO convert(BasicUser user) throws ValidationException; }
-
数据类型转换
隐式类型转换
-
数值
@Mapping(source = "employment.salary", target = "salary", numberFormat = "$#.00") PersonDTO convert(BasicUser user, Education education, Address address, Employment employment); // 会自动转换,如下: personDTO.setSalary( new DecimalFormat( "$#.00" ).format( employment.getSalary() ) );
-
日期
@Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy") ManagerDTO convert(Manager manager); // 会自动转换,如下: managerDTO.setDateOfBirth( new SimpleDateFormat( "dd/MMM/yyyy" ) .parse( manager.getDateOfBirth() ) ); // 如果没自定义转换, 则生成如下: managerDTO.setDateOfBirth( new SimpleDateFormat().parse( manager.getDateOfBirth() ) );
映射集合
-
通过循环遍历,进行映射。
-
如果使用了@Mapping的uses则会自动调用此对应的映射方法来执行元素转换。
-
简单使用
@Mapper public interface CollectionMapper { CollectionMapper INSTANCE = Mappers.getMapper(CollectionMapper.class); Set<String> convert(Set<Long> ids); Set<EmploymentDTO> convertEmployment(Set<Employment> employmentSet); }
-
需要对实体进行自定义映射,则需要先定义实体之间的转换方法。
@Mapper public interface CollectionMapper { CollectionMapper INSTANCE = Mappers.getMapper(CollectionMapper.class); // 自定义映射 @Mapping(source = "degreeName", target = "degree") @Mapping(source = "institute", target = "college") @Mapping(source = "yearOfPassing", target = "passingYear") EducationDTO convert(Education education); // 会去匹配自定义映射进行转换 List<EducationDTO> convert(List<Education> educationList); } // 会生成如下代码: educationDTO.degree( education.getDegreeName() ); educationDTO.college( education.getInstitute() ); educationDTO.passingYear( education.getYearOfPassing() );
-
对map进行映射
- 可以通过 keyNumberFormat 和 valueDateFormat 对转入的键值做一个转换
@Mapper public interface CollectionMapper { CollectionMapper INSTANCE = Mappers.getMapper(CollectionMapper.class); @MapMapping(keyNumberFormat = "#L", valueDateFormat = "dd.MM.yyyy") Map<String, String> map(Map<Long, Date> dateMap); } // 生成如下代码: String key = new DecimalFormat( "#L" ).format( entry.getKey() ); String value = new SimpleDateFormat( "dd.MM.yyyy" ).format( entry.getValue() );
映射策略
- 默认值为
ACCESSOR_ONLY
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4eI45Pl-1660918092350)(images/mapstruct - 集合映射策略.png)]
// 使用ADDER_PREFERRED策略
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
public interface PersonMapperAdderPreferred {
PersonDTO map(Person person);
}
映射流
- 和映射集合相同,只是
Stream
会从提供的返回Iterable
:
@Mapper
public interface CollectionMapper {
CollectionMapper INSTANCE = Mappers.getMapper(CollectionMapper.class);
Set<String> convertStream(Stream<Long> ids);
@Mapping(source = "degreeName", target = "degree")
@Mapping(source = "institute", target = "college")
@Mapping(source = "yearOfPassing", target = "passingYear")
EducationDTO convert(Education education);
List<EducationDTO> convert(Stream<Education> educationStream);
}
// 生成如下:
return ids.map( long1 -> String.valueOf( long1 ) )
.collect( Collectors.toCollection( HashSet<String>::new ) );
映射枚举
-
名字相同,则直接映射即可
-
名字不相同,使用
@ValueMapping
进行映射无法识别源值,抛出 IllegalStateException。
public enum DesignationCode {CEO} public enum DesignationConstant {CHIEF_EXECUTIVE_OFFICER} @Mapper public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); @ValueMappings({ @ValueMapping(source = "CEO", target = "CHIEF_EXECUTIVE_OFFICER"), }) DesignationConstant convertDesignation(DesignationCode code); }
-
如果有前缀,则使用如下4个属性进行映射
suffix
- 在源枚举上应用后缀stripSuffix
- 从源枚举中去除后缀prefix
- 在源枚举上应用前缀stripPrefix
- 从源枚举中去除前缀
public enum DegreeStream {MATHS} public enum DegreeStreamPrefix {MSC_MATHS} @Mapper public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); @EnumMapping(nameTransformationStrategy = "prefix", configuration = "MSC_") DegreeStreamPrefix convert(DegreeStream degreeStream); @EnumMapping(nameTransformationStrategy = "stripPrefix", configuration = "MSC_") DegreeStream convert(DegreeStreamPrefix degreeStreamPrefix); }
定义默认值或常量
- defaultValue:当值不存在时,则使用默认值
- constant:映射到目标枚举类型中具有相同名称的常量
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
uses = {CollectionMapper.class, ManagerMapper.class, Validator.class},
imports = UUID.class )
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(source = "education.yearOfPassing", target = "education.passingYear",
defaultValue = "2001")
@Mapping(source = "employment", target = ".")
@Mapping(target = "residentialCountry", constant = "US")
PersonDTO convert(BasicUser user,
Education education,
Address address,
Employment employment);
}
定义默认表达式
- 用于使用java表达式
- 在源属性为
null
时使用,才会触发。 - 还需要导入对应的类
@Mapper( imports = UUID.class )
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(source = "user.id", target = "id",
defaultExpression = "java( UUID.randomUUID().toString() )")
PersonDTO convert(BasicUser user,
Education education,
Address address,
Employment employment);
}
映射器检索策略
-
不使用依赖注入框架,使用
Mappers
获取映射器实例@Mapper public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); } // 使用 PersonDTO personDTO = UserMapper.INSTANCE.convert(user);
-
使用
@componentModel
注解导入依赖注入- 支持CDI和Spring框架
@Mapper(componentModel = "spring") public interface UserMapper {} // 生成如下: @Component public class UserMapperImpl implements UserMapper {} // 使用 @Controller public class UserController() { @Autowired private UserMapper userMapper; }
映射定制
- 使用装饰器模式,进行自定义
- 使用
@BeforeMapping
/@AfterMapping
,进行通用的设置
装饰器
- 定义一个Decorator类,在使用
@DecoratedWith
使其生效 - 对需要自定义映射的方法进行实现,其他的方法用默认实现生成对原始映射器的委托。
public abstract class UserMapperDecorator implements UserMapper {
private final UserMapper delegate;
protected UserMapperDecorator (UserMapper delegate) {
this.delegate = delegate;
}
@Override
public PersonDTO convert(BasicUser user,
Education education,
Address address,
Employment employment) {
// 委托
PersonDTO dto = delegate.convert(user, education, address, employment);
if (user.getName().split("\\w+").length > 1) {
dto.setFirstName(user.getName().substring(0, user.getName().lastIndexOf(' ')));
dto.setLastName(user.getName().substring(user.getName().lastIndexOf(" ") + 1));
}
else {
dto.setFirstName(user.getName());
}
return dto;
}
}
// 使用
@Mapper
@DecoratedWith(UserMapperDecorator.class)
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
PersonDTO convert(BasicUser user, Education education, Address address, Employment employment);
}
@BeforeMapping和@AfterMapping
@BeforeMapping
用于执行前,运行指定的逻辑@AfterMapping
用于执行后,运行指定的逻辑
@Mapper
@DecoratedWith(UserMapperDecorator.class)
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
// 执行前, 如果manager为null则设置一个空集合
@BeforeMapping
default void validateMangers(BasicUser user) {
if (Objects.isNull(user.getManagerList())) {
user.setManagerList(new ArrayList<>());
}
}
@Mapping(target = "residentialCountry", constant = "US")
void updateExisting( Address address );
// 执行后,对firstName和LastName进行一个字符转换
@AfterMapping
default void updateResult(BasicUser user,
@MappingTarget PersonDTO personDTO) {
personDTO.setFirstName(personDTO.getFirstName().toUpperCase());
personDTO.setLastName(personDTO.getLastName().toUpperCase());
}
}