org.mapstruct:mapstruct 包(@Mapper、@Mapping)的使用

2年前 (2022) 程序员胖胖胖虎阿
346 0 0

org.mapstruct:mapstruct 包的使用

最近在学习技术时候,发现一个特别好用的包,org.mapstruct:mapstruct,它是专门用来处理 domin 实体类与 model 类的属性映射的

它的优势:

  • 很多项目大量映射的方式通过手动get、set,首先写法很low,没有技术含量。而且中间还可能牵涉了很多类型转换,嵌套之类的繁琐操作,非常的麻烦。
  • 关于属性的映射,apache的BeanUtil. copyProperties也可以实现,但是其性能差而且容易出异常,很多规范严禁使用这种途径。MapStruct原理类似于lombok,性能高。MapStruct也是像lombok 在编译期进行实现,而且基于Getter、Setter,没有使用反射,所以一般也不存在运行时性能问题。
  • 方便属性的映射操作,简化代码,真正实现一套代码兼容多个平台。
  • 在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之后,一般采用DDD思想,DO 一般不会让外部依赖,提供对外接口的模块里放 DTO 用于对象传输,也即是 DO 对象对内,DTO对象对外,DTO 可以根据业务需要变更,并不需要映射 DO 的全部属性。那关于DO与DTO的属性映射关系,Mapstruct就真正能派上用场
  • 对于包装类是自动拆箱封箱操作的,线程安全。此外,mapstruct还支持一些复杂的功能:设置转换默认值和常量。当目标值是null时可以设置其默认值。

它的优势很多,自己在学习之后觉得真的是个好东西,所以在学习之后来总结一下这个包的使用,方便后面查看。

1、导包

在项目的pom.xml 文件夹中导入包

 <!--mapStruct依赖-->
<dependency>
     <groupId>org.mapstruct</groupId>
     <artifactId>mapstruct</artifactId>
     <version>1.3.1.Final</version>
</dependency>
<dependency>
     <groupId>org.mapstruct</groupId>
     <artifactId>mapstruct-processor</artifactId>
     <version>1.3.1.Final</version>
     <scope>provided</scope>
</dependency>

可以看到scope是provided,说明其在编译期实现

如果想要在代码编写的时候有很好的提示的时候,也可以在pom.xml文件中加入相应的插件

  • 这边为了方便,代码演示也用到了Lombok包,具体使用可以查看这个博客 lombok 的使用
  • 同时使用Lombok和mapstruct,后期运行出现了 Error:(15, 13) java: No property named “id“ exists in source parameter(s). Did you mean “null“? 查看该篇文章
<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>1.3.1.Final</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2、使用

这个包的注解其实也有很多,我主要介绍一个几个常用的注解

注解 使用位置 描述
Mapper 适用在 类 接口 枚举 上 进行类的映射
Mapping 适用在 方法 上 进行方法参数属性的映射
MapMapping 适用在 方法 上 进行方法参数(Map类型)的映射
Mappings 适用在 方法 上 组合注解,可以标记多个Mapping

2.1 注解基础知识的介绍

// 表明注解可以使用在哪里
// 这里表示注解可以使用在 方法 属性  注解  构造方法  参数  type 
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
//描述保留注解的各种策略,它们与元注解(@Retention)一起指定注释要保留多长时间
@Retention(RetentionPolicy.RUNTIME)
//表明这个注解是由 javadoc记录的
@Documented
public @interface MyOwnAnnotation {
    
}

2.2 @Mapper

@Mapper注解一般我们标记在接口上,表示这个接口作为一个映射接口,并且是编译时MapStruct处理器的入口。

注解源码:

package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
// 编译时有效
@Retention(RetentionPolicy.CLASS)
public @interface Mapper {
    // 此映射程序使用的其他映射程序类型。可以是手工编写的类或MapStruct生成的其他映射程序
    // 例如转换类有包含一个成员变量,这个也需要进行转换,用到其他的Mapper,这里就可以用use来导入
    Class<?>[] uses() default {};

    // 要将导入语句添加到生成的映射器实现类的其他类型 允许在通过映射给出的映射表达式中引用这些类型
    Class<?>[] imports() default {};

    // 如果源类的未映射的属性应该怎么处理,有IGNORE(忽略)WARN(警告)ERROR(报错)
    ReportingPolicy unmappedSourcePolicy() default ReportingPolicy.IGNORE;
	// 如果目标类的未映射的属性应该怎么处理,有IGNORE(忽略)WARN(警告)ERROR(报错)
    ReportingPolicy unmappedTargetPolicy() default ReportingPolicy.WARN;
	
    // 类型转换精度损失如何处理,例如从长整型转换为整形的精度损失
    ReportingPolicy typeConversionPolicy() default ReportingPolicy.IGNORE;
	
    /**
    * componentModel有以下几种取值,一般用的比较多的是spring
    * default: 默认取值,mapstruct 不使用任何组件类型, 我们在获取生成实例的时候,可以通过Mappers.getMapper(Class)方式获取
      spring: 生成的实现类上面会自动添加一个@Component注解,可以通过Spring的 @Autowired方式进行注入
	  cdi: 生成的映射器是一个应用程序范围的 CDI bean,可以通过 @Inject 检索
	  jsr330: 生成的实现类上会添加@javax.inject.Named 和@Singleton注解,可以通过 @Inject注解获取
    */
    String componentModel() default "default";
	
    //指定实现类的名称,默认是  @Mapper修饰的类名称+Impl
    String implementationName() default "<CLASS_NAME>Impl";
    
    //指定实现类所在的包名称,默认原包
    String implementationPackage() default "<PACKAGE_NAME>";

    // 配置模板,一般是  MapperConfig 注解标注的列作为模板
    Class<?> config() default void.class;
	
    // Collection属性的值在映射过程中采用的策略。一般我们都适用get/set方法来进行值得映射,如果有额外方法是否采用
    // ACCESSOR_ONLY:目标属性的setter将用于值得映射
    // ADDER_PREFERRED:如果两者都存在,则额外方法将优先于SETTER方法
    // SETTER_PREFERRED:如果两者都存在,目标属性的setter将用于值得映射
    // TARGET_IMMUTABLE:与SETTER_PREFERRED相同,但是在更新现有bean实例的情况下,目标集合不会被清除并通过addAll访问
    CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.ACCESSOR_ONLY;

    // 将null作为源参数值传递给此映射器的方法时要应用的策略
	// RETURN_NULL:直接返回空
    // RETURN_DEFAULT:返回默认值
    NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;

    // 当源bean属性为null或不存在时应用的策略
    // IGNORE:忽略,源bean属性等于null,那么将忽略目标bean属性并保留其现有值
    // SET_TO_DEFAULT:源bean属性等于null,则目标bean属性将设置为其默认值
    // SET_TO_NULL:源bean属性等于null,则目标bean属性将设置为null,默认为SET_TO_NULL
    NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default NullValuePropertyMappingStrategy.SET_TO_NULL;

