介绍
大多数时候,终端用户或服务不需要访问模型中的全部数据,而只需要访问某些特定的部分。
数据传输对象(Data Transfer Objects, DTO)经常被用于这些应用中。DTO只是持有另一个对象中被请求的信息的对象。通常情况下,这些信息是有限的一部分。例如,在持久化层定义的实体和发往客户端的DTO之间经常会出现相互之间的转换。由于DTO是原始对象的反映,因此这些类之间的映射器在转换过程中扮演着关键角色。
这就是MapStruct解决的问题:手动创建bean映射器非常耗时。 但是该库可以自动生成Bean映射器类。
基本映射
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
}
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
DoctorDto toDto(Doctor doctor);
}
不同属性名称映射
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);
}
多个源类映射
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);
}
子对象映射
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);
}
更新现有实例
希望用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);
}
数据类型映射
@Mapper
public interface PatientMapper {
@Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy")
Patient toModel(PatientDto patientDto);
}
// 数字格式转换示例
@Mapping(source = "price", target = "price", numberFormat = "$#.00")
枚举映射
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);
}
依赖注入
@Mapper(componentModel = "spring")
public interface DoctorMapper {}
在@Mapper注解中添加(componentModel = "spring"),是为了告诉MapStruct,在生成映射器实现类时,我们希望它能支持通过Spring的依赖注入来创建。现在,就不需要在接口中添加 INSTANCE 字段了。
这次生成的 DoctorMapperImpl 会带有 @Component 注解:
@Component
public class DoctorMapperImpl implements DoctorMapper {}
只要被标记为@Component,Spring就可以把它作为一个bean来处理,你就可以在其它类(如控制器)中通过@Autowire注解来使用它:
@Controller
public class DoctorController() {
@Autowired
private DoctorMapper doctorMapper;
}
添加默认值
@Mapping 注解有两个很实用的标志就是常量 constant 和默认值 defaultValue 。无论source如何取值,都将始终使用常量值; 如果source取值为null,则会使用默认值。
修改一下 DoctorMapper ,添加一个 constant 和一个 defaultValue :
@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);
}
添加表达式
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
}
@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);
}
可以看到,这里将 externalId的值设置为 java(UUID.randomUUID().toString()) ,如果源对象中没有 availability 属性,则会把目标对象中的 availability 设置为一个新的 LocalDateTime对象。
由于表达式只是字符串,我们必须在表达式中指定使用的类。但是这里的表达式并不是最终执行的代码,只是一个字母的文本值。因此,我们要在 @Mapper 中添加 imports = {LocalDateTime.class, UUID.class} 。
@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);
}
网友评论