MapStruct 使用指南

MapStruct 使用指南

一、背景

在我们开发的软件系统中,有着各式各样用于存储数据的实体,按照一般标准,可分为以下几类

  • POJO(Plain Ordinary Java Object):简单 Java 对象,就是普通 Java 对象,普通 JavaBeans ,有时可以作为 VO(value-object)或 DTO(Data Transfer Object)来使用
  • VO(View Object):视图对象,用于与前端的交互的展示层,作用是封装返回给前端的数据起
  • DTO(Data Transfer Object):数据传输对象,泛指用于各个层级间的数据传输对象。即提取依赖资源中所需要的的属性,同时减少不需要的属性,来提高传输速度,以及减少流量。
  • Entity :entity里的每一个字段,与数据库相对应。
  • 。。。

上述几类实体,经常需要互相转换,而手动创建 bean 映射器非常耗时,有没有更简单的解决方案呢?

MapStruct 应运而生,它可以自动生成 Bean 映射器类,来完成各个实体键数据的传输

在本文中,我们将深入研究 MapStruct

二、简介

MapStruct 是一个开源的基于 Java 的代码生成器,用于创建实现 Java Bean 之间转换的扩展映射器。

使用 MapStruct,我们只需要创建接口,而该库会通过注解,在编译过程中自动创建具体的映射实现,大大减少了通常需要手工编写的样板代码的数量。

三、MapStruct 依赖

  • 注意:mapstruct 与 lombok 版本必须匹配,否则会出现映射不上的问题。

1、Maven

    <dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.2.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.2.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

由于 MapStruct 在编译时工作,并且会集成到像 Maven 和 Gradle 这样的构建工具上,我们还必须在 <build/> 标签中添加一个插件 maven-compiler-plugin

并在其配置中添加 annotationProcessorPaths,该插件会在构建时生成对应的代码(这一步也可以不加,直接在依赖中添加 mapstruct-processor ,如上)

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                    
                    // 同时使用了 lombok ,必须加上这个,否则编译不通过
                    <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2、Gradle

plugins {
    id 'net.ltgt.apt' version '0.20'
}

apply plugin: 'net.ltgt.apt-idea'
apply plugin: 'net.ltgt.apt-eclipse'

dependencies {
    compile "org.mapstruct:mapstruct:${mapstructVersion}"
    annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
}

net.ltgt.apt 插件会负责处理注释。你可以根据使用的 IDE 启用插件 apt-ideaapt-eclipse 插件。

MapStruct 及其 处理器 的最新稳定版本都可以从 Maven中央仓库 中获得。

四、映射

1、基本映射

实例讲解

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);
    DoctorDto toDto(Doctor doctor);
}

当我们构建/编译应用程序时,MapStruct 注解处理器插件会识别出 DoctorMapper 接口并为其生成一个实现类。

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

使用

DoctorDto doctorDto = DoctorMapper.INSTANCE.toDto(doctor);

注意:你可能也注意到了上面实现代码中的DoctorDtoBuilder。因为 builder 代码往往比较长,为了简洁起见,这里省略了builder模式的实现代码。如果你的类中包含Builder,MapStruct 会尝试使用它来创建实例;如果没有的话,MapStruct 将通过 new 关键字进行实例化。

2、不同字段间映射

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

这个注解代码的含义是: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();
    }
}

3、多个源类

有时候,需要将多个类中的值聚合为一个 DTO,以下为演示代码:

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

如果 Education 类和 Doctor 类包含同名的字段,我们必须让映射器知道使用哪一个,否则它会抛出一个异常。

举例来说,如果两个模型都包含一个 id 字段,我们就要选择将哪个类中的 id 映射到 DTO 属性中。

4、子对象映射

多数情况下,POJO 中不会包含基本数据类型,其中往往会包含其它类,MapStruct 也支持对类里面的复杂对象,进行映射,演示代码如下:

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

因为我们要处理另一个需要映射的类,所以这里设置了 @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;
    }
}

5、更新现有实例

有时,需要用 DTO 的最新值更新一个模型中的属性,对目标对象使用 @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());
    }
}

五、数据类型转换

1、数据类型映射

MapStruct 支持 sourcetarget 属性之间的数据类型转换。它还提供了基本类型及其相应的包装类之间的自动转换。

自动类型转换适用于:

  • 基本类型及其对应的包装类之间。比如, intIntegerfloatFloatlongLongbooleanBoolean 等。
  • 任意基本类型与任意包装类之间。如 intlongbyteInteger 等。
  • 所有基本类型及包装类与 String 之间。如 booleanStringIntegerStringfloatString 等。
  • 枚举和 String 之间。
  • Java大数类型( java.math.BigIntegerjava.math.BigDecimal) 和 Java 基本类型(包括其包装类)与 String 之间。
  • 其它情况详见 MapStruct 官方文档
