MapStruct 使用指南
文章目录
一、背景
在我们开发的软件系统中,有着各式各样用于存储数据的实体,按照一般标准,可分为以下几类
- POJO(Plain Ordinary Java Object):简单 Java 对象,就是普通 Java 对象,普通 JavaBeans ,有时可以作为 VO(value-object)或 DTO(Data Transfer Object)来使用
- VO(View Object):视图对象,用于与前端的交互的展示层,作用是封装返回给前端的数据起
- DTO(Data Transfer Object):数据传输对象,泛指用于各个层级间的数据传输对象。即提取依赖资源中所需要的的属性,同时减少不需要的属性,来提高传输速度,以及减少流量。
- Entity :entity里的每一个字段,与数据库相对应。
- 。。。
上述几类实体,经常需要互相转换,而手动创建 bean 映射器非常耗时,有没有更简单的解决方案呢?
MapStruct 应运而生,它可以自动生成 Bean 映射器类,来完成各个实体键数据的传输
在本文中,我们将深入研究 MapStruct。
二、简介
MapStruct 是一个开源的基于 Java 的代码生成器,用于创建实现 Java Bean 之间转换的扩展映射器。
使用 MapStruct,我们只需要创建接口,而该库会通过注解,在编译过程中自动创建具体的映射实现,大大减少了通常需要手工编写的样板代码的数量。
三、MapStruct 依赖
- 注意:mapstruct 与 lombok 版本必须匹配,否则会出现映射不上的问题。
1、Maven
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
由于 MapStruct 在编译时工作,并且会集成到像 Maven 和 Gradle 这样的构建工具上,我们还必须在 <build/>
标签中添加一个插件 maven-compiler-plugin
。
并在其配置中添加 annotationProcessorPaths
,该插件会在构建时生成对应的代码(这一步也可以不加,直接在依赖中添加 mapstruct-processor
,如上)
<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>
// 同时使用了 lombok ,必须加上这个,否则编译不通过
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
2、Gradle
plugins {
id 'net.ltgt.apt' version '0.20'
}
apply plugin: 'net.ltgt.apt-idea'
apply plugin: 'net.ltgt.apt-eclipse'
dependencies {
compile "org.mapstruct:mapstruct:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
}
net.ltgt.apt
插件会负责处理注释。你可以根据使用的 IDE 启用插件 apt-idea
或 apt-eclipse
插件。
MapStruct 及其 处理器 的最新稳定版本都可以从 Maven中央仓库 中获得。
四、映射
1、基本映射
实例讲解
public class Doctor {
private int id;
private String name;
// getters and setters or builder
}
public class DoctorDto {
private int id;
private String name;
// getters and setters or builder
}
为了在这两者之间进行映射,我们要创建一个 DoctorMapper
接口。对该接口使用 @Mapper
注解,MapStruct 就会知道这是两个类之间的映射器。
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
DoctorDto toDto(Doctor doctor);
}
当我们构建/编译应用程序时,MapStruct 注解处理器插件会识别出 DoctorMapper 接口并为其生成一个实现类。
public class DoctorMapperImpl implements DoctorMapper {
@Override
public DoctorDto toDto(Doctor doctor) {
if ( doctor == null ) {
return null;
}
DoctorDtoBuilder doctorDto = DoctorDto.builder();
doctorDto.id(doctor.getId());
doctorDto.name(doctor.getName());
return doctorDto.build();
}
}
使用
DoctorDto doctorDto = DoctorMapper.INSTANCE.toDto(doctor);
注意:你可能也注意到了上面实现代码中的DoctorDtoBuilder
。因为 builder 代码往往比较长,为了简洁起见,这里省略了builder模式的实现代码。如果你的类中包含Builder,MapStruct 会尝试使用它来创建实例;如果没有的话,MapStruct 将通过 new
关键字进行实例化。
2、不同字段间映射
public class Doctor {
private int id;
private String name;
private String specialty;
// getters and setters or builder
}
public class DoctorDto {
private int id;
private String name;
private String specialization;
// getters and setters or builder
}
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
@Mapping(source = "doctor.specialty", target = "specialization")
DoctorDto toDto(Doctor doctor);
}
这个注解代码的含义是:Doctor
中的 specialty
字段对应于 DoctorDto
类的 specialization
。
编译之后,自动生成以下实现类
public class DoctorMapperImpl implements DoctorMapper {
@Override
public DoctorDto toDto(Doctor doctor) {
if (doctor == null) {
return null;
}
DoctorDtoBuilder doctorDto = DoctorDto.builder();
doctorDto.specialization(doctor.getSpecialty());
doctorDto.id(doctor.getId());
doctorDto.name(doctor.getName());
return doctorDto.build();
}
}
3、多个源类
有时候,需要将多个类中的值聚合为一个 DTO,以下为演示代码:
public class Education {
private String degreeName;
private String institute;
private Integer yearOfPassing;
// getters and setters or builder
}
public class DoctorDto {
private int id;
private String name;
private String degree;
private String specialization;
// getters and setters or builder
}
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
@Mapping(source = "doctor.specialty", target = "specialization")
@Mapping(source = "education.degreeName", target = "degree")
DoctorDto toDto(Doctor doctor, Education education);
}
如果 Education
类和 Doctor
类包含同名的字段,我们必须让映射器知道使用哪一个,否则它会抛出一个异常。
举例来说,如果两个模型都包含一个 id
字段,我们就要选择将哪个类中的 id
映射到 DTO 属性中。
4、子对象映射
多数情况下,POJO 中不会只包含基本数据类型,其中往往会包含其它类,MapStruct 也支持对类里面的复杂对象,进行映射,演示代码如下:
public class Patient {
private int id;
private String name;
// getters and setters or builder
}
public class Doctor {
private int id;
private String name;
private String specialty;
private List<Patient> patientList;
// getters and setters or builder
}
public class PatientDto {
private int id;
private String name;
// getters and setters or builder
}
public class DoctorDto {
private int id;
private String name;
private String degree;
private String specialization;
private List<PatientDto> patientDtoList;
// getters and setters or builder
}
@Mapper
public interface PatientMapper {
PatientMapper INSTANCE = Mappers.getMapper(PatientMapper.class);
PatientDto toDto(Patient patient);
}
@Mapper(uses = {PatientMapper.class})
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
@Mapping(source = "doctor.patientList", target = "patientDtoList")
@Mapping(source = "doctor.specialty", target = "specialization")
DoctorDto toDto(Doctor doctor);
}
因为我们要处理另一个需要映射的类,所以这里设置了 @Mapper
注解的 uses
标志,这样现在的 @Mapper
就可以使用另一个 @Mapper
映射器。我们这里只加了一个,但你想在这里添加多少 class/mapper 都可以。
我们已经添加了 uses
标志,所以在为 DoctorMapper
接口生成映射器实现时,MapStruct 也会把 Patient
模型转换成 PatientDto
——因为我们已经为这个任务注册了 PatientMapper
。
实现代码:
public class DoctorMapperImpl implements DoctorMapper {
private final PatientMapper patientMapper = Mappers.getMapper( PatientMapper.class );
@Override
public DoctorDto toDto(Doctor doctor) {
if ( doctor == null ) {
return null;
}
DoctorDtoBuilder doctorDto = DoctorDto.builder();
doctorDto.patientDtoList( patientListToPatientDtoList(doctor.getPatientList()));
doctorDto.specialization( doctor.getSpecialty() );
doctorDto.id( doctor.getId() );
doctorDto.name( doctor.getName() );
return doctorDto.build();
}
protected List<PatientDto> patientListToPatientDtoList(List<Patient> list) {
if ( list == null ) {
return null;
}
List<PatientDto> list1 = new ArrayList<PatientDto>( list.size() );
for ( Patient patient : list ) {
list1.add( patientMapper.toDto( patient ) );
}
return list1;
}
}
5、更新现有实例
有时,需要用 DTO 的最新值更新一个模型中的属性,对目标对象使用 @MappingTarget
注解,就可以更新现有的实例.
@Mapper(uses = {PatientMapper.class})
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
@Mapping(source = "doctorDto.patientDtoList", target = "patientList")
@Mapping(source = "doctorDto.specialization", target = "specialty")
void updateModel(DoctorDto doctorDto, @MappingTarget Doctor doctor);
}
重新生成实现代码,就可以得到 updateModel()
方法:
public class DoctorMapperImpl implements DoctorMapper {
@Override
public void updateModel(DoctorDto doctorDto, Doctor doctor) {
if (doctorDto == null) {
return;
}
if (doctor.getPatientList() != null) {
List<Patient> list = patientDtoListToPatientList(doctorDto.getPatientDtoList());
if (list != null) {
doctor.getPatientList().clear();
doctor.getPatientList().addAll(list);
} else {
doctor.setPatientList(null);
}
} else {
List<Patient> list = patientDtoListToPatientList(doctorDto.getPatientDtoList());
if (list != null) {
doctor.setPatientList(list);
}
}
doctor.setSpecialty(doctorDto.getSpecialization());
doctor.setId(doctorDto.getId());
doctor.setName(doctorDto.getName());
}
}
五、数据类型转换
1、数据类型映射
MapStruct 支持 source
和 target
属性之间的数据类型转换。它还提供了基本类型及其相应的包装类之间的自动转换。
自动类型转换适用于:
- 基本类型及其对应的包装类之间。比如,
int
和Integer
,float
和Float
,long
和Long
,boolean
和Boolean
等。 - 任意基本类型与任意包装类之间。如
int
和long
,byte
和Integer
等。 - 所有基本类型及包装类与
String
之间。如boolean
和String
,Integer
和String
,float
和String
等。 - 枚举和
String
之间。 - Java大数类型(
java.math.BigInteger
,java.math.BigDecimal
) 和 Java 基本类型(包括其包装类)与String
之间。 - 其它情况详见 MapStruct 官方文档。
public class PatientDto {
private int id;
private String name;
private LocalDate dateOfBirth;
// getters and setters or builder
}
public class Patient {
private int id;
private String name;
private String dateOfBirth;
// getters and setters or builder
}
@Mapper
public interface PatientMapper {
@Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy")
Patient toModel(PatientDto patientDto);
}
当对日期进行转换时,我们也可以使用 dateFormat
设置格式声明。生成的实现代码形式大致如下:
public class PatientMapperImpl implements PatientMapper {
@Override
public Patient toModel(PatientDto patientDto) {
if (patientDto == null) {
return null;
}
PatientBuilder patient = Patient.builder();
if (patientDto.getDateOfBirth() != null) {
patient.dateOfBirth(DateTimeFormatter.ofPattern("dd/MMM/yyyy")
.format(patientDto.getDateOfBirth()));
}
patient.id(patientDto.getId());
patient.name(patientDto.getName());
return patient.build();
}
}
如果我们没有声明 dateFormat
格式的话,MapStruct 会使用 LocalDate
的默认格式,大致如下:
if (patientDto.getDateOfBirth() != null) {
patient.dateOfBirth(DateTimeFormatter.ISO_LOCAL_DATE
.format(patientDto.getDateOfBirth()));
}
2、数字格式转换
// 数字格式转换示例
@Mapping(source = "price", target = "price", numberFormat = "$#.00")
3、枚举映射
public enum PaymentType {
CASH,
CHEQUE,
CARD_VISA,
CARD_MASTER,
CARD_CREDIT
}
public enum PaymentTypeView {
CASH,
CHEQUE,
CARD
}
@Mapper
public interface PaymentTypeMapper {
PaymentTypeMapper INSTANCE = Mappers.getMapper(PaymentTypeMapper.class);
@ValueMappings({
@ValueMapping(source = "CARD_VISA", target = "CARD"),
@ValueMapping(source = "CARD_MASTER", target = "CARD"),
@ValueMapping(source = "CARD_CREDIT", target = "CARD")
})
PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType);
}
实现代码:
public class PaymentTypeMapperImpl implements PaymentTypeMapper {
@Override
public PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType) {
if (paymentType == null) {
return null;
}
PaymentTypeView paymentTypeView;
switch (paymentType) {
case CARD_VISA: paymentTypeView = PaymentTypeView.CARD;
break;
case CARD_MASTER: paymentTypeView = PaymentTypeView.CARD;
break;
case CARD_CREDIT: paymentTypeView = PaymentTypeView.CARD;
break;
case CASH: paymentTypeView = PaymentTypeView.CASH;
break;
case CHEQUE: paymentTypeView = PaymentTypeView.CHEQUE;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + paymentType );
}
return paymentTypeView;
}
}
但是,如果你要将很多值转换为一个更一般的值,这种方式就有些不切实际了。其实我们不必手动分配每一个值,只需要让 MapStruct 将所有剩余的可用枚举项(在目标枚举中找不到相同名称的枚举项),直接转换为对应的另一个枚举项。
可以通过 MappingConstants
实现这一点:
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "CARD")
PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType);
实现代码:
@Override
public PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType) {
if ( paymentType == null ) {
return null;
}
PaymentTypeView paymentTypeView;
switch ( paymentType ) {
case CASH: paymentTypeView = PaymentTypeView.CASH;
break;
case CHEQUE: paymentTypeView = PaymentTypeView.CHEQUE;
break;
default: paymentTypeView = PaymentTypeView.CARD;
}
return paymentTypeView;
}
还有一种选择,使用 ANY UNMAPPED
:
@ValueMapping(source = MappingConstants.ANY_UNMAPPED, target = "CARD")
PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType);
4、集合映射
1)、List映射
@Mapper
public interface DoctorMapper {
List<DoctorDto> map(List<Doctor> doctor);
}
实现代码:
public class DoctorMapperImpl implements DoctorMapper {
@Override
public List<DoctorDto> map(List<Doctor> doctor) {
if ( doctor == null ) {
return null;
}
List<DoctorDto> list = new ArrayList<DoctorDto>( doctor.size() );
for ( Doctor doctor1 : doctor ) {
list.add( doctorToDoctorDto( doctor1 ) );
}
return list;
}
protected DoctorDto doctorToDoctorDto(Doctor doctor) {
if ( doctor == null ) {
return null;
}
DoctorDto doctorDto = new DoctorDto();
doctorDto.setId( doctor.getId() );
doctorDto.setName( doctor.getName() );
doctorDto.setSpecialization( doctor.getSpecialization() );
return doctorDto;
}
}
但是需要注意,如果我们在 DTO 中新增一个字段 fullName
,生成代码时会出现警告:
警告: Unmapped target property: “fullName”.
基本上,这意味着 MapStruct 在当前情况下无法为我们自动生成映射方法。因此,我们需要手动定义 Doctor
和 DoctorDto
之间的映射方法。具体参考之前的小节。
2)、Set和Map映射
@Mapper
public interface DoctorMapper {
Set<DoctorDto> setConvert(Set<Doctor> doctor);
Map<String, DoctorDto> mapConvert(Map<String, Doctor> doctor);
}
实现代码:
public class DoctorMapperImpl implements DoctorMapper {
@Override
public Set<DoctorDto> setConvert(Set<Doctor> doctor) {
if ( doctor == null ) {
return null;
}
Set<DoctorDto> set = new HashSet<DoctorDto>( Math.max( (int) ( doctor.size() / .75f ) + 1, 16 ) );
for ( Doctor doctor1 : doctor ) {
set.add( doctorToDoctorDto( doctor1 ) );
}
return set;
}
@Override
public Map<String, DoctorDto> mapConvert(Map<String, Doctor> doctor) {
if ( doctor == null ) {
return null;
}
Map<String, DoctorDto> map = new HashMap<String, DoctorDto>( Math.max( (int) ( doctor.size() / .75f ) + 1, 16 ) );
for ( java.util.Map.Entry<String, Doctor> entry : doctor.entrySet() ) {
String key = entry.getKey();
DoctorDto value = doctorToDoctorDto( entry.getValue() );
map.put( key, value );
}
return map;
}
protected DoctorDto doctorToDoctorDto(Doctor doctor) {
if ( doctor == null ) {
return null;
}
DoctorDto doctorDto = new DoctorDto();
doctorDto.setId( doctor.getId() );
doctorDto.setName( doctor.getName() );
doctorDto.setSpecialization( doctor.getSpecialization() );
return doctorDto;
}
}
3)、集合映射策略
@Mapper
注解中的 collectionMappingStrategy
属性,该属性取值如下
ACCESSOR_ONLY
(默认值)SETTER_PREFERRED
ADDER_PREFERRED
TARGET_IMMUTABLE
。
public class Hospital {
private List<Doctor> doctors;
// getters and setters or builder
}
public class HospitalDto {
private List<DoctorDto> doctors;
// 子类型集合字段getter
public List<DoctorDto> getDoctors() {
return doctors;
}
// 子类型集合字段setter
public void setDoctors(List<DoctorDto> doctors) {
this.doctors = doctors;
}
// 子类型数据adder
public void addDoctor(DoctorDto doctorDTO) {
if (doctors == null) {
doctors = new ArrayList<>();
}
doctors.add(doctorDTO);
}
}
映射器:
@Mapper(uses = DoctorMapper.class)
public interface HospitalMapper {
HospitalMapper INSTANCE = Mappers.getMapper(HospitalMapper.class);
HospitalDto toDto(Hospital hospital);
}
实现代码:
public class HospitalMapperImpl implements HospitalMapper {
@Override
public HospitalDto toDto(Hospital hospital) {
if ( hospital == null ) {
return null;
}
HospitalDto hospitalDto = new HospitalDto();
hospitalDto.setDoctors( doctorListToDoctorDtoList( hospital.getDoctors() ) );
return hospitalDto;
}
}
可以看到,在默认情况下采用的策略是 ACCESSOR_ONLY
,使用setter方法 setDoctors()
向 HospitalDto
对象中写入列表数据。
使用 ADDER_PREFERRED
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
uses = DoctorMapper.class)
public interface HospitalMapper {
HospitalMapper INSTANCE = Mappers.getMapper(HospitalMapper.class);
HospitalDto toDto(Hospital hospital);
}
public class CompanyMapperAdderPreferredImpl implements CompanyMapperAdderPreferred {
private final EmployeeMapper employeeMapper = Mappers.getMapper( EmployeeMapper.class );
@Override
public CompanyDTO map(Company company) {
if ( company == null ) {
return null;
}
CompanyDTO companyDTO = new CompanyDTO();
if ( company.getEmployees() != null ) {
for ( Employee employee : company.getEmployees() ) {
companyDTO.addEmployee( employeeMapper.map( employee ) );
}
}
return companyDTO;
}
}
如果目标 DTO 中既没有 setter
方法也没有 adder
方法,会先通过 getter
方法获取子类型集合,再调用集合的对应接口添加子类型对象。
其他策略参考 官方文档
4)、目标集合实现类型
MapStruct 支持将集合接口作为映射方法的目标类型。
在这种情况下,在生成的代码中会使用一些集合接口默认实现。 例如,上面的示例中,List
的默认实现是 ArrayList
。
常见接口及其对应的默认实现如下:
Interface type | Implementation type |
---|---|
Collection | ArrayList |
List | ArrayList |
Map | HashMap |
SortedMap | TreeMap |
ConcurrentMap | ConcurrentHashMap |
更多映射参考 官方文档
可以找到 MapStruct 支持的所有接口列表,以及每个接口对应的默认实现类型。
六、进阶操作
1、依赖注入
修改 DoctorMapper
以支持 Spring 框架:
@Mapper(componentModel = "spring")
public interface DoctorMapper {}
实现代码:
@Component
public class DoctorMapperImpl implements DoctorMapper {}
使用:
@Controller
public class DoctorController() {
@Autowired
private DoctorMapper doctorMapper;
}
2、添加默认值
@Mapping
注解标志属性:
- 常量
constant
:无论source
如何取值,都将始终使用常量值; - 默认值
defaultValue
:如果source
取值为null
,则会使用默认值。
@Mapper(uses = {PatientMapper.class}, componentModel = "spring")
public interface DoctorMapper {
@Mapping(target = "id", constant = "-1")
@Mapping(source = "doctor.patientList", target = "patientDtoList")
@Mapping(source = "doctor.specialty", target = "specialization", defaultValue = "Information Not Available")
DoctorDto toDto(Doctor doctor);
}
实现代码:
@Component
public class DoctorMapperImpl implements DoctorMapper {
@Autowired
private PatientMapper patientMapper;
@Override
public DoctorDto toDto(Doctor doctor) {
if (doctor == null) {
return null;
}
DoctorDto doctorDto = new DoctorDto();
if (doctor.getSpecialty() != null) {
doctorDto.setSpecialization(doctor.getSpecialty());
} else {
doctorDto.setSpecialization("Information Not Available");
}
doctorDto.setPatientDtoList(patientListToPatientDtoList(doctor.getPatientList()));
doctorDto.setName(doctor.getName());
doctorDto.setId(-1);
return doctorDto;
}
}
3、添加表达式
@Mapping
注解添加 Java 表达式。
defaultExpression
(source
取值为null
时生效)expression
(类似常量,永久生效)。
public class Doctor {
private int id;
private String name;
private String externalId;
private String specialty;
private LocalDateTime availability;
private List<Patient> patientList;
// getters and setters or builder
}
public class DoctorDto {
private int id;
private String name;
private String externalId;
private String specialization;
private LocalDateTime availability;
private List<PatientDto> patientDtoList;
// getters and setters or builder
}
@Mapper(uses = {PatientMapper.class}, componentModel = "spring", imports = {LocalDateTime.class, UUID.class})
public interface DoctorMapper {
@Mapping(target = "externalId", expression = "java(UUID.randomUUID().toString())")
@Mapping(source = "doctor.availability", target = "availability", defaultExpression = "java(LocalDateTime.now())")
@Mapping(source = "doctor.patientList", target = "patientDtoList")
@Mapping(source = "doctor.specialty", target = "specialization")
DoctorDto toDtoWithExpression(Doctor doctor);
}
表达式只是字符串,我们必须在表达式中指定使用的类。但是这里的表达式并不是最终执行的代码,只是一个字母的文本值。因此,我们要在 @Mapper
中添加 imports = {LocalDateTime.class, UUID.class}
4、添加自定义方法
a、使用接口
- 用
default
实现自定义方法
public class DoctorPatientSummary {
private int doctorId;
private int patientCount;
private String doctorName;
private String specialization;
private String institute;
private List<Integer> patientIds;
// getters and setters or builder
}
@Mapper
public interface DoctorMapper {
default DoctorPatientSummary toDoctorPatientSummary(Doctor doctor, Education education) {
return DoctorPatientSummary.builder()
.doctorId(doctor.getId())
.doctorName(doctor.getName())
.patientCount(doctor.getPatientList().size())
.patientIds(doctor.getPatientList()
.stream()
.map(Patient::getId)
.collect(Collectors.toList()))
.institute(education.getInstitute())
.specialization(education.getDegreeName())
.build();
}
}
使用
DoctorPatientSummary summary = doctorMapper.toDoctorPatientSummary(dotor, education);
b、使用抽象类
@Mapper
public abstract class DoctorCustomMapper {
public DoctorPatientSummary toDoctorPatientSummary(Doctor doctor, Education education) {
return DoctorPatientSummary.builder()
.doctorId(doctor.getId())
.doctorName(doctor.getName())
.patientCount(doctor.getPatientList().size())
.patientIds(doctor.getPatientList()
.stream()
.map(Patient::getId)
.collect(Collectors.toList()))
.institute(education.getInstitute())
.specialization(education.getDegreeName())
.build();
}
}
c、@BeforeMapping 和 @AfterMapping
@Mapper(uses = {PatientMapper.class}, componentModel = "spring")
public abstract class DoctorCustomMapper {
@BeforeMapping
protected void validate(Doctor doctor) {
if(doctor.getPatientList() == null){
doctor.setPatientList(new ArrayList<>());
}
}
@AfterMapping
protected void updateResult(@MappingTarget DoctorDto doctorDto) {
doctorDto.setName(doctorDto.getName().toUpperCase());
doctorDto.setDegree(doctorDto.getDegree().toUpperCase());
doctorDto.setSpecialization(doctorDto.getSpecialization().toUpperCase());
}
@Mapping(source = "doctor.patientList", target = "patientDtoList")
@Mapping(source = "doctor.specialty", target = "specialization")
public abstract DoctorDto toDoctorDto(Doctor doctor);
}
实现代码:
@Component
public class DoctorCustomMapperImpl extends DoctorCustomMapper {
@Autowired
private PatientMapper patientMapper;
@Override
public DoctorDto toDoctorDto(Doctor doctor) {
validate(doctor);
if (doctor == null) {
return null;
}
DoctorDto doctorDto = new DoctorDto();
doctorDto.setPatientDtoList(patientListToPatientDtoList(doctor
.getPatientList()));
doctorDto.setSpecialization(doctor.getSpecialty());
doctorDto.setId(doctor.getId());
doctorDto.setName(doctor.getName());
updateResult(doctorDto);
return doctorDto;
}
}
5、映射异常处理
public class Validator {
public int validateId(int id) throws ValidationException {
if(id == -1){
throw new ValidationException("Invalid value in ID");
}
return id;
}
}
@Mapper(uses = {PatientMapper.class, Validator.class}, componentModel = "spring")
public interface DoctorMapper {
@Mapping(source = "doctor.patientList", target = "patientDtoList")
@Mapping(source = "doctor.specialty", target = "specialization")
DoctorDto toDto(Doctor doctor) throws ValidationException;
}
实现代码:
@Component
public class DoctorMapperImpl implements DoctorMapper {
@Autowired
private PatientMapper patientMapper;
@Autowired
private Validator validator;
@Override
public DoctorDto toDto(Doctor doctor) throws ValidationException {
if (doctor == null) {
return null;
}
DoctorDto doctorDto = new DoctorDto();
doctorDto.setPatientDtoList(patientListToPatientDtoList(doctor
.getPatientList()));
doctorDto.setSpecialization(doctor.getSpecialty());
doctorDto.setId(validator.validateId(doctor.getId()));
doctorDto.setName(doctor.getName());
doctorDto.setExternalId(doctor.getExternalId());
doctorDto.setAvailability(doctor.getAvailability());
return doctorDto;
}
}
6、映射配置
a、继承配置
@Mapper(uses = {PatientMapper.class, Validator.class}, componentModel = "spring")
public interface DoctorMapper {
@Mapping(source = "doctorDto.specialization", target = "specialty")
@Mapping(source = "doctorDto.patientDtoList", target = "patientList")
Doctor toModel(DoctorDto doctorDto);
// @Mapping(source = "doctorDto.specialization", target = "specialty")
// @Mapping(source = "doctorDto.patientDtoList", target = "patientList")
// 继承上面配置,避免在写一遍
@InheritConfiguration
void updateModel(DoctorDto doctorDto, @MappingTarget Doctor doctor);
}
b、继承逆向配置
@Mapper(componentModel = "spring")
public interface PatientMapper {
@Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy")
Patient toModel(PatientDto patientDto);
// 继承逆向配置,避免反着写一遍
@InheritInverseConfiguration
PatientDto toDto(Patient patient);
}
以上是全文,谢谢浏览!!!
作者:GuoYaxiang
原文链接:https://juejin.cn/post/6956190395319451679