属性映射工具——MapStruct

Posted ddgougou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了属性映射工具——MapStruct相关的知识,希望对你有一定的参考价值。

一、背景

  按照日常开发习惯,在现在多模块多层级的项目中,应用于应用之间,模块于模块之间数据模型一般都不通用,每层都有自己的数据模型。对于不同领域层使用不同JavaBean对象传输数据,避免相互影响。比如传输对象DTO、业务普通封装对象BO、数据库映射对象DO等。于是在不同层之间进行数据传输时,不可避免地需要将这些对象的属性进行互相转换操作。

  常见的转换方式有:

    • 调用getter/setter方法进行属性赋值:一大堆‘巨简单’的代码,不美观;
    • 调用BeanUtil.copyPropertie进行反射属性赋值:坑巨多,比如sources与target写反,难以定位某个字段在哪里进行的赋值,不利于debug,同时因为用到反射,导致性能也不佳。 

  而本文介绍的MapStruct规避了上述的缺点。

 

二、简介

  MapStruct是一个代码生成器,它基于约定优于配置的方法极大地简化了Java bean类型之间映射的实现。

  通过上面的介绍我们应该能够理解到这么几点,首先它是一个代码生成器,就是用来帮开发者自动生成代码的工具,只需要通过简单的代码就可以实现原来手工编写的样板代码,因为它采用约定大于配置的设计思想,所以开发者只需要掌握简单的代码编写就可以了。也就是说人家框架帮你自动生成了原先手工编写的代码,但实际上那些手工编写的代码还是存在的,只不过你没有编写,框架帮你自动生成了而已。这其实也回到框架的本质,事情还是那些事,就看你来做,还是它来做,它如果多做,你就少做,甚至可以不做。这里提到的它指的是各种框架,它的本质就是帮开发者做了一些事情。

    优点:

      • 通过使用普通方法调用而不是反射来快速执行
      • 速度快:由于MapStruct不采用所谓的反射机制,而是像开发者原来手工逐个赋值那样编码,所以没有额外的性能损失,跟你自己写的代码是一样的。
      • 编译时类型安全性
      • 展示生成报告:在生成代码过程中如果发现映射不完整、不正确会立即输出日志。

  工作原理(使用java apt技术,该技术也用于lombok的实现)

    1. 在代码编译时会触发MapStruct插件运行
    2. 当MapStruct运起来之后会扫描它自己特定注解的类
    3. 解析类中的方法按照自己的策略在项目编译目录(build)下生成实现类,如果生成过程中出现异常则会输出日志,并中断当前整个项目编译工作。

 

三、简单实践

    项目背景:Spring Boot+Maven项目,UserDAO——数据库映射对象,UserDTO——数据传输对象,

  3.1依赖

    maven项目 pom.xml    

<dependencies>
  <!--MapStruct-->
  <dependency>
    <groupId>org.mapstruct</groupId>
       <artifactId>mapstruct-jdk8</artifactId>
       <version>1.2.0.Final</version>
    </dependency>
    <dependency>
       <groupId>org.mapstruct</groupId>
       <artifactId>mapstruct-processor</artifactId>
       <version>1.2.0.Final</version>
    </dependency>

    <!--lombok-->
    <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.10</version>
       <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
       </plugin>
       <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.2.0.Final</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

gradle项目  

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

    // If you are using mapstruct in test code
    testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
}

   3.2 UserDAO 

import lombok.Data;
import java.sql.Timestamp;

@Data
public class UserDAO {

    // 主键
    private Long id;

    // 姓名
    private String name;

    // 性别
    private Integer sex;

    // 描述
    private String remark;

    // 创建时间
    private Timestamp createTime;
}

   3.3 UserDTO 

import com.gougou.mapstruct.enums.SexEnum;
import lombok.Data;
import java.io.Serializable;

/**
 * dto:网络传输对象
 */
@Data
public class UserDTO implements Serializable {

    private static final long serialVersionUID = -2767215193284523251L;

    // 主键
    private String id;

    // 姓名
    private String name;

    // 性别
    private SexEnum sex;

    // 描述
    private String desc;

    // 创建时间
    private String createTime;
}

   3.4 SexEnum

import lombok.Getter;
import lombok.Setter;

public enum SexEnum {

    man(1, "男"),
    woman(0, "女");

    @Setter
    @Getter
    private Integer code;

    @Setter
    @Getter
    private String name;

    SexEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    public static SexEnum of(Integer code){
        for(SexEnum sexEnum:SexEnum.values()){
            if(sexEnum.code.equals(code)){
                return sexEnum;
            }
        }
        return null;
    }
}

   3.5 transfer

  Mapper 即映射器, 一般来说就是写 xxxMapper 接口。 当然, 不一定是以 Mapper 结尾的。 只是官方是这么写的。

  简单的映射(字段和类型都匹配), 只有一个要求, 在接口上写 @Mapper 注解即可。 然后方法上入参对应要被转化的对象, 返回值对应转化后的对象, 方法名称可任意。在实现类的时候, 如果属性名称相同, 则会进行对应的转化(隐式转化)。属性名不相同, 可通过 @Mapping 注解进行指定转化。否则没有值。

import com.gougou.mapstruct.dao.UserDAO;
import com.gougou.mapstruct.dto.UserDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.util.List;

/**
 * UserDTO与UserDAO之间的转化类
 */
@Mapper(uses = {
        SexEnumIntegerMapper.class,
        StringTimestampMapper.class
})
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mappings({
            @Mapping(source = "sex", target = "sex",qualifiedByName = {"SexEnumIntegerMapper", "integerBySexEnum"}),
            @Mapping(source = "desc", target = "remark"),
            @Mapping(source = "createTime", target = "createTime",qualifiedByName = {"StringTimestampMapper", "timestampByString"})
    })
    UserDAO toDO(UserDTO userDTO);

    List<UserDAO> toDOs(List<UserDTO> userDTOList);

    @Mappings({
            @Mapping(source = "sex", target = "sex",qualifiedByName = {"SexEnumIntegerMapper", "sexEnumByInteger"}),
            @Mapping(source = "remark", target = "desc"),
            @Mapping(source = "createTime", target = "createTime",qualifiedByName = {"StringTimestampMapper", "stringByTimestamp"})
    })
    UserDTO toDTO(UserDAO userDAO);

    List<UserDTO> toDTOs(List<UserDAO> userDAOList);
}
import com.gougou.mapstruct.enums.SexEnum;
import org.mapstruct.Named;

/**
 * SexEnum与Integer之间的转化
 */
@Named("SexEnumIntegerMapper")
public class SexEnumIntegerMapper {

    @Named("sexEnumByInteger")
    public SexEnum sexEnumByInteger(Integer intParam){
        return SexEnum.of(intParam);
    }

    @Named("integerBySexEnum")
    public Integer integerBySexEnum(SexEnum sexEnum){
        return sexEnum.getCode();
    }
}
import org.mapstruct.Named;

import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * String与timestamp之间的转化
 */
@Named("StringTimestampMapper")
public class StringTimestampMapper {

    private static final String dateFormatStr = "yyyy-MM-dd HH:mm:ss";

    @Named("timestampByString")
    public Timestamp timestampByString(String strParam) {
        SimpleDateFormat sf = new SimpleDateFormat(dateFormatStr);
        java.util.Date date = null;
        try {
            date = sf.parse(strParam);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return new java.sql.Timestamp(date.getTime());
    }

    @Named("stringByTimestamp")
    public String stringByTimestamp(Timestamp timestamp) {
        DateFormat df = new SimpleDateFormat(dateFormatStr);
        return df.format(timestamp);
    }
}

   3.6 测试

import com.gougou.mapstruct.dao.UserDAO;
import com.gougou.mapstruct.dto.UserDTO;
import com.gougou.mapstruct.enums.SexEnum;
import com.gougou.mapstruct.transfer.UserMapper;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

public class MainTest {

    private UserDTO userDTO = new UserDTO();

    private List<UserDTO> userDTOList = new ArrayList<>(2);

    private UserDAO userDAO = new UserDAO();

    private List<UserDAO> userDAOList = new ArrayList<>(2);

    @Before
    public void initUserDTO() {
        userDTO.setId("1122");
        userDTO.setDesc("这是张三");
        userDTO.setName("张三");
        userDTO.setSex(SexEnum.man);
        userDTO.setCreateTime("2020-05-06 19:00:00");

        userDAO.setId(3377L);
        userDAO.setRemark("这是李梅梅");
        userDAO.setName("李梅梅");
        userDAO.setSex(0);
        userDAO.setCreateTime(new java.sql.Timestamp(1588765009399L));

        UserDTO userDTO2 = new UserDTO();
        userDTO2.setId("2211");
        userDTO2.setDesc("这是张三2");
        userDTO2.setName("张三2");
        userDTO2.setSex(SexEnum.man);
        userDTO2.setCreateTime("2020-05-06 19:49:00");

        UserDAO userDAO2 = new UserDAO();
        userDAO2.setId(7733L);
        userDAO2.setRemark("这是李梅梅2");
        userDAO2.setName("李梅梅2");
        userDAO2.setSex(0);
        userDAO2.setCreateTime(new java.sql.Timestamp(1588766094618L));

        userDAOList.add(userDAO);
        userDAOList.add(userDAO2);

        userDTOList.add(userDTO);
        userDTOList.add(userDTO2);
    }

    /**
     * DAO -> DTO
     */
    @Test
    public void test1() {
        UserDTO userDTO1 = UserMapper.INSTANCE.toDTO(userDAO);
        // UserDTO(id=3377, name=李梅梅, sex=woman, desc=这是李梅梅, createTime=2020-05-06 19:36:49)
        System.out.println(userDTO1.toString());
    }