public class PatientDto {
    private int id;
    private String name;
    private LocalDate dateOfBirth;
    // getters and setters or builder
}
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()));
}

2、数字格式转换

   // 数字格式转换示例
   @Mapping(source = "price", target = "price", numberFormat = "$#.00")

3、枚举映射

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

实现代码:

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

但是,如果你要将很多值转换为一个更一般的值,这种方式就有些不切实际了。其实我们不必手动分配每一个值,只需要让 MapStruct 将所有剩余的可用枚举项(在目标枚举中找不到相同名称的枚举项),直接转换为对应的另一个枚举项。

可以通过 MappingConstants 实现这一点:

@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "CARD")
PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType);

实现代码:

@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;
}

还有一种选择,使用 ANY UNMAPPED

@ValueMapping(source = MappingConstants.ANY_UNMAPPED, target = "CARD")
PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType);

4、集合映射

1)、List映射
@Mapper
public interface DoctorMapper {
    List<DoctorDto> map(List<Doctor> doctor);
}

实现代码:

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() );
        doctorDto.setSpecialization( doctor.getSpecialization() );

        return doctorDto;
    }
}

但是需要注意,如果我们在 DTO 中新增一个字段 fullName,生成代码时会出现警告:

警告: Unmapped target property: “fullName”.

基本上,这意味着 MapStruct 在当前情况下无法为我们自动生成映射方法。因此,我们需要手动定义 DoctorDoctorDto 之间的映射方法。具体参考之前的小节。

2)、Set和Map映射
@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;
    }
}
3)、集合映射策略

@Mapper 注解中的 collectionMappingStrategy 属性,该属性取值如下

  • ACCESSOR_ONLY (默认值)
  • SETTER_PREFERRED
  • ADDER_PREFERRED
  • TARGET_IMMUTABLE
public class Hospital {
    private List<Doctor> doctors;
    // getters and setters or builder
}
public class HospitalDto {

    private List<DoctorDto> doctors;

		// 子类型集合字段getter
    public List<DoctorDto> getDoctors() {
        return doctors;
    }
		// 子类型集合字段setter
    public void setDoctors(List<DoctorDto> doctors) {
        this.doctors = doctors;
    }
		// 子类型数据adder
    public void addDoctor(DoctorDto doctorDTO) {
        if (doctors == null) {
            doctors = new ArrayList<>();
        }

        doctors.add(doctorDTO);
    }
}

映射器:

@Mapper(uses = DoctorMapper.class)
public interface HospitalMapper {
    HospitalMapper INSTANCE = Mappers.getMapper(HospitalMapper.class);

    HospitalDto toDto(Hospital hospital);
}

实现代码:

public class HospitalMapperImpl implements HospitalMapper {

    @Override
    public HospitalDto toDto(Hospital hospital) {
        if ( hospital == null ) {
            return null;
        }

        HospitalDto hospitalDto = new HospitalDto();

        hospitalDto.setDoctors( doctorListToDoctorDtoList( hospital.getDoctors() ) );

        return hospitalDto;
    }
}

可以看到,在默认情况下采用的策略是 ACCESSOR_ONLY ,使用setter方法 setDoctors()HospitalDto 对象中写入列表数据。

使用 ADDER_PREFERRED

@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
        uses = DoctorMapper.class)
public interface HospitalMapper {
    HospitalMapper INSTANCE = Mappers.getMapper(HospitalMapper.class);

    HospitalDto toDto(Hospital hospital);
}
public class CompanyMapperAdderPreferredImpl implements CompanyMapperAdderPreferred {

    private final EmployeeMapper employeeMapper = Mappers.getMapper( EmployeeMapper.class );

    @Override
    public CompanyDTO map(Company company) {
        if ( company == null ) {
            return null;
        }

        CompanyDTO companyDTO = new CompanyDTO();

        if ( company.getEmployees() != null ) {
            for ( Employee employee : company.getEmployees() ) {
                companyDTO.addEmployee( employeeMapper.map( employee ) );
            }
        }

        return companyDTO;
    }
}

如果目标 DTO 中既没有 setter 方法也没有 adder 方法,会先通过 getter 方法获取子类型集合,再调用集合的对应接口添加子类型对象。

其他策略参考 官方文档

4)、目标集合实现类型

MapStruct 支持将集合接口作为映射方法的目标类型。

在这种情况下,在生成的代码中会使用一些集合接口默认实现。 例如,上面的示例中,List 的默认实现是 ArrayList

