MapStruct 快速指南
MapStruct
MapStruct
是一个代码生成器,它基于约定优于配置方法极大地简化了 Java bean
类型之间映射的实现。自动生成的映射转换代码只使用简单的方法调用,因此速度快、类型安全而且易于理解阅读,源码仓库 Github
地址 MapStruct。总的来说,有如下三个特点:
- 基于注解
- 在编译期自动生成映射转换代码
- 类型安全、高性能、无依赖性
1.maven 配置
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
将 annotationProcessorPaths 部分添加到 maven-compiler-plugin 插件的配置部分。mapstruct-processor 用于在构建期间生成映射器实现
要启用 Lombok 支持,我们需要在注解处理器路径中添加依赖项。从 Lombok 版本 1.18.16 开始,我们还必须添加对 lombok-mapstruct-binding 的依赖。现在我们在 Maven 编译器插件中有 mapstruct-processor 和 Lombok
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<!-- Lombok 在编译时会通过这个插件生成代码 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</path>
<!-- MapStruct 在编译时会通过这个插件生成代码 -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
2.基本映射
2.1 bean
@Data
public class SimpleDestination {
private String name;
private String description;
}
@Data
public class SimpleSource {
private String name;
private String description;
}
2.2 Interface
@Mapper
public interface SimpleSourceDestinationMapper {
SimpleDestination sourceToDestination(SimpleSource source);
SimpleSource destinationToSource(SimpleDestination destination);
}
我们没有为SimpleSourceDestinationMapper创建实现类——因为MapStruct为我们创建了它
可以通过mvn clean package -Dmaven.test.skip=true
命令来触发MapStruct处理。
这将在/target/generated-sources/annotations/下生成实现类。
下面是MapStruct为我们自动创建的类:
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-09-27T00:18:32+0800",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_281 (Oracle Corporation)"
)
@Component
public class SimpleSourceDestinationMapperImpl implements SimpleSourceDestinationMapper {
@Override
public SimpleDestination sourceToDestination(SimpleSource source) {
if ( source == null ) {
return null;
}
SimpleDestination simpleDestination = new SimpleDestination();
simpleDestination.setName( source.getName() );
simpleDestination.setDescription( source.getDescription() );
return simpleDestination;
}
@Override
public SimpleSource destinationToSource(SimpleDestination destination) {
if ( destination == null ) {
return null;
}
SimpleSource simpleSource = new SimpleSource();
simpleSource.setName( destination.getName() );
simpleSource.setDescription( destination.getDescription() );
return simpleSource;
}
}
2.3 Test Case
private SimpleSourceDestinationMapper mapper = Mappers.getMapper(SimpleSourceDestinationMapper.class);
@Test
public void givenSourceToDestination_whenMaps_thenCorrect() {
SimpleSource simpleSource = new SimpleSource();
simpleSource.setName("SourceName");
simpleSource.setDescription("SourceDescription");
SimpleDestination destination = mapper.sourceToDestination(simpleSource);
assertEquals(simpleSource.getName(), destination.getName());
assertEquals(simpleSource.getDescription(),
destination.getDescription());
}
@Test
public void givenDestinationToSource_whenMaps_thenCorrect() {
SimpleDestination destination = new SimpleDestination();
destination.setName("DestinationName");
destination.setDescription("DestinationDescription");
SimpleSource source = mapper.destinationToSource(destination);
assertEquals(destination.getName(), source.getName());
assertEquals(destination.getDescription(),
source.getDescription());
}
3.映射具有不同字段名称的字段
3.1 bean
@Data
public class Employee {
private int id;
private String name;
}
@Data
public class EmployeeDTO {
private int employeeId;
private String employeeName;
}
3.2 Interface
当映射不同的字段名称时,我们需要将其源字段配置为其目标字段,为此,我们需要为每个字段添加@Mapping 注解。
在 MapStruct 中,我们还可以使用点表示法来定义 bean 的成员:
@Mapper
public interface EmployeeMapper {
@Mapping(target="employeeId", source="entity.id")
@Mapping(target="employeeName", source="entity.name")
EmployeeDTO employeeToEmployeeDTO(Employee entity);
@Mapping(target="id", source="dto.employeeId")
@Mapping(target="name", source="dto.employeeName")
Employee employeeDTOtoEmployee(EmployeeDTO dto);
}
3.3 Test Case
@Test
public void givenEmployeeDTOwithDiffNametoEmployee_whenMaps_thenCorrect() {
EmployeeDTO dto = new EmployeeDTO();
dto.setEmployeeId(1);
dto.setEmployeeName("John");
Employee entity = mapper.employeeDTOtoEmployee(dto);
assertEquals(dto.getEmployeeId(), entity.getId());
assertEquals(dto.getEmployeeName(), entity.getName());
}
4.子bean映射bean
4.1 bean
@Data
public class EmployeeDTO {
private int employeeId;
private String employeeName;
private DivisionDTO division;
}
@Data
public class Employee {
private int id;
private String name;
private Division division;
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Division {
private Integer id;
private String name;
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class DivisionDTO {
private Integer id;
private String name;
}
4.2 Interface
这里我们需要添加一个方法,将 Division 转换为 DivisionDTO,反之亦然;如果 MapStruct 检测到需要转换的对象类型且转换的方法存在于同一个类中,它会自动使用
@Mapper
public interface EmployeeMapper {
@Mapping(target = "employeeId", source = "entity.id")
@Mapping(target = "employeeName", source = "entity.name")
EmployeeDTO employeeToEmployeeDTO(Employee entity);
@Mapping(target = "id", source = "dto.employeeId")
@Mapping(target = "name", source = "dto.employeeName")
Employee employeeDTOtoEmployee(EmployeeDTO dto);
DivisionDTO divisionToDivisionDTO(Division entity);
Division divisionDTOtoDivision(DivisionDTO dto);
}
4.3 Test Case
@Test
public void givenEmpDTONestedMappingToEmp_whenMaps_thenCorrect() {
EmployeeDTO dto = new EmployeeDTO();
dto.setDivision(new DivisionDTO(1, "Division1"));
Employee entity = mapper.employeeDTOtoEmployee(dto);
assertEquals(dto.getDivision().getId(),
entity.getDivision().getId());
assertEquals(dto.getDivision().getName(),
entity.getDivision().getName());
}
5.日期映射器
5.1 bean
@Data
public class Employee {
private int id;
private String name;
private Division division;
private Date startDt;
}
@Data
public class EmployeeDTO {
private int employeeId;
private String employeeName;
private DivisionDTO division;
private String employeeStartDt;
}
5.2 Interface
@Mapper
public interface EmployeeMapper {
@Mapping(target = "employeeId", source = "entity.id")
@Mapping(target = "employeeName", source = "entity.name")
@Mapping(target="employeeStartDt", source = "entity.startDt",
dateFormat = "dd-MM-yyyy HH:mm:ss")
EmployeeDTO employeeToEmployeeDTO(Employee entity);
@Mapping(target = "id", source = "dto.employeeId")
@Mapping(target = "name", source = "dto.employeeName")
@Mapping(target="startDt", source="dto.employeeStartDt",
dateFormat="dd-MM-yyyy HH:mm:ss")
Employee employeeDTOtoEmployee(EmployeeDTO dto);
DivisionDTO divisionToDivisionDTO(Division entity);
Division divisionDTOtoDivision(DivisionDTO dto);
}
5.3 Test Case
private static final String DATE_FORMAT = "dd-MM-yyyy HH:mm:ss";
@Test
public void givenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect() throws ParseException {
Employee entity = new Employee();
entity.setStartDt(new Date());
EmployeeDTO dto = employeeMapper.employeeToEmployeeDTO(entity);
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
assertEquals(format.parse(dto.getEmployeeStartDt()).toString(),
entity.getStartDt().toString());
}
@Test
public void givenEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect() throws ParseException {
EmployeeDTO dto = new EmployeeDTO();
dto.setEmployeeStartDt("01-04-2016 01:00:00");
Employee entity = employeeMapper.employeeDTOtoEmployee(dto);
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
assertEquals(format.parse(dto.getEmployeeStartDt()).toString(),
entity.getStartDt().toString());
}
6.自定义方法映射-抽象类
有时,我们可能希望以超出@Mapping 功能的方式自定义我们的映射器。
例如,除了类型转换之外,我们可能还想以某种方式转换值,如下面的示例所示。
在这种情况下,我们可以创建一个抽象类并实现我们想要自定义的方法,而将那些应该由 MapStruct 生成的抽象类保留下来。
6.1 bean
@Data
public class Transaction {
private Long id;
private String uuid = UUID.randomUUID().toString();
private BigDecimal total;
}
@Data
public class TransactionDTO {
private String uuid;
private Long totalInCents;
}
6.2 Interface
@Mapper
public abstract class TransactionMapper {
public TransactionDTO toTransactionDTO(Transaction transaction) {
TransactionDTO transactionDTO = new TransactionDTO();
transactionDTO.setUuid(transaction.getUuid());
transactionDTO.setTotalInCents(transaction.getTotal()
.multiply(new BigDecimal("100")).longValue());
return transactionDTO;
}
public abstract List<TransactionDTO> toTransactionDTO(
Collection<Transaction> transactions);
}
使用接口的默认方法
@Mapper
public interface TransactionMapper {
default TransactionDTO toTransactionDTO(Transaction transaction) {
TransactionDTO transactionDTO = new TransactionDTO();
transactionDTO.setUuid(transaction.getUuid());
transactionDTO.setTotalInCents(transaction.getTotal()
.multiply(new BigDecimal("100")).longValue());
return transactionDTO;
}
public List<TransactionDTO> toTransactionDTO(
Collection<Transaction> transactions);
}
将生成以下内容:
public class TransactionMapperImpl extends TransactionMapper {
@Override
public List<TransactionDTO> toTransactionDTO(Collection<Transaction> transactions) {
if ( transactions == null ) {
return null;
}
List<TransactionDTO> list = new ArrayList<TransactionDTO>( transactions.size() );
for ( Transaction transaction : transactions ) {
list.add( toTransactionDTO( transaction ) );
}
return list;
}
}
6.3 Test Case
private TransactionMapper transactionMapper = Mappers.getMapper(TransactionMapper.class);
@Test
public void givenTransactionEntitytoTransactionWithExpression_whenMaps_thenCorrect() {
Transaction transaction = new Transaction();
transaction.setId(1L);
transaction.setTotal(new BigDecimal(10));
TransactionDTO transactionDTO = transactionMapper.toTransactionDTO(transaction);
assertEquals(transaction.getUuid(), transactionDTO.getUuid());
}
7.@BeforeMapping @AfterMapping 使用
7.1 bean
@Data
public class Car {
private int id;
private String name;
}
@Data
public class CarDTO {
private int id;
private String name;
private FuelType fuelType;
}
public class BioDieselCar extends Car {
}
public class ElectricCar extends Car {
}
public enum FuelType {
ELECTRIC, BIO_DIESEL
}
7.2 Interface
@Mapper
public abstract class CarsMapper {
@BeforeMapping
protected void enrichDTOWithFuelType(Car car, @MappingTarget CarDTO carDto) {
if (car instanceof ElectricCar) {
carDto.setFuelType(FuelType.ELECTRIC);
}
if (car instanceof BioDieselCar) {
carDto.setFuelType(FuelType.BIO_DIESEL);
}
}
@AfterMapping
protected void convertNameToUpperCase(@MappingTarget CarDTO carDto) {
carDto.setName(carDto.getName().toUpperCase());
}
public abstract CarDTO toCarDto(Car car);
}
实现结果
public class CarsMapperImpl extends CarsMapper {
@Override
public CarDTO toCarDto(Car car) {
if ( car == null ) {
return null;
}
CarDTO carDTO = new CarDTO();
//根据类型判断
enrichDTOWithFuelType( car, carDTO );
carDTO.setId( car.getId() );
carDTO.setName( car.getName() );
//转大写
convertNameToUpperCase( carDTO );
return carDTO;
}
}
7.3 Test Case
CarsMapper carsMapper = Mappers.getMapper(CarsMapper.class);
@Test
public void givenCarsEntitytoCarsWithExpression_whenMaps_thenCorrect() {
Car cars = new BioDieselCar();
cars.setId(1);
cars.setName("1");
CarDTO carDTO = carsMapper.toCarDto(cars);
assertEquals(carDTO.getFuelType(), FuelType.BIO_DIESEL);
}
8.默认表达式 defaultExpression
8.1 Interface
@Mapper
public interface EmployeeMapper {
@Mapping(target = "employeeId", source = "entity.id", defaultExpression = "java(java.util.UUID.randomUUID().toString())")
@Mapping(target = "employeeName", source = "entity.name")
@Mapping(target="employeeStartDt", source = "entity.startDt",
dateFormat = "dd-MM-yyyy HH:mm:ss")
EmployeeDTO employeeToEmployeeDTO(Employee entity);
@Mapping(target = "id", source = "dto.employeeId")
@Mapping(target = "name", source = "dto.employeeName")
@Mapping(target="startDt", source="dto.employeeStartDt",
dateFormat="dd-MM-yyyy HH:mm:ss")
Employee employeeDTOtoEmployee(EmployeeDTO dto);
}
9. 数据类型映射
@Mapping(source = "price", target = "price", numberFormat = "$#.00")
10.枚举映射
10.1 bean
public enum PaymentType {
CASH,
CHEQUE,
CARD_VISA,
CARD_MASTER,
CARD_CREDIT
}
public enum PaymentTypeView {
CASH,
CHEQUE,
CARD
}
10.2 Interface
@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 Ill
MappingConstants
实现默认映射之后,所有剩余(未匹配)的枚举项都会映射为CARD
@Mapper
public interface PaymentTypeMapper {
PaymentTypeMapper INSTANCE = Mappers.getMapper(PaymentTypeMapper.class);
@ValueMapping(source = MappingConstants.ANY_REMAINING, 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 CASH: paymentTypeView = PaymentTypeView.CASH;
break;
case CHEQUE: paymentTypeView = PaymentTypeView.CHEQUE;
break;
default: paymentTypeView = PaymentTypeView.CARD;
}
return paymentTypeView;
}
}
使用MappingConstants.ANY_UNMAPPED
采用这种方式时,MapStruct不会像前面那样先处理默认映射,再将剩余的枚举项映射到target
值。而是,直接将所有未通过@ValueMapping
注解做显式映射的值都转换为target
值。
@Mapper
public interface PaymentTypeMapper {
PaymentTypeMapper INSTANCE = Mappers.getMapper(PaymentTypeMapper.class);
@ValueMapping(source = MappingConstants.ANY_UNMAPPED, 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 ) {
default: paymentTypeView = PaymentTypeView.CARD;
}
return paymentTypeView;
}
}
10.3 Test Case
@Test
void enumeration_mapping() {
PaymentTypeView paymentTypeView = PaymentTypeMapper.INSTANCE.paymentTypeToPaymentTypeView(PaymentType.CARD_MASTER);
assertEquals(paymentTypeView,PaymentTypeView.CARD);
}
11.集合映射
11.1 bean
@Data
public class Doctor {
private int id;
private String name;
}
@Data
public class DoctorDto {
private int id;
private String name;
}
11.2 Interface
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
List<DoctorDto> map(List<Doctor> doctor);
}
实现 可以看到生成了2个方法
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() );
return doctorDto;
}
}
11.3 Test Case
@Test
void coll_mapping() {
List<Doctor> doctor = new ArrayList<>();
doctor.add(new Doctor(1,"李四"));
doctor.add(new Doctor(2,"张三"));
doctor.add(new Doctor(3,"王五"));
List<DoctorDto> map = DoctorMapper.INSTANCE.map(doctor);
System.out.println(map.toString());
}
12.Set和Map映射
12.1 bean
@Data
public class Doctor {
private int id;
private String name;
}
@Data
public class DoctorDto {
private int id;
private String name;
}
12.2 Interface
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
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 :
12.3 Test Case
@Test
void set_mapping() {
Set<Doctor> doctor = new HashSet<>();
doctor.add(new Doctor(1, "李四"));
doctor.add(new Doctor(2, "张三"));
doctor.add(new Doctor(3, "王五"));
Set<DoctorDto> map = DoctorMapper.INSTANCE.setConvert(doctor);
System.out.println(map.toString());
}
@Test
void map_mapping() {
Map<String, Doctor> map = new HashMap<>();
map.put("1", new Doctor(1, "李四"));
map.put("2", new Doctor(2, "张三"));
map.put("3", new Doctor(3, "王五"));
Map<String, DoctorDto> dtoMap = DoctorMapper.INSTANCE.mapConvert(map);
System.out.println(dtoMap.toString());
}
13.依赖注入
到目前为止,我们一直在通过getMapper()
方法访问生成的映射器:
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
但是,如果你使用的是Spring,只需要简单修改映射器配置,就可以像常规依赖项一样注入映射器。
修改 DoctorMapper
以支持Spring框架:
@Mapper(componentModel = "spring")
public interface DoctorMapper {}
这次生成的 DoctorMapperImpl
会带有 @Component
注解:
@Component
public class DoctorMapperImpl implements DoctorMapper {}
只要被标记为@Component
,Spring就可以把它作为一个bean来处理,你就可以在其它类(如控制器)中通过@Autowire
注解来使用它:
@Controller
public class DoctorController() {
@Autowired
private DoctorMapper doctorMapper;
}
14.映射异常处理
异常处理是不可避免的,应用程序随时会产生异常状态。MapStruct提供了对异常处理的支持,可以简化开发者的工作。
14.1 Bean
@Data
public class Doctor {
private int id;
private String name;
}
@Data
public class DoctorDto {
private int id;
private String name;
}
14.2 Validator
添加Validator
校验
@Component
public class Validator {
public int validateId(int id) throws ValidationException {
if(id == -1){
throw new ValidationException("Invalid value in ID");
}
return id;
}
public String validateName(String name) throws NullPointerException {
if(name == null){
throw new NullPointerException("name is null");e in ID");
}
return name;
}
}
14.3 Interface
@Mapper(uses = {Validator.class},componentModel = "spring")
public interface DoctorMapper {
DoctorDto toDto(Doctor doctor);
}
映射器代码如下:
@Component
public class DoctorMapperImpl implements DoctorMapper {
@Autowired
private Validator validator;
@Override
public DoctorDto toDto(Doctor doctor) {
if ( doctor == null ) {
return null;
}
DoctorDto doctorDto = new DoctorDto();
try {
doctorDto.setId( validator.validateId( doctor.getId() ) );
}
catch ( ValidationException e ) {
throw new RuntimeException( e );
}
try {
doctorDto.setName( validator.validateName( doctor.getName() ) );
}
catch ( NullPointerException e ) {
throw new RuntimeException( e );
}
return doctorDto;
}
}
14.4 Test Case
@Test
void toDto(){
Doctor doctor = new Doctor(1, "李四");
DoctorDto doctorDto = doctorMapper.toDto(doctor);
System.out.println(doctorDto.toString());
//会报错
Doctor doctorError = new Doctor(1, null);
DoctorDto doctorDtoError = doctorMapper.toDto(doctorError);
System.out.println(doctorDtoError.toString());
}
15.自定义方法映射-@Named
15.1 bean
@Data
public class Doctor {
private int id;
private String name;
}
@Data
public class DoctorDto {
private int id;
private String name;
}
15.2 Interface
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
@Mapping(source = "id", target = "id", qualifiedByName = "multiply10")
DoctorDto toDto(Doctor doctor);
@Named("multiply10")
public static int multiply10(int id) {
return id * 10;
}
}
映射的方法
public class DoctorMapperImpl implements DoctorMapper {
@Override
public DoctorDto toDto(Doctor doctor) {
if ( doctor == null ) {
return null;
}
DoctorDto doctorDto = new DoctorDto();
doctorDto.setId( DoctorMapper.multiply10( doctor.getId() ) );
doctorDto.setName( doctor.getName() );
return doctorDto;
}
}
15.3 Test Case
@Test
void toDto(){
Doctor doctor = new Doctor(1, "李四");
DoctorDto doctorDto = DoctorMapper.INSTANCE.toDto(doctor);
System.out.println(doctorDto.toString());
}
16.自定义方法映射-自定义注解
16.1 bean
@Data
public class Doctor {
private int id;
private String name;
}
@Data
public class DoctorDto {
private int id;
private String name;
}
16.2 annotation
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PoundToKilogramMapper {
}
16.3 Interface
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
@Mapping(source = "id", target = "id", qualifiedBy = PoundToKilogramMapper.class)
DoctorDto toDto(Doctor doctor);
@PoundToKilogramMapper
public static int multiply10(int id) {
return id * 10;
}
}
映射的方法
public class DoctorMapperImpl implements DoctorMapper {
@Override
public DoctorDto toDto(Doctor doctor) {
if ( doctor == null ) {
return null;
}
DoctorDto doctorDto = new DoctorDto();
doctorDto.setId( DoctorMapper.multiply10( doctor.getId() ) );
doctorDto.setName( doctor.getName() );
return doctorDto;
}
}
16.4 Test Case
@Test
void toDto(){
Doctor doctor = new Doctor(1, "李四");
DoctorDto doctorDto = DoctorMapper.INSTANCE.toDto(doctor);
System.out.println(doctorDto.toString());
}
17.忽略映射字段
17.1 忽略特定字段
如果映射不完整,它也可以生成错误报告
Warning:(X,X) java: Unmapped target property: "propertyName".
可以用ignore = true
来忽略
@Mapping(target = "comments", ignore = true)
17.2 映射器设置策略
我们可以将unmappedTargetPolicy设置为@Mapper注解。结果,它的所有方法都将忽略未映射的属性
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
DoctorDto toDto(Doctor doctor);
}
17.3 共享MapperConfig
@MapperConfig(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface IgnoreUnmappedMapperConfig {
}
@Mapper(config = IgnoreUnmappedMapperConfig.class)
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
DoctorDto toDto(Doctor doctor);
}
17.4 配置选项
我们可以配置 MapStruct 代码生成器的注释处理器选项。使用Maven时,我们可以使用处理器插件的compilerArgs参数传递处理器选项
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<compilerArg>
-Amapstruct.unmappedTargetPolicy=IGNORE
</compilerArg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
18.多个源对象
目前有一个场景,我们需要一个设备的完整信息包含设备信息,设备位置信息。 当我们得到设备信息,与设备位置信息后合并成为一个完整的设备信息
18.1 bean
@Data
public class Device {
private String name;
private String no;
}
@Data
public class DeviceSpace {
private String area;
private String room;
}
@Data
public class DeviceDTO {
private String deviceName;
private String deviceNo;
private String spaceArea;
private String spaceRoom;
}
18.2 Interface
@Mapper
public interface DeviceMapper {
DeviceMapper INSTANCE = Mappers.getMapper(DeviceMapper.class);
@Mapping(source = "space.area", target = "spaceArea")
@Mapping(source = "space.room", target = "spaceRoom")
@Mapping(source = "device.name", target = "deviceName")
@Mapping(source = "device.no", target = "deviceNo")
DeviceDTO from(Device device, DeviceSpace space);
}
映射器生成代码
public class DeviceMapperImpl implements DeviceMapper {
@Override
public DeviceDTO from(Device device, DeviceSpace space) {
if ( device == null && space == null ) {
return null;
}
DeviceDTO deviceDTO = new DeviceDTO();
if ( device != null ) {
deviceDTO.setDeviceName( device.getName() );
deviceDTO.setDeviceNo( device.getNo() );
}
if ( space != null ) {
deviceDTO.setSpaceArea( space.getArea() );
deviceDTO.setSpaceRoom( space.getRoom() );
}
return deviceDTO;
}
}
18.3 Test Case
@Test
void from() {
Device device = new Device();
device.setName("device");
device.setNo("no");
DeviceSpace space = new DeviceSpace();
space.setArea("area");
space.setRoom("room");
DeviceDTO from = DeviceMapper.INSTANCE.from(device, space);
System.out.println(from.toString());
}