MapStruct 快速指南

MapStruct 快速指南

MapStruct

MapStruct 是一个代码生成器,它基于约定优于配置方法极大地简化了 Java bean 类型之间映射的实现。自动生成的映射转换代码只使用简单的方法调用,因此速度快、类型安全而且易于理解阅读,源码仓库 Github 地址 MapStruct。总的来说,有如下三个特点:

  1. 基于注解
  2. 在编译期自动生成映射转换代码
  3. 类型安全、高性能、无依赖性

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());
    }