屬性映射工具——MapStruct(一)


目錄:

屬性映射工具——MapStruct(一)

屬性映射工具——MapStruct(二)

屬性映射工具——MapStruct(三)

屬性映射工具——MapStruct(四)

屬性映射工具——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地址

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM