隨著微服務和分布式應用的廣泛采用,出于服務的獨立性和數據的安全性方面的考慮膳汪,每個服務都會按照自己的需要定義業(yè)務數據對象唯蝶,這樣當服務相互調用時就要經常進行數據對象之間的映射。目前遗嗽,有很多實現數據對象映射的庫粘我,本文介紹一種高性能的映射庫
MapStruct
。
MapStruct簡介
MapStruct
是在編譯時根據定義(接口)生成映射類(實現)痹换,自動生成需要手工編寫數據映射代碼征字,通過直接調用復制數據,不需要通過反射娇豫,因此速度非吵捉快。
本文將介紹一些MapStruct
的高級功能冯痢,包括:
- 更新已經存在的實例
- 添加自定義映射方法
- 創(chuàng)建自定義映射器
- 異常處理
- 復用映射關系(繼承氮昧,反向繼承)
更新已經存在的實例
有時候,我們想用源數據對象更新已經存在的數據對象浦楣,而不是新建對象袖肥,這時需要用@MappingTarget
指定目標對象。
在DoctorMapper
中添加新的映射方法振劳。
@Mapper(componentModel = "spring", imports = { LocalDateTime.class, UUID.class })
public interface DoctorMapper {
/*添加了新的映射方法椎组,用@MappingTarget更新目標對象*/
@Mapping(source = "doctorDto.patientDtoList", target = "patientList")
@Mapping(source = "doctorDto.specialization", target = "specialty")
void updateModel(DoctorDto doctorDto, @MappingTarget Doctor doctor); // @MappingTarget指定了要更新的數據對象
}
生成的代碼將目標對象作為參數傳入映射方法。
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.setAvailability(doctorDto.getAvailability());
doctor.setExternalId(doctorDto.getExternalId());
doctor.setId(doctorDto.getId());
doctor.setName(doctorDto.getName());
}
注意:patientList
也進行了更新历恐。
添加自定義映射方法
之前我們是添加接口方法讓MapStruct
自動生成實現代碼寸癌,也可以用default
關鍵字在接口中添加手工編寫的方法专筷。
@Data
public class DoctorPatientSummary {
private int doctorId;
private int patientCount;
private String doctorName;
private String specialization;
private String institute;
private List<Integer> patientIds;
}
在DoctorMapper
中添加自定義的映射方法。
@Mapper(componentModel = "spring", imports = { LocalDateTime.class, UUID.class })
public interface DoctorMapper {
/*省略了之前定義的映射函數*/
/*手工指定映射關系*/
default DoctorPatientSummary toDoctorPatientSummary(Doctor doctor, Education education) {
DoctorPatientSummary summary = new DoctorPatientSummary();
summary.setDoctorId(doctor.getId());
summary.setDoctorName(doctor.getName());
summary.setPatientCount(doctor.getPatientList().size());
summary.setPatientIds(doctor.getPatientList().stream().map(Patient::getId).collect(Collectors.toList()));
summary.setInstitute(education.getInstitute());
summary.setSpecialization(education.getDegreeName());
return summary;
}
}
MapStruct
自動生成的實現類實現了映射接口蒸苇,因此可以在實現類的實例中直接調用該方法磷蛹。
創(chuàng)建自定義映射器
除了通過接口定義映射,還可以用抽象類(abstract
)實現填渠,MapStruct
也會自動生成實現類弦聂。
@Mapper(componentModel = "spring")
public abstract class DoctorCustomMapper {
public DoctorPatientSummary toDoctorPatientSummary(Doctor doctor, Education education) {
DoctorPatientSummary summary = new DoctorPatientSummary();
summary.setDoctorId(doctor.getId());
summary.setDoctorName(doctor.getName());
summary.setPatientCount(doctor.getPatientList().size());
summary.setPatientIds(doctor.getPatientList().stream().map(Patient::getId).collect(Collectors.toList()));
summary.setInstitute(education.getInstitute());
summary.setSpecialization(education.getDegreeName());
return summary;
}
}
自動生成的實現類鸟辅。
@Component
public class DoctorCustomMapperImpl extends DoctorCustomMapper {}
通過抽象類實現的映射類還可以用@BeforeMapping
和@AfterMapping
添加映射前和映射后執(zhí)行的方法氛什。
@Mapper(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());
}
/*抽象方法,由MapStruct生成實現代碼*/
@Mapping(source = "doctor.patientList", target = "patientDtoList")
@Mapping(source = "doctor.specialty", target = "specialization")
public abstract DoctorDto toDoctorDto(Doctor doctor);
/*省略其它定義*/
}
@Component
public class DoctorCustomMapperImpl extends DoctorCustomMapper {
public DoctorDto toDoctorDto(Doctor doctor) {
validate(doctor); // 調用了@BeforeMapping注解的方法
if (doctor == null)
return null;
DoctorDto doctorDto = new DoctorDto();
doctorDto.setPatientDtoList(patientListToPatientDtoList(doctor.getPatientList()));
doctorDto.setSpecialization(doctor.getSpecialty());
doctorDto.setAvailability(doctor.getAvailability());
doctorDto.setExternalId(doctor.getExternalId());
doctorDto.setId(doctor.getId());
doctorDto.setName(doctor.getName());
updateResult(doctorDto);
return doctorDto; // 調用了@AfterMapping注解的方法
}
/*省略了生成的其它代碼*/
}
異常處理
數據對象轉換過程中難免會發(fā)生異常匪凉,MapStruct
也支持進行異常處理枪眉。
假設我們有一個進行數據檢查的類Validator
,檢查失敗會拋出自定義異常ValidationException
再层。
public class Validator {
public int validateId(int id) throws ValidationException {
if (id == -1) {
throw new ValidationException("Invalid value in ID");
}
return id;
}
}
在定義的映射方法上添加拋出異常贸铜。
@Mapper(componentModel = "spring", uses = { PatientMapper.class, Validator.class }, imports = { LocalDateTime.class, UUID.class })
public interface DoctorMapper {
/*省略自動生成的內容*/
DoctorDto toDto(Doctor doctor, Education education) throws ValidationException; // 方法拋出異常
/*省略自動生成的內容*/
}
@Component
public class DoctorMapperImpl implements DoctorMapper {
@Autowired
private PatientMapper patientMapper;
@Autowired
private Validator validator;
public DoctorDto toDto(Doctor doctor, Education education) throws ValidationException {
if (doctor == null && education == null)
return null;
DoctorDto doctorDto = new DoctorDto();
if (doctor != null) {
if (doctor.getAvailability() != null) {
doctorDto.setAvailability(doctor.getAvailability());
} else {
doctorDto.setAvailability(LocalDateTime.now());
}
if (doctor.getSpecialty() != null) {
doctorDto.setSpecialization(doctor.getSpecialty());
} else {
doctorDto.setSpecialization("沒有指定");
}
doctorDto.setPatientDtoList(patientListToPatientDtoList(doctor.getPatientList()));
doctorDto.setId(this.validator.validateId(doctor.getId())); // 這里會檢查id的合法性,不合法拋異常
doctorDto.setName(doctor.getName());
}
if (education != null)
doctorDto.setDegree(education.getDegreeName());
doctorDto.setExternalId(UUID.randomUUID().toString());
return doctorDto;
}
復用映射關系
有時候多個映射方法使用相同的映射關系聂受,例如:生成新數據對象或更新現有數據對象的方法蒿秦。
@Mapper(uses = { PatientMapper.class }, componentModel = "spring")
public interface DoctorInheritMapper {
@Mapping(source = "doctorDto.specialization", target = "specialty")
@Mapping(source = "doctorDto.patientDtoList", target = "patientList")
Doctor toModel(DoctorDto doctorDto);
@InheritConfiguration
void updateModel(DoctorDto doctorDto, @MappingTarget Doctor doctor);
}
@Component
public class DoctorInheritMapperImpl implements DoctorInheritMapper {
public Doctor toModel(DoctorDto doctorDto) {
if (doctorDto == null)
return null;
Doctor doctor = new Doctor();
doctor.setSpecialty(doctorDto.getSpecialization());
doctor.setPatientList(patientDtoListToPatientList(doctorDto.getPatientDtoList()));
doctor.setAvailability(doctorDto.getAvailability());
doctor.setExternalId(doctorDto.getExternalId());
doctor.setId(doctorDto.getId());
doctor.setName(doctorDto.getName());
return doctor;
}
/*這里按照繼承的映射關系生成了代碼*/
public void updateModel(DoctorDto doctorDto, Doctor doctor) {
if (doctorDto == null)
return;
doctor.setSpecialty(doctorDto.getSpecialization());
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.setAvailability(doctorDto.getAvailability());
doctor.setExternalId(doctorDto.getExternalId());
doctor.setId(doctorDto.getId());
doctor.setName(doctorDto.getName());
}
/*省略生成的其他代碼*/
}
還有一種用到繼承的情況是需要按照一套規(guī)則進行雙向的數據對象轉換。
@Mapper(componentModel = "spring")
public interface PatientMapper {
@Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy")
PatientDto toDto(Patient patient);
@InheritInverseConfiguration
Patient toModel(PatientDto patientDto);
}
@Component
public class PatientMapperImpl implements PatientMapper {
public PatientDto toDto(Patient patient) {
if (patient == null)
return null;
PatientDto patientDto = new PatientDto();
if (patient.getDateOfBirth() != null)
patientDto.setDateOfBirth(LocalDate.parse(patient.getDateOfBirth(), DateTimeFormatter.ofPattern("dd/MMM/yyyy")));
patientDto.setId(patient.getId());
patientDto.setName(patient.getName());
return patientDto;
}
/*根據繼承的反向規(guī)則生成的代碼*/
public Patient toModel(PatientDto patientDto) {
if (patientDto == null)
return null;
Patient patient = new Patient();
if (patientDto.getDateOfBirth() != null)
patient.setDateOfBirth(DateTimeFormatter.ofPattern("dd/MMM/yyyy").format(patientDto.getDateOfBirth()));
patient.setId(patientDto.getId());
patient.setName(patientDto.getName());
return patient;
}
}
總結
MapStruct
是一個非常強大的數據對象映射庫蛋济,除了減少手工代碼的編寫量棍鳖,還為我們提供了一個進行數據對象映射的基礎框架。MapStruct
還有很多用法碗旅,需要不斷研究和實踐渡处。