    //用于在config()指定的接口中应用原型方法的方法级配置注释的策略
    // 可以继承的注释有例如Mapping、IterableMapping、MapMapping或BeanMapping。
    // AUTO_INHERIT_ALL_FROM_CONFIG:如果原型方法的源类型和目标类型可从给定映射方法的类型分配,则自动继承方法级正向和反向配置注释。
    // AUTO_INHERIT_FROM_CONFIG:如果原型方法的源类型和目标类型可从给定映射方法的类型分配,则自动继承方法级正向配置注释
    // AUTO_INHERIT_REVERSE_FROM_CONFIG:如果原型方法的源和目标类型可从给定映射方法的目标和源类型分配,则自动继承方法级反向配置注释
    // EXPLICIT:仅当使用InheritConfiguration显式引用原型方法时,才应用方法级配置注释
    MappingInheritanceStrategy mappingInheritanceStrategy() default MappingInheritanceStrategy.EXPLICIT;

    // 确定何时对bean映射的源属性值进行null检查,可被MapperConfig、BeanMapping或Mapping上的覆盖
    // ALWAYS:始终
    // ON_IMPLICIT_CONVERSION:在涉及隐式类型转换得时候进行检查
    NullValueCheckStrategy nullValueCheckStrategy() default NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;

    // 确定注入方式是使用字段注入还是构造函数注入
    // FIELD:字段注入
    // CONSTRUCTOR:构造函数注入
    InjectionStrategy injectionStrategy() default InjectionStrategy.FIELD;
	
    // 如果MapStruct找不到其他映射方法或应用自动转换,它将尝试在两个bean之间生成子映射方法。如果此属性设置为true,MapStruct将不会尝试自动生成子映射方法
    boolean disableSubMappingMethodsGeneration() default false;
	
    // 生成静态Builder类和builder方法
    Builder builder() default @Builder;
}

2.2.1 简单使用

例如现在有一个Log对象,与数据库表进行相关映射的entity,传输对象是LogDTO对象

我们经常涉及到需要把获取到entity与DTO进行相互转换,来看看看怎么用

(这里先简单使用一下举个例子,所以设置俩个bean的字段名称、类型都是一样的,但是Log比LogDTO多了几个字段)

/**
* 传输对象DTO
*/
@Getter
@Setter
public class LogDTO implements Serializable {

    private Long id;

    private String realName;

    private String name;

    private String suffix;

    private String type;

    private String size;
}

/**
* 数据库映射相关entity
*/
@Getter
@Setter
@Entity
@Table(name="log")
@NoArgsConstructor
public class Log extends BaseEntity implements Serializable {

    @Id
    @Column(name = "storage_id")
    @ApiModelProperty(value = "ID", hidden = true)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ApiModelProperty(value = "真实文件名")
    private String realName;

    @ApiModelProperty(value = "文件名")
    private String name;

    @ApiModelProperty(value = "后缀")
    private String suffix;

    @ApiModelProperty(value = "路径")
    private String path;

    @ApiModelProperty(value = "类型")
    private String type;

    @ApiModelProperty(value = "大小")
    private String size;

    public LocalStorage(String realName,String name, String suffix, String path, String type, String size) {
        this.realName = realName;
        this.name = name;
        this.suffix = suffix;
        this.path = path;
        this.type = type;
        this.size = size;
    }

    public void copy(LocalStorage source){
        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
    }
}

首先定义一个基础得Mapper,其他所有得Mapper都可以实现这个BaseMapper,里面定义了几个方法,分别就是

  • 单个实体与单个DTO的相互转变
  • 集合实体与集合DTO的相互转变
package com.study.mapper;

import java.util.List;

public interface BaseMapper<D, E> {

    /**
     * DTO转Entity
     * @param dto /
     * @return /
     */
    E toEntity(D dto);

    /**
     * Entity转DTO
     * @param entity /
     * @return /
     */
    D toDto(E entity);

    /**
     * DTO集合转Entity集合
     * @param dtoList /
     * @return /
     */
    List<E> toEntity(List<D> dtoList);

    /**
     * Entity集合转DTO集合
     * @param entityList /
     * @return /
     */
    List <D> toDto(List<E> entityList);
}

LogMapper,继承BaseMapper,就相应也会有对应方法

/**
* 我们这里使用了spring注入的形式,忽略掉当转换时没有映射到的目标属性
* 使用的时候可以通过 @Autowired的方式注入
*/
@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface LogMapper extends BaseMapper<LogDTO, Log> {

}

/**
* 如果这里的componentModel为default的时候,那我们在使用的时候,需要通过 Mappers.getMapper来获取使用
*/
@Mapper(componentModel = "default",unmappedTargetPolicy = ReportingPolicy.IGNORE)
LogMapper INSTANCES = Mappers.getMapper(LogMapper.class);

我们编译后生成对应的class文件后,来反编译看看源码变成什么样子了(如果不会反编译,可以查看另外一篇博客 JAVA 如何将class文件转换成java文件 )

可以看到这时候生成了一个具体的实现类,里面实现了对应的四个方法,可以看到默认都是通过set来进行属性的赋值的

package com.study.mapper;
// Referenced classes of package com.study.mapper:
//            BaseMapper
// logMapper接口
public interface LogMapper extends BaseMapper{
}

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   LogMapperImpl.java
package com.study.mapper;
import com.study.DO.Log;
import com.study.DTO.LogDTO;
import java.util.*;
// LogMapper的实现类LogMapperImpl
public class LogMapperImpl implements LogMapper{

    public LogMapperImpl()
    {
    }
	// LogDTO转换为Log
    public Log toEntity(LogDTO dto)
    {
        if(dto == null)
        {
            return null;
        } else
        {
            Log log = new Log();
            log.setId(dto.getId());
            log.setRealName(dto.getRealName());
            log.setName(dto.getName());
            log.setSuffix(dto.getSuffix());
            log.setType(dto.getType());
            log.setSize(dto.getSize());
            return log;
        }
    }
	// Log转换为LogDTO
    public LogDTO toDto(Log entity)
    {
        if(entity == null)
        {
            return null;
        } else
        {
            LogDTO logDTO = new LogDTO();
            logDTO.setId(entity.getId());
            logDTO.setRealName(entity.getRealName());
            logDTO.setName(entity.getName());
            logDTO.setSuffix(entity.getSuffix());
            logDTO.setType(entity.getType());
            logDTO.setSize(entity.getSize());
            return logDTO;
        }
    }
	// List LogDTO转换为 List Log
    public List toEntity(List dtoList)
    {
        if(dtoList == null)
            return null;
        List list = new ArrayList(dtoList.size());
        LogDTO logDTO;
        for(Iterator iterator = dtoList.iterator(); iterator.hasNext(); list.add(toEntity(logDTO)))
            logDTO = (LogDTO)iterator.next();

        return list;
    }
	// List LogDTO转换为List  Log
    public List toDto(List entityList)
    {
        if(entityList == null)
            return null;
        List list = new ArrayList(entityList.size());
        Log log;
        for(Iterator iterator = entityList.iterator(); iterator.hasNext(); list.add(toDto(log)))
            log = (Log)iterator.next();

        return list;
    }

    public volatile Object toDto(Object obj)
    {
        return toDto((Log)obj);
    }

    public volatile Object toEntity(Object obj)
    {
        return toEntity((LogDTO)obj);
    }
}


在其他地方具体的使用Mapper的时候:

因为已经利用Spring的方式注入到Ioc容器中了,所以可以直接利用 @Autowired 来注入然后调用对应的方法完成对应的转换操作。

@RestController
public class UserController {
    
    @Autowired
    LogService logService;
    
    @PostMapping("/logs/save")
    public String saveLog(LogDTO logDTO) {
        // 传输对象用DTO
        logService.save(logDTO);
        return "{\"result\":\"success\"}";
    }
}

@Service
public class LogService {
    
    @Autowired
    LogResposity logResposity;
    
    @Autowired
    LogMapper logMapper;

    public void save(LogDTO logDTO) {
        // 先转换,再存储
        Log log = logMapper.toEntity(logDTO);
        logResposity.save(log);
    }
}

2.3 @Mapping

@Mapping 用在方法上,一般用于 方法传参pojo与返回pojo 的类型转换

对应的源码与解释如下:

// 可重复使用
@Repeatable(Mappings.class)
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD})
public @interface Mapping {
    // 设置的目标类的属性名称,必须传入
    String target();
	// 源类的属性名称
    String source() default "";
   
	// 设置指定目标属性的常量字符串(如果没有源目标的话,可以用这个指定)
    String constant() default "";
    
    //当源属性为null,则设置提供的默认值(字符串类型)
    String defaultValue() default "";
    
     // 生成的映射方法是否应忽略通过target指定的属性。
    boolean ignore() default false;
    
    // 属性从字符串映射到日期,则SimpleDataFormat可处理的格式字符串,反之亦然。
    String dateFormat() default "";

    // 属性从数字映射到字符串,或者从数字映射到字符串,则可由DecimalFormat处理的格式字符串
    String numberFormat() default "";
    
    // 要根据其设置指定目标属性的表达式字符串,可以是表达式
    String expression() default "";
    
	// 当指定的源属性为null时,将根据该表达式设置指定的目标属性
    String defaultExpression() default "";
  
	
    // 可以指定注解来帮助选择合适的映射规则
    Class<? extends Annotation>[] qualifiedBy() default {};
	
    // 基于字符串的限定符形式;当为给定属性寻找合适的映射方法时,只考虑那些直接或间接地(即在类级别上)为每个指定的限定符名称命名的注释的方法
    String[] qualifiedByName() default {};
    
	// 指定在多个映射方法符合条件时要使用的映射方法的结果类型
    Class<?> resultType() default void.class;
	
    // 映射属性所依赖的结果类型的一个或多个属性
    String[] dependsOn() default {};
	
    // 确定何时对bean映射的源属性值进行null检查,可被MapperConfig、BeanMapping或Mapping上的覆盖
    // ALWAYS:始终
    // ON_IMPLICIT_CONVERSION:在涉及隐式类型转换得时候进行检查
    NullValueCheckStrategy nullValueCheckStrategy() default NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;

    // 当源bean属性为null或不存在时应用的策略
    // IGNORE:忽略,源bean属性等于null,那么将忽略目标bean属性并保留其现有值
    // SET_TO_DEFAULT:源bean属性等于null,则目标bean属性将设置为其默认值
    // SET_TO_NULL:源bean属性等于null,则目标bean属性将设置为null,默认为SET_TO_NULL
    NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default NullValuePropertyMappingStrategy.SET_TO_NULL;
}

2.3.1 当属性字段不相同的时候的映射处理(source、target、constant、defaultValue、ignore属性的使用)

定义一个Log和一个LogDTO,这里Log的每个属性A,转换为LogDTO的属性ADTO,同理