    /**
     * DTO -> DAO
     */
    @Test
    public void test2() {
        UserDAO userDAO1 = UserMapper.INSTANCE.toDO(userDTO);
        // UserDAO(id=1122, name=张三, sex=1, remark=这是张三, createTime=2020-05-06 19:00:00.0)
        System.out.println(userDAO1.toString());
    }

    /**
     * List<DAO> -> List<DTO>
     */
    @Test
    public void test3() {
        List<UserDTO> userDTOList1 = UserMapper.INSTANCE.toDTOs(userDAOList);
        /**
         * UserDTO(id=3377, name=李梅梅, sex=woman, desc=这是李梅梅, createTime=2020-05-06 19:36:49)
         * UserDTO(id=7733, name=李梅梅2, sex=woman, desc=这是李梅梅2, createTime=2020-05-06 19:54:54)
         */
        userDTOList1.stream().forEach(x -> System.out.println(x));
    }

    /**
     * List<DTO> -> List<DAO>
     */
    @Test
    public void test4() {
        List<UserDAO> userDAOList1 = UserMapper.INSTANCE.toDOs(userDTOList);
        /**
         * UserDAO(id=1122, name=张三, sex=1, remark=这是张三, createTime=2020-05-06 19:00:00.0)——————这里的格式是TimeStamp的toString方法默认的实现,与本次转换无关
         * UserDAO(id=2211, name=张三2, sex=1, remark=这是张三2, createTime=2020-05-06 19:49:00.0)
         */
        userDAOList1.stream().forEach(x -> System.out.println(x));
        userDAOList1.stream().forEach(x -> System.out.println(x.getCreateTime()));
    }

}

   3.7 编译后的代码 

  通过 MapStruct 来生成的代码, 其类似于人手写。 速度上可以得到保证。本例子中生成的代码可以在编译后在 target/generated-sources/annotations 里看到。如下。所以说MapStruct生成的代码易于Debug,在使用反射的时候,如果出现了问题, 很多时候是很难找到是什么原因的,因为不直观。

import com.gougou.mapstruct.dao.UserDAO;
import com.gougou.mapstruct.dto.UserDTO;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-05-06T19:40:20+0800",
    comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
public class UserMapperImpl implements UserMapper {

    private final SexEnumIntegerMapper sexEnumIntegerMapper = new SexEnumIntegerMapper();
    private final StringTimestampMapper stringTimestampMapper = new StringTimestampMapper();

    @Override
    public UserDAO toDO(UserDTO userDTO) {
        if ( userDTO == null ) {
            return null;
        }

        UserDAO userDAO = new UserDAO();

        userDAO.setRemark( userDTO.getDesc() );
        userDAO.setCreateTime( stringTimestampMapper.timestampByString( userDTO.getCreateTime() ) );
        userDAO.setSex( sexEnumIntegerMapper.integerBySexEnum( userDTO.getSex() ) );
        if ( userDTO.getId() != null ) {
            userDAO.setId( Long.parseLong( userDTO.getId() ) );
        }
        userDAO.setName( userDTO.getName() );

        return userDAO;
    }

    @Override
    public List<UserDAO> toDOs(List<UserDTO> userDTOList) {
        if ( userDTOList == null ) {
            return null;
        }

        List<UserDAO> list = new ArrayList<UserDAO>( userDTOList.size() );
        for ( UserDTO userDTO : userDTOList ) {
            list.add( toDO( userDTO ) );
        }

        return list;
    }

    @Override
    public UserDTO toDTO(UserDAO userDAO) {
        if ( userDAO == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setCreateTime( stringTimestampMapper.stringByTimestamp( userDAO.getCreateTime() ) );
        userDTO.setSex( sexEnumIntegerMapper.sexEnumByInteger( userDAO.getSex() ) );
        userDTO.setDesc( userDAO.getRemark() );
        if ( userDAO.getId() != null ) {
            userDTO.setId( String.valueOf( userDAO.getId() ) );
        }
        userDTO.setName( userDAO.getName() );

        return userDTO;
    }

    @Override
    public List<UserDTO> toDTOs(List<UserDAO> userDAOList) {
        if ( userDAOList == null ) {
            return null;
        }

        List<UserDTO> list = new ArrayList<UserDTO>( userDAOList.size() );
        for ( UserDAO userDAO : userDAOList ) {
            list.add( toDTO( userDAO ) );
        }

        return list;
    }
}

   

、其他

   附上两个地址   MapStruct官网     MapStruct Git地址

 

以上是关于属性映射工具——MapStruct的主要内容,如果未能解决你的问题,请参考以下文章

属性映射工具——MapStruct

Java实体映射工具MapStruct详解

MapStruct 映射工具

实体映射最强工具类:MapStruct 真香!

推荐一个 Java 实体映射工具 MapStruct

bean复制映射工具包mapstruct