目录
mapStruct学习资料https://juejin.cn/post/6956190395319451679#heading-5
MapStruct概述
数据传输对象(Data Transfer Objects, DTO)经常被用于这些应用中。DTO只是持有另一个对象中被请求的信息的对象。通常情况下,这些信息是有限的一部分。例如,在持久化层定义的实体和发往客户端的DTO之间经常会出现相互之间的转换。由于DTO是原始对象的反映,因此这些类之间的映射器在转换过程中扮演着关键角色。
MapStruct解决的问题:
手动创建bean映射器非常耗时。 但是该库可以自动生成Bean映射器类
MapStruct是一个开源的基于Java的代码生成器,用于创建实现Java Bean之间转换的扩展映射器。使用MapStruct,我们只需要创建接口,而该库会通过注解在编译过程中自动创建具体的映射实现,大大减少了通常需要手工编写的样板代码的数量
MapStruct 依赖
<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.2.0.Final</version>
</dependency>
MapStruct及其处理器的最新稳定版本都可以从Maven中央仓库中获得
映射
基本映射
先从一些基本的映射开始。我们会创建一个Doctor对象和一个DoctorDto。为了方便起见,它们的属性字段都使用相同的名称:
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); //1、调用该方法返回一个DoctorDto的对象 DoctorDto toDto(Doctor doctor); //2、仅转换,不返回相关对象 (需要添加@MappingTarget注解表示转换到哪一个对象中去) void toDto(Doctor doctor, @MappingTarget DoctorDto doctorDto); }
1、这段代码中创建了一个
DoctorMapper
类型的实例INSTANCE
,在生成对应的实现代码后,这就是我们调用的“入口”。2、在接口中定义了
toDto()
方法,该方法接收一个Doctor
实例为参数,并返回一个DoctorDto
实例。这足以让MapStruct知道我们想把一个Doctor
实例映射到一个DoctorDto
实例。3、当我们构建/编译应用程序时,MapStruct注解处理器插件会识别出DoctorMapper接口并为其生成一个实现类。
//mapStruct自动实现的类 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(); } }
DoctorMapperImpl
类中包含一个toDto()
方法,将我们的Doctor
属性值映射到DoctorDto
的属性字段中。如果要将Doctor
实例映射到一个DoctorDto
实例,可以这样写:1、DoctorDto doctorDto = DoctorMapper.INSTANCE.toDto(doctor); 2、DoctorDto doctorDto = new DoctorDto; DoctorMapper.INSTANCE.toDto(doctor, doctorDto);
注意:你可能也注意到了上面实现代码中的
DoctorDtoBuilder
。因为builder代码往往比较长,为了简洁起见,这里省略了builder模式的实现代码。如果你的类中包含Builder,MapStruct会尝试使用它来创建实例;如果没有的话,MapStruct将通过new
关键字进行实例化。
不同字段间映射
不同属性名称
更新Doctor
类,添加一个属性specialty
public class Doctor {
private int id;
private String name;
private String specialty;
// getters and setters or builder
}
DoctorDto
类中添加一个specialization
属性
public class DoctorDto {
private int id;
private String name;
private String specialization;
// getters and setters or builder
}
我们需要让
DoctorMapper
知道这里的不一致。我们可以使用@Mapping
注解,并设置其内部的source
和target
标记分别指向不一致的两个字段。@Mapper public interface DoctorMapper { DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class); //doctor.specialty中的doctor可以不加 @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(); } }
多个源类
有时,单个类不足以构建DTO,我们可能希望将多个类中的值聚合为一个DTO,供终端用户使用。这也可以通过在@Mapping
注解中设置适当的标志来完成
新建另一个对象 Education
public class Education {
private String degreeName;
private String institute;
private Integer yearOfPassing;
// getters and setters or builder
}
向 DoctorDto
中添加一个新的字段:
public class DoctorDto {
private int id;
private String name;
private String degree;
private String specialization;
// getters and setters or builder
}
将 DoctorMapper
接口更新为如下代码
@Mapper public interface DoctorMapper { DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class); //需要指明是哪一个对象的哪一个属性映射到DoctorDto中的哪一个属性 @Mapping(source = "doctor.specialty", target = "specialization") @Mapping(source = "education.degreeName", target = "degree") DoctorDto toDto(Doctor doctor, Education education); }
我们添加了另一个
@Mapping
注解,并将其source
设置为Education
类的degreeName
,将target
设置为DoctorDto
类的degree
字段。如果
Education
类和Doctor
类包含同名的字段,我们必须让映射器知道使用哪一个,否则它会抛出一个异常。举例来说,如果两个模型都包含一个id
字段,我们就要选择将哪个类中的id
映射到DTO属性中。
子对象映射
多数情况下,POJO中不会只包含基本数据类型,其中往往会包含其它类。比如说,一个Doctor
类中会有多个患者类
public class Patient {
private int id;
private String name;
// getters and setters or builder
}
在Doctor中添加一个患者列表List
public class Doctor {
private int id;
private String name;
private String specialty;
private List<Patient> patientList;
// getters and setters or builder
}
因为Patient
需要转换,为其创建一个对应的DTO
public class PatientDto {
private int id;
private String name;
// getters and setters or builder
}
最后,在 DoctorDto
中新增一个存储 PatientDto
的列表
public class DoctorDto {
private int id;
private String name;
private String degree;
private String specialization;
private List<PatientDto> patientDtoList;
// getters and setters or builder
}
在修改
DoctorMapper
之前,我们先创建一个支持Patient
和PatientDto
转换的映射器接口:@Mapper public interface PatientMapper { PatientMapper INSTANCE = Mappers.getMapper(PatientMapper.class); PatientDto toDto(Patient patient); }
这是一个基本映射器,只会处理几个基本数据类型。
然后,我们再来修改
DoctorMapper
处理一下患者列表:@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; } }
显然,除了
toDto()
映射方法外,最终实现中还添加了一个新的映射方法——patientListToPatientDtoList()
。这个方法是在没有显式定义的情况下添加的,只是因为我们把PatientMapper
添加到了DoctorMapper
中。该方法会遍历一个
Patient
列表,将每个元素转换为PatientDto
,并将转换后的对象添加到DoctorDto
对象内中的列表中。
忽略某一个属性转换
在@Mapping()中添加 ignore = true,表示忽略目标对象中的该属性的转换
@Mapping(target = "type", ignore = true)
更新现有实例 ( @MappingTarget )
有时,我们希望用DTO的最新值更新一个模型中的属性,对目标对象(我们的例子中是DoctorDto
)使用@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());
}
}
值得注意的是,由于患者列表是该模型中的子实体,因此患者列表也会进行更新。
数据类型转换
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官方文档。
在生成映射器代码的过程中,如果源字段和目标字段之间属于上述任何一种情况,则MapStrcut会自行处理类型转换。
日期格式转换
PatientDto
,新增一个 dateofBirth
字段
public class PatientDto {
private int id;
private String name;
private LocalDate dateOfBirth;
// getters and setters or builder
}
加入 Patient
对象中有一个String
类型的 dateOfBirth
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()));
}
数字格式转换
在进行日期转换的时候,可以通过dateFormat
标志指定日期的格式。
对于数字的转换,也可以使用numberFormat
指定显示格式:
// 数字格式转换示例
@Mapping(source = "price", target = "price", numberFormat = "$#.00")
枚举映射
枚举映射的工作方式与字段映射相同。MapStruct会对具有相同名称的枚举进行映射,这一点没有问题。但是,对于具有不同名称的枚举项,我们需要使用
@ValueMapping
注解。同样,这与普通类型的@Mapping
注解也相似。
先创建两个枚举。第一个是 PaymentType
:
public enum PaymentType {
CASH,
CHEQUE,
CARD_VISA,
CARD_MASTER,
CARD_CREDIT
}
比如说,这是一个应用内可用的支付方式,现在我们要根据这些选项创建一个更一般、有限的识图:
public enum PaymentTypeView {
CASH,
CHEQUE,
CARD
}
我们创建这两个enum
之间的映射器接口:
@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);
}
这个例子中,我们设置了一般性的CARD
值,和更具体的 CARD_VISA
, CARD_MASTER
和 CARD_CREDIT
。两个枚举间的枚举项数量不匹配—— PaymentType
有5个值,而 PaymentTypeView
只有3个。
为了在这些枚举项之间建立桥梁,我们可以使用@ValueMappings
注解,该注解中可以包含多个@ValueMapping
注解。这里,我们将source
设置为三个具体枚举项之一,并将target
设置为CARD
。
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;
}
}
CASH
和CHEQUE
默认转换为对应值,特殊的 CARD
值通过switch
循环处理。
但是,如果你要将很多值转换为一个更一般的值,这种方式就有些不切实际了。其实我们不必手动分配每一个值,只需要让MapStruct将所有剩余的可用枚举项(在目标枚举中找不到相同名称的枚举项),直接转换为对应的另一个枚举项。
直接将所有未通过
@ValueMapping
注解做显式映射的值都转换为target
值。@ValueMapping(source = MappingConstants.ANY_UNMAPPED, target = "CARD") PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType);
集合映射
使用MapStruct处理集合映射的方式与处理简单类型相同。
我们创建一个简单的接口或抽象类并声明映射方法。 MapStruct将根据我们的声明自动生成映射代码。 通常,生成的代码会遍历源集合,将每个元素转换为目标类型,并将每个转换后元素添加到目标集合中。
List映射
除了对象与对象之间的映射,也存在对象列表与对象列表之间的映射,列表之间的映射需要我们在实现对象映射的基础上,直接处理列表即可。
PotentialKolAccountDTO toDTO(AurrPotentialKolAccount aurrPotentialKolAccount);
List<PotentialKolAccountDTO> toDTOList(List<AurrPotentialKolAccount> list);
Set和Map映射
DoctorMapper:(首先设置Doctor和DoctorDto之间的映射关系)
@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;
}
}
进阶操作
依赖注入 (通过spring实现)
到目前为止,我们一直在通过getMapper()
方法访问生成的映射器:
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
但是,如果你使用的是Spring,只需要简单修改映射器配置,就可以像常规依赖项一样注入映射器。
修改 DoctorMapper
以支持Spring框架:
@Mapper(componentModel = "spring")
public interface DoctorMapper {}
@Component
public class DoctorMapperImpl implements DoctorMapper {}
只要被标记为@Component
,Spring就可以把它作为一个bean来处理,你就可以在其它类(如控制器)中通过@Autowire
注解来使用它:
@Controller
public class DoctorController() {
@Autowired
private DoctorMapper doctorMapper;
}
!!!!!!!!而不需要每次都通过 DoctorMapper.INSTANCE.toDto(doctor) 调用
添加默认值
@Mapping
注解有两个很实用的标志就是常量constant
和默认值defaultValue
。无论source
如何取值,都将始终使用常量值; 如果source
取值为null
,则会使用默认值。
@Mapper(uses = {PatientMapper.class})
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);
}
如果specialty
不可用,我们会替换为"Information Not Available"
字符串,此外,我们将id
硬编码为-1
。如果 doctor.getSpecialty()
返回值为null
,则将specialization
设置为我们的默认信息。无论任何情况,都会对 id
赋值,因为这是一个constant
。
添加表达式
MapStruct甚至允许在@Mapping
注解中输入Java表达式。你可以设置 defaultExpression
( source
取值为 null
时生效),或者一个expression
(类似常量,永久生效)。
在 Doctor
和 DoctorDto
两个类中都加了两个新属性,一个是 String
类型的 externalId
,另一个是LocalDateTime
类型的 appointment
,两个类大致如下:
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
}
修改 DoctorMapper:
@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);
}
添加自定义方法
我们还可以向接口中添加自定义的default
方法,也可以通过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
}
我们在 DoctorMapper
中添加一个default
方法,该方法会将 Doctor
和 Education
对象转换为一个 DoctorPatientSummary
:
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
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();
}
}
在MapStruct生成映射器实现类之后,你就可以使用这个实现方法,就像访问任何其它映射器方法一样
DoctorPatientSummary summary = DoctorMapper.INSTANCE.toDoctorPatientSummary(dotor, education);
@BeforeMapping 和 @AfterMapping (自己生成实现)
为了进一步控制和定制化,我们可以定义 @BeforeMapping
和 @AfterMapping
方法。显然,这两个方法是在每次映射之前和之后执行的。也就是说,在最终的实现代码中,会在两个对象真正映射之前和之后添加并执行这两个方法。
在 DoctorCustomMapper
中添加两个方法:
@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;
}
}
validate()
方法会在 DoctorDto
对象实例化之前执行,而updateResult()
方法会在映射结束之后执行
例子
如果存在多个不同的afterMappering,mapStruct会自动实现映射
@Mapper
public interface PlanConvert {
PlanConvert INSTANCE = Mappers.getMapper(PlanConvert.class);
@Mapping(target = "topicUrl", ignore = true)
@Mapping(target = "bgmUrl", ignore = true)
@Mapping(target = "startTime", source = "startTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(target = "endTime", source = "endTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(target = "planId", source = "id")
PlanBaseInfoDTO toDTO(PlanEntity planEntity);
@AfterMapping //自动映射上一个相同的形参,并在上一个的实现方法中最后调用该方法
default void afterToEntity(PlanEntity planEntity, @MappingTarget PlanBaseInfoDTO planBaseInfoDTO) {
planBaseInfoDTO.setBgmUrl(CollUtil.join(planEntity.getBgmUrl(), ","));
planBaseInfoDTO.setTopicUrl(CollUtil.join(planEntity.getTopicUrl(), ","));
planBaseInfoDTO.setType(planEntity.getType() == null ? PlanTypeEnum.OUTSIDE.getCode() : planEntity.getType().getCode());
}
@Mapping(target = "startTime", source = "startTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(target = "endTime", source = "endTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(target = "type", ignore = true)
PlanPageInfoDTO toDTO(PlanBO planBO);
@AfterMapping //如上
default void afterToDTO(PlanBO planBO, @MappingTarget PlanPageInfoDTO planPageInfoDTO) {
PlanTypeEnum planTypeEnum = PlanTypeEnum.of(planBO.getType());
planPageInfoDTO.setType(planTypeEnum != null ? planTypeEnum.getCode() : null);
}
}
自动实现类:
public PlanBaseInfoDTO toDTO(PlanEntity planEntity) {
if ( planEntity == null ) {
return null;
}
PlanBaseInfoDTO planBaseInfoDTO = new PlanBaseInfoDTO();
if ( planEntity.getStartTime() != null ) {
planBaseInfoDTO.setStartTime( DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss" ).format( planEntity.getStartTime() ) );
}
planBaseInfoDTO.setPlanId( planEntity.getId() );
if ( planEntity.getEndTime() != null ) {
planBaseInfoDTO.setEndTime( DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss" ).format( planEntity.getEndTime() ) );
}
if ( planEntity.getType() != null ) {
planBaseInfoDTO.setType( planEntity.getType().name() );
}
planBaseInfoDTO.setSongId( planEntity.getSongId() );
planBaseInfoDTO.setScheme( planEntity.getScheme() );
planBaseInfoDTO.setOther( planEntity.getOther() );
planBaseInfoDTO.setCreator( planEntity.getCreator() );
planBaseInfoDTO.setModifier( planEntity.getModifier() );
if ( planEntity.getCreateTime() != null ) {
planBaseInfoDTO.setCreateTime( DateTimeFormatter.ISO_LOCAL_DATE_TIME.format( planEntity.getCreateTime() ) );
}
if ( planEntity.getUpdateTime() != null ) {
planBaseInfoDTO.setUpdateTime( DateTimeFormatter.ISO_LOCAL_DATE_TIME.format( planEntity.getUpdateTime() ) );
}
List<String> list = planEntity.getViewer();
if ( list != null ) {
planBaseInfoDTO.setViewer( new ArrayList<String>( list ) );
}
else {
planBaseInfoDTO.setViewer( null );
}
planBaseInfoDTO.setPromoType( planEntity.getPromoType() );
planBaseInfoDTO.setProjectId( planEntity.getProjectId() );
//!!!!!最后调用该方法
afterToEntity( planEntity, planBaseInfoDTO );
return planBaseInfoDTO;
}
继承配置
两个映射方法使用了相同的注解配置, source
和 target
都是相同的。其实我们可以使用@InheritConfiguration
注释,从而避免这两个映射器方法的重复配置。
如果对一个方法添加 @InheritConfiguration
注解,MapStruct会检索其它的已配置方法,寻找可用于当前方法的注解配置。一般来说,这个注解都用于mapping
方法后面的update
方法
@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);
@InheritConfiguration
void updateModel(DoctorDto doctorDto, @MappingTarget Doctor doctor);
}
继承逆向配置
编写映射函数将Model 转为 DTO,以及将 DTO 转为 Model
两个方法的配置不会是完全相同的,实际上,它们应该是相反的。将Model 转为 DTO,以及将 DTO 转为 Model——映射前后的字段相同,但是源属性字段与目标属性字段是相反的。
可以在第二个方法上使用@InheritInverseConfiguration
注解,避免写两遍映射配置
@Mapper(componentModel = "spring")
public interface PatientMapper {
@Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy")
Patient toModel(PatientDto patientDto);
@InheritInverseConfiguration
PatientDto toDto(Patient patient);
}