(这里为使用constant属性,所以Log中多了一个address,LogDTO转换Log是没有对应属性的,这时候我们用Constant来指定值

(这里为使用defaultValue属性,所以Log中多了一个defaultValue,转换当源属性为null的时候,我们用defaultValue来指定默认值

(这里为使用ignore属性,所以Log中多了一个ignoreValue,转换时候忽略该值

@Getter
@Setter
@NoArgsConstructor
public class Log implements Serializable {

    private Long id;

    private String realName;
    
    private String name;
    
    // constant属性 测试新增
    private String address;
    
    // defaultValue属性 测试新增
    private String defaultValue;
    
    //ignore属性 测试新增
    private String ignoreValue;
}

@Getter
@Setter
public class LogDTO implements Serializable {

    private Long idDTO;

    private String realNameDTO;

    private String nameDTO;
    
    // defaultValue属性 测试新增,为了后面注解标注方便,这里将字段名称设置为一样
    private String defaultValue;
    
    //ignore属性 测试新增,这里将字段名称设置为一样
    private String ignoreValue;
}


// 如果下面的mapper有多个参数的时候,例如这里再定义一个Log2 entity
// 属性转换举例
@Getter
@Setter
public class Log2 implements Serializable {


    private Long id;

    private String realName;

    private String name;

    private String address;

}

那么如何编写Mapper?这里就用到 source 与 target属性了

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface LogMapper {


   @Mappings({
            @Mapping(source = "id", target = "idDTO"),
            @Mapping(source = "realName", target = "realNameDTO"),
            @Mapping(source = "name", target = "nameDTO"),
       		// 使用defaultValue
            @Mapping(target = "defaultValue", defaultValue = "morenzhi"),
       		// 使用ignore
            @Mapping(target = "ignoreValue", ignore = true)

    })
    LogDTO convertToDTO(Log log);


    @Mappings({
            @Mapping(source = "idDTO", target = "id"),
            @Mapping(source = "realNameDTO", target = "realName"),
            @Mapping(source = "nameDTO", target = "name"),
        	// 使用constant
            @Mapping(target = "address", constant = "1111"),
        	// 使用ignore
            @Mapping(target = "ignoreValue", ignore = true)
    })
    Log covertToDO(LogDTO logDTO);
    
    
    
	// 有多个参数的时候,我们可以通过  参数.属性 的方式进行转换
    @Mappings({
            @Mapping(source = "log.id", target = "idDTO"),
            @Mapping(source = "log2.realName", target = "realNameDTO"),
            @Mapping(source = "log.name", target = "nameDTO"),
    })
    LogDTO convertToDTO2(Log log , Log2 log2);

}

反编译后的代码,(如果不会反编译,可以查看另外一篇博客 JAVA 如何将class文件转换成java文件 )

  • 转换的时候是根据注解中标注的source与target标注的属性进行转换的
  • 在DTO转换为entity的时候,DTO无address,entity有address,设置了constant属性,其也是生效的
  • 在entity转换为DTO的时候,加了判断值是否未空,为空就设置为指定值的逻辑,defaultValue属性生效
  • 在DTO转换为entity、entity转换为DTO都对ignoreValue 加了ignore,所以没有其的转换逻辑,ignore属性生效
  • 多参数的时候,属性转换规则就符合我们要求的,通过参数名称.属性值的方式来进行属性的转换
package com.study.mapper;
import com.study.DO.Log;
import com.study.DTO.LogDTO;
public class LogMapperImpl implements LogMapper
{

    public LogMapperImpl(){
    }

 public LogDTO convertToDTO(Log log)
    {
        if(log == null)
            return null;
        LogDTO logDTO = new LogDTO();
        logDTO.setIdDTO(log.getId());
        logDTO.setNameDTO(log.getName());
        logDTO.setRealNameDTO(log.getRealName());
	    // 这里加了判断,如果默认值为空,就设置值为指定的默认值
        if(log.getDefaultValue() != null)
            logDTO.setDefaultValue(log.getDefaultValue());
        else
            logDTO.setDefaultValue("morenzhi");
        return logDTO;
    }

    public Log covertToDO(LogDTO logDTO){
        if(logDTO == null)
        {
            return null;
        } else{
            Log log = new Log();
            log.setName(logDTO.getNameDTO());
            log.setRealName(logDTO.getRealNameDTO());
            log.setId(logDTO.getIdDTO());
            log.setDefaultValue(logDTO.getDefaultValue());
            // 设置constant指定的值
            log.setAddress("1111");
            return log;
        }
    }
    // 可以看到这里的属性转换就根据我们自定义的属性转换一样
     public LogDTO convertToDTO2(Log log, Log2 log2){
        if(log == null && log2 == null)
            return null;
        LogDTO logDTO = new LogDTO();
        if(log != null){
            logDTO.setIdDTO(log.getId());
            logDTO.setNameDTO(log.getName());
        }
        if(log2 != null)
            logDTO.setRealNameDTO(log2.getRealName());
        return logDTO;
    }
}

使用的时候,查看一下,这些属性的生效:

定义的log对象:(defaultValue=null,ignoreValue=ignore)
log:Log(id=1, realName=hh, name=uu, address=addre, defaultValue=null, ignoreValue=ignore)

转换后的logDTO对象:(defaultValue=morenzhi, ignoreValue=null)
logDTO:LogDTO(idDTO=1, realNameDTO=hh, nameDTO=uu, defaultValue=morenzhi, ignoreValue=null)
    

定义的logDTO对象:(ignoreValue=ignore d)
logDTO:LogDTO(idDTO=2, realNameDTO=ll, nameDTO=vv, defaultValue=null, ignoreValue=ignore d)

转换后的log对象:(address=1111, ignoreValue=null)
log:Log(id=2, realName=ll, name=vv, address=1111, defaultValue=null, ignoreValue=null)

2.3.2 当属性字段类型不相同的时候的映射处理(dataformat、numberformat属性的使用)

当属性的类型不一致的时候,其实mapstruct会帮我们做一些自动类型转换,但是自动类型转换涉及到的类型有限,如下:

  • 基本类型及其包装类
  • 基本类型的包装类型和String类型之间
  • String类型和枚举类型之间
  • 自定义常量

(1)自动类型转换

Log2与LogDTO2的转换,字段名称都相同,来测试自定义类型转换的一些规则。定义了一个枚举类Type,来辅助测试

  • intNum:测试基本类型及其包装类的相互转换
  • integerNum:基本类型的包装类型和String类型之间相互转换
  • type:String类型和枚举类型之间相互转换
  • constant:String类型和枚举类型之间相互转换
@Getter
@Setter
public class Log2 implements Serializable {

    private Long id;

    private String realName;

    private String name;
	// 测试基本类型及其包装类的相互转换
    private int intNum;
	// 基本类型的包装类型和String类型之间相互转换
    private Integer integerNum;
	// String类型和枚举类型之间相互转换
    private Type type;
	// 自定义常量之间
    private final Integer constant= 1;

}

@Getter
@Setter
public class LogDTO2 implements Serializable {

    private Long id;

    private String realName;

    private String name;
	// 测试基本类型及其包装类的相互转换
    private Integer intNum;
	// 基本类型的包装类型和String类型之间相互转换
    private String integerNum;
	// String类型和枚举类型之间相互转换
    private String type;
	// 自定义常量之间
    private final Integer constant= 1;

}

@Getter
public enum Type {

    DEFAULT("h");

    String ms;

    Type(String ms){
        this.ms = ms;
    }

}

对应mapper的编写:因为字段相同,我们无需用到@Mapping,关于属性不同的,其自动进行类型转换即可

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface LogMapper {

    LogDTO2 convertToDTO2(Log2 log2);


    Log2 covertToDO2(LogDTO2 logDTO2);

}

反编译后的源码:

  • 基本类型与包装类型通过 **xxx.valueOf 与 xx.intValue()**来转换
  • 基本类型的包装类型和String类型之间,也是一样,先parse,然后 **xxx.valueOf 与 xx.intValue()**来转换
  • String类型和枚举类型之间:这里我们需要注意,枚举类自动类型转换,这里的Type转换得到的String,是DEFAULT,而不是里面的ms信息”h“
  • 自定义常量,这里其实好像没有转换,保留了之前定义的常量值
public class LogMapperImpl implements LogMapper{

    public LogMapperImpl() {
    }

    public LogDTO2 convertToDTO2(Log2 log2) {
        if(log2 == null)
            return null;
        LogDTO2 logDTO2 = new LogDTO2();
        logDTO2.setId(log2.getId());
        logDTO2.setRealName(log2.getRealName());
        logDTO2.setName(log2.getName());
        logDTO2.setIntNum(Integer.valueOf(log2.getIntNum()));
        if(log2.getIntegerNum() != null)
            logDTO2.setIntegerNum(String.valueOf(log2.getIntegerNum()));
        if(log2.getType() != null)
            logDTO2.setType(log2.getType().name());
        return logDTO2;
    }

    public Log2 covertToDO2(LogDTO2 logDTO2){
        if(logDTO2 == null)
            return null;
        Log2 log2 = new Log2();
        log2.setId(logDTO2.getId());
        log2.setRealName(logDTO2.getRealName());
        log2.setName(logDTO2.getName());
        if(logDTO2.getIntNum() != null)
            log2.setIntNum(logDTO2.getIntNum().intValue());
        if(logDTO2.getIntegerNum() != null)
            log2.setIntegerNum(Integer.valueOf(Integer.parseInt(logDTO2.getIntegerNum())));
        if(logDTO2.getType() != null)
            log2.setType((Type)Enum.valueOf(com/study/config/Type, logDTO2.getType()));
        return log2;
    }
}

(2)dataformat、numberformat属性的使用

还是Log与LogDTO的转换,涉及到数据类型不一样的时候,例如String转换为Date,String转换为Number

Log与LogDTO,新增两个字段,来使用 dataformat、numberformat

@Getter
@Setter
public class Log implements Serializable {
    private Long id;

    private String realName;

    private String name;
	// 这里保存String,测试DateFormat
    private String dateStr;
	// 这里保存String,测试numberFormat
    private String numberStr;
}
@Getter
@Setter
public class LogDTO implements Serializable {

    private Long idDTO;

    private String realNameDTO;

    private String nameDTO;
	// 这里保存Date,测试DateFormat
    private Date date;
	// 这里保存Double,测试numberFormat
    private Double number;

}

对应mapper的编写,LogMapper

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface LogMapper {

    @Mappings({
            @Mapping(source = "id", target = "idDTO"),
            @Mapping(source = "realName", target = "realNameDTO"),
            @Mapping(source = "name", target = "nameDTO"),
        	// dateFormat :符合SimpleDataFormat的处理格式
            @Mapping(source = "dateStr", target = "date", dateFormat = "yyyy-MM-dd yy:mm:ss"),
        	// numberFormat:符合DecimalFormat的处理格式
            @Mapping(source = "numberStr" , target = "number" , numberFormat = "$#.00")
    })
    LogDTO convertToDTO(Log log);

}

反编译后的源码:在类型转换的时候,会进行对应的parse解析

public class LogMapperImpl implements LogMapper{

    public LogMapperImpl()
    {
    }

    public LogDTO convertToDTO(Log log){
        if(log == null)
            return null;
        LogDTO logDTO = new LogDTO();
        try
        {
            if(log.getDateStr() != null)
                logDTO.setDate((new SimpleDateFormat("yyyy-MM-dd yy:mm:ss")).parse(log.getDateStr()));
        }
        catch(ParseException e){
            throw new RuntimeException(e);
        }
        try{
            if(log.getNumberStr() != null)
                logDTO.setNumber(Double.valueOf((new DecimalFormat("$#.00")).parse(log.getNumberStr()).doubleValue()));
        }
        catch(ParseException e)
        {
            throw new RuntimeException(e);
        }
        logDTO.setIdDTO(log.getId());
        logDTO.setNameDTO(log.getName());
        logDTO.setRealNameDTO(log.getRealName());
        return logDTO;
    }

}

在我们平常的工程中,掌握着几个属性其实已经够用了~

下面根据更复杂的一些需求来做个例子学习

2.3.3 高级属性的使用

(1)expression与defaultExpression的使用

在一些应用场景我们需要使用一些工具类来完成一些字段的转换,我们如何在@Mapping中来使用

举个例子:我们有一个日期工具类DateUtil,来统一完成从String到LocalDateTime 的转换,那么我们如果想使用这个工具类,我们就需要使用expression表达式来完成了

现在有 LogExpression与LogExpressionDTO

  • createTime:我们需要使用统一工具类的方法将str转换为DTO中的LocalDateTime类型的createTimeDTO。使用 expression
  • updateTime:当LogExpression中的updateTime为空的时候,我们用指定的 defaultExpression 来获取对应值
@Getter
@Setter
@ToString
public class LogExpression {

    // 使用 expression 转换
    private String createTime;

    // 使用 defaultExpression 来进行转换
    private String updateTime;
}

@Getter
@Setter
@ToString
public class LogExpressionDTO {

    private LocalDateTime createTimeDTO;


    private LocalDateTime updateTimeDTO;
}

我们定义了一个日期转换的工具类DateUtil,在com.study.util包下,里面有一个str到LocalDateTime 的转换方法strToDate方法

package com.study.util;

import org.apache.commons.lang3.StringUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateUtil {
    /**
     * 完成str到LocalDateTime 的转换
     *
     * @param str 符合要求的字符串
     * @return 返回LocalDateTime对象
     */
    public static LocalDateTime strToDate(String str) {
        if (StringUtils.isBlank(str)) {
            return LocalDateTime.now();
        }
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss");
        return LocalDateTime.parse(str, df);
    }
}

LogMapper的编写:我们针对LogExpression转换为LogExpressionDTO为例

  • expression表达式的编写知识需要掌握
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface LogMapper {
    @Mappings({
        	// 不指定源属性,目标转换的值根据expression计算得到的值来设置
        	// com.study.util.DateUtil 这个类的路径
        	// strToDate(String str) 这个类的方法,logExpression.getCreateTime() 为传入的参数
            @Mapping(target = "createTimeDTO", expression = "java(com.study.util.DateUtil.strToDate(logExpression.getCreateTime()))"),
        	// 如果 源属性updateTime为空的话,用默认的defaultExpression来设置目标属性updateTimeDTO的值
            @Mapping(target = "updateTimeDTO", source = "updateTime" , defaultExpression = "java(com.study.util.DateUtil.strToDate(logExpression.getUpdateTime()))")
    })
    LogExpressionDTO convertToDTO3(LogExpression logExpression);
}

反编译后的代码:

  • createTimeDTO:很明显,这个属性的值直接就是调用我们指定类的指定方法来进行值得设置
  • updateTimeDTO:使用的是 defaultExpression这个属性,当源属性为空的时候,根据这个进行目标值的设置,与defaultValue属性不同,因为defaultValue特指String类型,所以我们设置其他类型的字段的时候,就需要defaultExpression
public class LogMapperImpl implements LogMapper{

    public LogMapperImpl(){
    }

    public LogExpressionDTO convertToDTO3(LogExpression logExpression){
        if(logExpression == null)
            return null;
        LogExpressionDTO logExpressionDTO = new LogExpressionDTO();
        // 判断源属性的值是否为空,如果为空则调用方法来设置
        if(logExpression.getUpdateTime() != null)
            logExpressionDTO.setUpdateTimeDTO(LocalDateTime.parse(logExpression.getUpdateTime()));
        else
            logExpressionDTO.setUpdateTimeDTO(DateUtil.strToDate(logExpression.getUpdateTime()));
        // 根据调用方法来获取值
        logExpressionDTO.setCreateTimeDTO(DateUtil.strToDate(logExpression.getCreateTime()));
        return logExpressionDTO;
    }
}

(2)@Mapper import属性与Mapping的结合使用

针对(1)的应用场景,我们可以有另外的实现方法,就是借助@Mapper的一些属性设置来完成同样的功能

LogExpression、LogExpressionDTO、DateUtil还是使用上面的

针对LogMapper2,我们使用@Mapper与@Mapping结合的方式来实现

package com.study.mapper;
import com.study.DO.LogExpression;
import com.study.DTO.LogExpressionDTO;
import com.study.util.DateUtil;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.ReportingPolicy;
import java.time.LocalDateTime;
// (1)借助imports属性,将所用到的包导入进来
// 这里导入了我们自定义的com.study.util包下的DateUtil类  和 java.time包下的 LocalDateTime 类
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE , imports = {DateUtil.class , LocalDateTime.class})
public interface LogMapper2 {


    @Mappings({
        	// (2)在这里使用自定义类的时候,就可以直接 类名称.方法名称(方法参数)的形式来调用对应的方法
            @Mapping(target = "createTimeDTO", expression = "java(DateUtil.strToDate(logExpression.getCreateTime()))"),
            @Mapping(target = "updateTimeDTO", source = "updateTime" , defaultExpression = "java(DateUtil.strToDate(logExpression.getUpdateTime()))")
    })
    LogExpressionDTO convertToDTO3(LogExpression logExpression);


    @Mappings({
        	// (2)同理,我们导入的java.time包下的LocalDateTime类的方法也可以这么调用
            @Mapping(target = "createTimeDTO", expression = "java(LocalDateTime.now())"),
            @Mapping(target = "updateTimeDTO", source = "updateTime" , defaultExpression = "java(LocalDateTime.now())")
    })
    LogExpressionDTO convertToDTO4(LogExpression logExpression);
}

反编译后的代码:

package com.study.mapper;
import com.study.DO.LogExpression;
import com.study.DTO.LogExpressionDTO;
import com.study.util.DateUtil;
import java.time.LocalDateTime;


public class LogMapper2Impl implements LogMapper2{

    public LogMapper2Impl(){
    }
	//这个方法跟上面是一样的,功能实现一致
    public LogExpressionDTO convertToDTO3(LogExpression logExpression){
        if(logExpression == null)
            return null;
        LogExpressionDTO logExpressionDTO = new LogExpressionDTO();
        if(logExpression.getUpdateTime() != null)
            logExpressionDTO.setUpdateTimeDTO(LocalDateTime.parse(logExpression.getUpdateTime()));
        else
            logExpressionDTO.setUpdateTimeDTO(DateUtil.strToDate(logExpression.getUpdateTime()));
        logExpressionDTO.setCreateTimeDTO(DateUtil.strToDate(logExpression.getCreateTime()));
        return logExpressionDTO;
    }
	// 这里也能成功调用LocalDateTime的方法
    public LogExpressionDTO convertToDTO4(LogExpression logExpression)
    {
        if(logExpression == null)
            return null;
        LogExpressionDTO logExpressionDTO = new LogExpressionDTO();
        if(logExpression.getUpdateTime() != null)
            logExpressionDTO.setUpdateTimeDTO(LocalDateTime.parse(logExpression.getUpdateTime()));
        else
            logExpressionDTO.setUpdateTimeDTO(LocalDateTime.now());
        logExpressionDTO.setCreateTimeDTO(LocalDateTime.now());
        return logExpressionDTO;
    }
}

(3)@Mapper use属性与Mapping的结合使用

这里,涉及到如果一个entity类有另外一个entity作为成员变量的时候,那么我们如何来进行转换呢?

例如,我们有一些预备环境,如下:

  • Log3 entity 与 传输对象 Log3DTO
  • Log3Inner eneity 与传输对象 Log3InnerDTO
  • Log3 entity 里面有一个Log3Inner类型的成员变量,对应的 Log3DTO有一个Log3InnerDTO类型的成员变量
@Getter
@Setter
@ToString
public class Log3 implements Serializable {


    private Long id;

    private String name;

    private Log3Inner log3Inner;
}

@Getter
@Setter
@ToString
public class Log3DTO implements Serializable {


    private Long id;

    private String name;
	// 注意,这两个属性的名称要一样,不然如果想用自动转换,是转换不过来的,那时候就必须用source与target来指定了
    private Log3InnerDTO log3Inner;

}
@Getter
@Setter
@ToString
public class Log3Inner {

    private Long id;

    private String realName;

    private String name;

    private String address;

    private String defaultValue;

    private String ignoreValue;
}

@Getter
@Setter
@ToString
public class Log3InnerDTO {

    private Long id;

    private String realName;

    private String name;

    private String address;

    private String defaultValue;

    private String ignoreValue;
}

针对Log3Inner与Log3InnerDTO的属性转变,我们也已经写好了,如下:

  • 这里我们写了一个统一的BaseMapper来完成相互转换
  • Log3InnerMapper 继承这个BaseMapper即可,就相应的完成了相互转换
package com.study.mapper;
import java.util.List;
public interface BaseMapper<D, E> {

    /**
     * DTO转Entity
     * @param dto /
     * @return /
     */
    E toEntity(D dto);

    /**
     * Entity转DTO
     * @param entity /
     * @return /
     */
    D toDto(E entity);

    /**
     * DTO集合转Entity集合
     * @param dtoList /
     * @return /
     */
    List<E> toEntity(List<D> dtoList);

    /**
     * Entity集合转DTO集合
     * @param entityList /
     * @return /
     */
    List <D> toDto(List<E> entityList);
}

package com.study.mapper;
import com.study.DO.Log3Inner;
import com.study.DTO.Log3InnerDTO;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface Log3InnerMapper extends BaseMapper<Log3InnerDTO, Log3Inner>{

}

那么我们如何来写Log3Mapper呢?

(1)第一种:我们直接像Log3InnerMapper继承BaseMapper行不行

可以!看下面

  • 这里会自动生成内部的转换方法来进行该字段的相互转换***(注意:这里的前提是这两个类中的这个字段的名称是相同的,不然是生成不了自动转换的相关方法的)***
// 我们编写的代码
@Mapper(componentModel = "spring",  unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface Log3Mapper extends BaseMapper<Log3DTO, Log3> {

}

// 反编译后的源码
// 根本没有
public class Log3MapperImpl implements Log3Mapper{

    public Log3MapperImpl(){
    }

     public Log3 toEntity(Log3DTO dto){
        if(dto == null){
            return null;
        } else{
            Log3 log3 = new Log3();
            log3.setId(dto.getId());
            log3.setName(dto.getName());
            log3.setLog3Inner(log3InnerDTOToLog3Inner(dto.getLog3Inner()));
            return log3;
        }
    }

    public Log3DTO toDto(Log3 entity){
        if(entity == null){
            return null;
        } else{
            Log3DTO log3DTO = new Log3DTO();
            log3DTO.setId(entity.getId());
            log3DTO.setName(entity.getName());
            log3DTO.setLog3Inner(log3InnerToLog3InnerDTO(entity.getLog3Inner()));
            return log3DTO;
        }
    }

    public List toEntity(List dtoList){
        if(dtoList == null)
            return null;
        List list = new ArrayList(dtoList.size());
        Log3DTO log3DTO;
        for(Iterator iterator = dtoList.iterator(); iterator.hasNext(); list.add(toEntity(log3DTO)))
            log3DTO = (Log3DTO)iterator.next();

        return list;
    }

    public List toDto(List entityList)
    {
        if(entityList == null)
            return null;
        List list = new ArrayList(entityList.size());
        Log3 log3;
        for(Iterator iterator = entityList.iterator(); iterator.hasNext(); list.add(toDto(log3)))
            log3 = (Log3)iterator.next();

        return list;
    }

    protected Log3Inner log3InnerDTOToLog3Inner(Log3InnerDTO log3InnerDTO){
        if(log3InnerDTO == null)
        {
            return null;
        } else
        {
            Log3Inner log3Inner = new Log3Inner();
            log3Inner.setId(log3InnerDTO.getId());
            log3Inner.setRealName(log3InnerDTO.getRealName());
            log3Inner.setName(log3InnerDTO.getName());
            log3Inner.setAddress(log3InnerDTO.getAddress());
            return log3Inner;
        }
    }

    protected Log3InnerDTO log3InnerToLog3InnerDTO(Log3Inner log3Inner){
        if(log3Inner == null)
        {
            return null;
        } else
        {
            Log3InnerDTO log3InnerDTO = new Log3InnerDTO();
            log3InnerDTO.setId(log3Inner.getId());
            log3InnerDTO.setRealName(log3Inner.getRealName());
            log3InnerDTO.setName(log3Inner.getName());
            log3InnerDTO.setAddress(log3Inner.getAddress());
            return log3InnerDTO;
        }
    }

    public volatile Object toDto(Object obj)
    {
        return toDto((Log3)obj);
    }

    public volatile Object toEntity(Object obj)
    {
        return toEntity((Log3DTO)obj);
    }
}

注意:这种情况虽然可以仅限当Log3Inner与Log3InnerDTO字段全部都相同的情况下,如果字段都不相同,那生成的对应方法通过set赋值的时候就会出现问题

protected Log3Inner log3InnerDTOToLog3Inner(Log3InnerDTO log3InnerDTO){
    if(log3InnerDTO == null){
        return null;
    } else{
        Log3Inner log3Inner = new Log3Inner();
        return log3Inner;
    }
}

protected Log3InnerDTO log3InnerToLog3InnerDTO(Log3Inner log3Inner){
    if(log3Inner == null){
        return null;
    } else{
        Log3InnerDTO log3InnerDTO = new Log3InnerDTO();
        return log3InnerDTO;
    }
}

(2)第种:我们借助 @Mapper 的use属性与Mapping结合来实现行不行???

可以!!!,看下面

  • 这个就直接用通过use属性引入的Mapper中的方法来实现属性的相互转换

这里就不会存在第一种方法的那个限制,如果出现转换失败的问题,那就去看Log3InnerMapper是否能够正常转换

// 我们编写的代码
@Mapper(componentModel = "spring", uses = {Log3InnerMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface Log3Mapper extends BaseMapper<Log3DTO, Log3> {

}

// 反编译后的代码
public class Log3MapperImpl implements Log3Mapper{

    public Log3MapperImpl()
    {
    }

    public Log3 toEntity(Log3DTO dto)
    {
        if(dto == null)
        {
            return null;
        } else
        {
            Log3 log3 = new Log3();
            log3.setId(dto.getId());
            log3.setName(dto.getName());
             // 调用mapper中的相应方法来实现转换
            log3.setLog3Inner((Log3Inner)log3InnerMapper.toEntity(dto.getLog3Inner()));
            return log3;
        }
    }

    public Log3DTO toDto(Log3 entity){
        if(entity == null)
        {
            return null;
        } else
        {
            Log3DTO log3DTO = new Log3DTO();
            log3DTO.setId(entity.getId());
            log3DTO.setName(entity.getName());
            // 调用mapper中的相应方法来实现转换
            log3DTO.setLog3Inner((Log3InnerDTO)log3InnerMapper.toDto(entity.getLog3Inner()));
            return log3DTO;
        }
    }

    public List toEntity(List dtoList){
        if(dtoList == null)
            return null;
        List list = new ArrayList(dtoList.size());
        Log3DTO log3DTO;
        for(Iterator iterator = dtoList.iterator(); iterator.hasNext(); list.add(toEntity(log3DTO)))
            log3DTO = (Log3DTO)iterator.next();

        return list;
    }

    public List toDto(List entityList){
        if(entityList == null)
            return null;
        List list = new ArrayList(entityList.size());
        Log3 log3;
        for(Iterator iterator = entityList.iterator(); iterator.hasNext(); list.add(toDto(log3)))
            log3 = (Log3)iterator.next();

        return list;
    }

    public volatile Object toDto(Object obj)
    {
        return toDto((Log3)obj);
    }

    public volatile Object toEntity(Object obj)
    {
        return toEntity((Log3DTO)obj);
    }

    private Log3InnerMapper log3InnerMapper;
}

(3)第三种:我们自己通过sourse、target来实现几个方法行不行??

可以!!!,看下面

  • 跟第一种一样,这里也会自动生成内部的转换方法来进行该字段的相互转换
// 自己编写的代码
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface Log3InnerMapper {
	// 如果字段都相同,这里可以不同指定
    @Mappings({
            @Mapping(source = "id", target = "idDTO"),
            @Mapping(source = "realName", target = "realNameDTO"),
            @Mapping(source = "name", target = "nameDTO"),
            @Mapping(source = "address", target = "addressDTO"),
    })
    Log3InnerDTO convertToDTO(Log3Inner log3Inner);


    @Mappings({
            @Mapping(source = "idDTO", target = "id"),
            @Mapping(source = "realNameDTO", target = "realName"),
            @Mapping(source = "nameDTO", target = "name"),
            @Mapping(source = "addressDTO", target = "address")
    })
    Log3Inner covertToDO(Log3InnerDTO log3InnerDTO);
}

// 反编译后得到的代码
public class Log3MapperImpl implements Log3Mapper{

    public Log3MapperImpl()
    {
    }

    public Log3DTO convertToDTO(Log3 log3)
    {
        if(log3 == null)
        {
            return null;
        } else
        {
            Log3DTO log3DTO = new Log3DTO();
            log3DTO.setLog3Inner(log3InnerToLog3InnerDTO(log3.getLog3Inner()));
            log3DTO.setName(log3.getName());
            log3DTO.setId(log3.getId());
            return log3DTO;
        }
    }

    public Log3 covertToDO(Log3DTO log3DTO)
    {
        if(log3DTO == null)
        {
            return null;
        } else
        {
            Log3 log3 = new Log3();
            log3.setLog3Inner(log3InnerDTOToLog3Inner(log3DTO.getLog3Inner()));
            log3.setName(log3DTO.getName());
            log3.setId(log3DTO.getId());
            return log3;
        }
    }

    protected Log3InnerDTO log3InnerToLog3InnerDTO(Log3Inner log3Inner)
    {
        if(log3Inner == null)
        {
            return null;
        } else
        {
            Log3InnerDTO log3InnerDTO = new Log3InnerDTO();
            log3InnerDTO.setId(log3Inner.getId());
            log3InnerDTO.setRealName(log3Inner.getRealName());
            log3InnerDTO.setName(log3Inner.getName());
            log3InnerDTO.setAddress(log3Inner.getAddress());
            return log3InnerDTO;
        }
    }

    protected Log3Inner log3InnerDTOToLog3Inner(Log3InnerDTO log3InnerDTO)
    {
        if(log3InnerDTO == null)
        {
            return null;
        } else
        {
            Log3Inner log3Inner = new Log3Inner();
            log3Inner.setId(log3InnerDTO.getId());
            log3Inner.setRealName(log3InnerDTO.getRealName());
            log3Inner.setName(log3InnerDTO.getName());
            log3Inner.setAddress(log3InnerDTO.getAddress());
            return log3Inner;
        }
    }
}

注意:虽然原理跟第一种方法很类似,当字段名称都相同的时候,其可以自动帮我们进行类型的转换,但是如果字段名称都不相同,第一种方法是没有什么补救措施的,这个可以进行指定,可修改下就很大*

例如:这时候,我们将Log3InnerDTO的字段改的与Log3Inner不相同,都加一个后缀,这时候如果直接第一种方式,肯定是不行的(可以试试)

如果采用这个第三种方式,如果你没有特殊指明source与target的话,其实也是不行的,但是指明的话,就可以

@Getter
@Setter
@ToString
public class Log3InnerDTO {

    private Long idDTO;

    private String realNameDTO;

    private String nameDTO;

    private String addressDTO;

}


@Mapper(componentModel = "spring",  unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface Log3Mapper   {

    @Mappings({
            @Mapping(source = "id", target = "id"),
            @Mapping(source = "name", target = "name"),
        	// source为参数名称.属性名称.属性名称      ,target为属性名称.属性名称
            @Mapping(source = "log3.log3Inner.id", target = "log3Inner.idDTO"),
            @Mapping(source = "log3.log3Inner.realName", target = "log3Inner.realNameDTO"),
            @Mapping(source = "log3.log3Inner.name", target = "log3Inner.nameDTO"),
            @Mapping(source = "log3.log3Inner.address", target = "log3Inner.addressDTO")

    })
    Log3DTO convertToDTO(Log3 log3);


    @Mappings({
            @Mapping(source = "id", target = "id"),
            @Mapping(source = "name", target = "name"),
            @Mapping(source = "log3DTO.log3Inner.idDTO", target = "log3Inner.id"),
            @Mapping(source = "log3DTO.log3Inner.realNameDTO", target = "log3Inner.realName"),
            @Mapping(source = "log3DTO.log3Inner.nameDTO", target = "log3Inner.name"),
            @Mapping(source = "log3DTO.log3Inner.addressDTO", target = "log3Inner.address")
    })
    Log3 covertToDO(Log3DTO log3DTO);

}

反编译后的自动生成的那两个方法的代码就变成了:(可以对比一下第一种的相应代码)

这时候它就可以根据source和target的属性来进行属性值的转换。

protected Log3InnerDTO log3InnerToLog3InnerDTO(Log3Inner log3Inner){
    if(log3Inner == null){
        return null;
    } else{
        Log3InnerDTO log3InnerDTO = new Log3InnerDTO();
        log3InnerDTO.setAddressDTO(log3Inner.getAddress());
        log3InnerDTO.setIdDTO(log3Inner.getId());
        log3InnerDTO.setRealNameDTO(log3Inner.getRealName());
        log3InnerDTO.setNameDTO(log3Inner.getName());
        return log3InnerDTO;
    }
}

protected Log3Inner log3InnerDTOToLog3Inner(Log3InnerDTO log3InnerDTO){
    if(log3InnerDTO == null){
        return null;
    } else{
        Log3Inner log3Inner = new Log3Inner();
        log3Inner.setId(log3InnerDTO.getIdDTO());
        log3Inner.setAddress(log3InnerDTO.getAddressDTO());
        log3Inner.setRealName(log3InnerDTO.getRealNameDTO());
        log3Inner.setName(log3InnerDTO.getNameDTO());
        return log3Inner;
    }
}

2.4 MapMapping

  • 标注在方法上的注解
  • 标注参数为Map类的时候的方法
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface MapMapping {
    // key 的 dateFormat
    String keyDateFormat() default "";
	// value 的 dateFormat
    String valueDateFormat() default "";
	// key 的 NumberFormat
    String keyNumberFormat() default "";
	// value 的 NumberFormat
    String valueNumberFormat() default "";

    //可以指定键值限定符来帮助选择合适的映射器
    Class<? extends Annotation>[] keyQualifiedBy() default {};

    // 上面是根据类,这个是根据名称
    String[] keyQualifiedByName() default {};

    // 同理 key
    Class<? extends Annotation>[] valueQualifiedBy() default {};

    String[] valueQualifiedByName() default {};

	// 指定在多个映射方法符合条件的情况下,映射方法的结果中要使用的键的类型。    
    Class<?> keyTargetType() default void.class;
	//指定在多个映射方法符合条件的情况下,映射方法的结果中要使用的value的类型。
    Class<?> valueTargetType() default void.class;
	// 参照@Mapping
    NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
}

使用:具体其他的使用,看@Mapping,大同小异

@Mapper
 public interface SimpleMapper {
       @MapMapping(valueDateFormat = "dd.MM.yyyy")
       Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
 }

2.5 Mappings

用在方法上的 mapping 的组合注解

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD})
public @interface Mappings {
    Mapping[] value();
}

具体使用:

 @Mappings({
            @Mapping(source = "id", target = "idDTO"),
            @Mapping(source = "realName", target = "realNameDTO"),
            @Mapping(source = "name", target = "nameDTO"),
    })
LogDTO convertToDTO(Log log);

总结

lombok 包 与 mapstruct 包 两个包一起用,我们可以省去很多代码的编写

虽然降低了一些可读性,但是方便了后面的维护工作,各有优劣,自己选择

题外话

小潘的个人微信公众号【小潘学程序】,有兴趣可给个关注~

一起学习,一起成长

相关文章

暂无评论

暂无评论...