常见接口及其对应的默认实现如下:

Interface typeImplementation type
CollectionArrayList
ListArrayList
MapHashMap
SortedMapTreeMap
ConcurrentMapConcurrentHashMap

更多映射参考 官方文档

可以找到 MapStruct 支持的所有接口列表,以及每个接口对应的默认实现类型。

六、进阶操作

1、依赖注入

修改 DoctorMapper 以支持 Spring 框架:

@Mapper(componentModel = "spring")
public interface DoctorMapper {}

实现代码:

@Component
public class DoctorMapperImpl implements DoctorMapper {}

使用:

@Controller
public class DoctorController() {
    @Autowired
    private DoctorMapper doctorMapper;
}

2、添加默认值

@Mapping 注解标志属性:

  • 常量 constant:无论 source 如何取值,都将始终使用常量值;
  • 默认值 defaultValue :如果 source 取值为 null,则会使用默认值。
@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);
}

实现代码:

@Component
public class DoctorMapperImpl implements DoctorMapper {

    @Autowired
    private PatientMapper patientMapper;
    
    @Override
    public DoctorDto toDto(Doctor doctor) {
        if (doctor == null) {
            return null;
        }

        DoctorDto doctorDto = new DoctorDto();

        if (doctor.getSpecialty() != null) {
            doctorDto.setSpecialization(doctor.getSpecialty());
        } else {
            doctorDto.setSpecialization("Information Not Available");
        }
        doctorDto.setPatientDtoList(patientListToPatientDtoList(doctor.getPatientList()));
        doctorDto.setName(doctor.getName());

        doctorDto.setId(-1);

        return doctorDto;
    }
}

3、添加表达式

@Mapping 注解添加 Java 表达式。

  • defaultExpressionsource 取值为 null 时生效)
  • expression(类似常量,永久生效)。
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);
}

表达式只是字符串,我们必须在表达式中指定使用的类。但是这里的表达式并不是最终执行的代码,只是一个字母的文本值。因此,我们要在 @Mapper 中添加 imports = {LocalDateTime.class, UUID.class}

4、添加自定义方法

a、使用接口
  • 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
}
@Mapper
public interface DoctorMapper {

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

使用

DoctorPatientSummary summary = doctorMapper.toDoctorPatientSummary(dotor, education);
b、使用抽象类
@Mapper
public abstract class DoctorCustomMapper {
    public 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();
    }
}
c、@BeforeMapping 和 @AfterMapping
@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;
    }
}

5、映射异常处理

public class Validator {
    public int validateId(int id) throws ValidationException {
        if(id == -1){
            throw new ValidationException("Invalid value in ID");
        }
        return id;
    }
}
@Mapper(uses = {PatientMapper.class, Validator.class}, componentModel = "spring")
public interface DoctorMapper {

    @Mapping(source = "doctor.patientList", target = "patientDtoList")
    @Mapping(source = "doctor.specialty", target = "specialization")
    DoctorDto toDto(Doctor doctor) throws ValidationException;
}

实现代码:

@Component
public class DoctorMapperImpl implements DoctorMapper {

    @Autowired
    private PatientMapper patientMapper;
    @Autowired
    private Validator validator;

    @Override
    public DoctorDto toDto(Doctor doctor) throws ValidationException {
        if (doctor == null) {
            return null;
        }

        DoctorDto doctorDto = new DoctorDto();

        doctorDto.setPatientDtoList(patientListToPatientDtoList(doctor
            .getPatientList()));
        doctorDto.setSpecialization(doctor.getSpecialty());
        doctorDto.setId(validator.validateId(doctor.getId()));
        doctorDto.setName(doctor.getName());
        doctorDto.setExternalId(doctor.getExternalId());
        doctorDto.setAvailability(doctor.getAvailability());

        return doctorDto;
    }
}

6、映射配置

a、继承配置
@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);

    // @Mapping(source = "doctorDto.specialization", target = "specialty")
    // @Mapping(source = "doctorDto.patientDtoList", target = "patientList")
    // 继承上面配置,避免在写一遍
    @InheritConfiguration
    void updateModel(DoctorDto doctorDto, @MappingTarget Doctor doctor);
}
b、继承逆向配置
@Mapper(componentModel = "spring")
public interface PatientMapper {

    @Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy")
    Patient toModel(PatientDto patientDto);

    // 继承逆向配置,避免反着写一遍
    @InheritInverseConfiguration
    PatientDto toDto(Patient patient);
}

以上是全文,谢谢浏览!!!


作者:GuoYaxiang
原文链接:https://juejin.cn/post/6956190395319451679

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值