業務開發必看-實體映射工具推薦

前言

聲明:java

一、DO(業務實體對象),DTO(數據傳輸對象)。spring

在一個成熟的工程中,尤爲是如今的分佈式系統中,應用與應用之間,還有單獨的應用細分模塊以後,DO 通常不會讓外部依賴,這時候須要在提供對外接口的模塊裏放 DTO 用於對象傳輸,也便是 DO 對象對內,DTO對象對外,DTO 能夠根據業務須要變動,並不須要映射 DO 的所有屬性。shell

  • 在咱們對外暴露的Dubbo接口,通常這樣定義接口類
/** * 獲取營銷信息 * * @param hallId 場館編號 * @param modelCode 車型代碼 * @return */
    BrandMarketInfoDTO getBrandMarketInfo(String hallId, String modelCode);
複製代碼
  • 在和數據持久層映射實現的接口中,通常咱們會這麼寫
/** * 獲取水牌營銷信息 * * @param hallId * @param modelCode * @return */
HallCarManageDO getBoardMarketInfo(@Param("hallId") String hallId, @Param("modelCode") String modelCode);
複製代碼

可是咱們HallCarManageDO和BrandMarketInfoDTO中的屬性和屬性類型不是相等的。這種 對象與對象之間的互相轉換,就須要有一個專門用來解決轉換問題的工具,畢竟每個字段都 get/set 會很麻煩。緩存

常見工具類及實現

如下列舉被廣大開發工程師經常使用的Bean屬性複製工具安全

  • Spring.BeanUtils
  • Cglib.BeanCopier
  • MapStruct

如下選取屬性賦值的功能來對比每一個工具類的不一樣app

BeanUtils

import org.springframework.beans.BeanUtils;

/** * @author james mu * @date 2019/10/22 */
public class PojoUtils {

    /** * * @param source 源對象 * @param clazz 目標對象 * * @return 複製屬性後的對象 */
    public static <T> T copyProperties(Object source, Class<T> clazz) {
        if (source == null) {
            return null;
        }
        T target;
        try {
            target = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("經過反射建立對象失敗");
        }
        BeanUtils.copyProperties(source, target);
        return target;
    }

}

複製代碼

springframework的BeanUtils也是經過java內省機制獲取getter/setter,而後經過反射調用從而實現屬性複製。分佈式

BeanCopier

依賴引用

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>
複製代碼

工具類實現

import net.sf.cglib.beans.BeanCopier;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/** * @author james mu * @date 2019/10/22 */
public class BeanCopierUtils {

    /** * BeanCopier拷貝速度快,性能瓶頸出如今建立BeanCopier實例的過程當中。 * 因此,把建立過的BeanCopier實例放到緩存中,下次能夠直接獲取,提高性能 */
    public static Map<String, BeanCopier> beanCopierMap = new ConcurrentHashMap<String, BeanCopier>();

    /** * cp 對象賦值 * * @param source 源對象 * @param target 目標對象 */
    public static void copyProperties(Object source, Object target) {
        String beanKey = generateKey(source.getClass(), target.getClass());
        BeanCopier copier = null;
        if (!beanCopierMap.containsKey(beanKey)) {
            copier = BeanCopier.create(source.getClass(), target.getClass(), false);
            beanCopierMap.put(beanKey, copier);
        } else {
            copier = beanCopierMap.get(beanKey);
        }
        copier.copy(source, target, null);
    }

    private static String generateKey(Class<?> class1, Class<?> class2) {
        return class1.toString() + class2.toString();
    }
}

複製代碼
  1. 使用動態代理,生成字節碼類,再經過Java反射成Class,調用其copy方法。ide

  2. 你們能夠看到這裏用到了ConcurrentHashMap存取copier,由於BeanCopier.create使用了緩存,該過程也消耗資源,建議全局只初始化一次。工具

    自定義轉換器

  3. 支持自定義轉換器。post

MapStruct

MapSturct 是一個生成類型安全, 高性能且無依賴的 JavaBean 映射代碼的註解處理器(annotation processor)。

抓一下重點:

  1. 註解處理器
  2. 能夠生成 JavaBean 之間那的映射代碼
  3. 類型安全, 高性能, 無依賴性

從字面的理解, 咱們能夠知道, 該工具能夠幫咱們實現 JavaBean 之間的轉換, 經過註解的方式。

同時, 做爲一個工具類,相比於手寫, 其應該具備便捷, 不容易出錯的特色。

入門

依賴引用

<dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.3.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.3.0.Final</version>
            <scope>provided</scope>
        </dependency>
複製代碼

案例演示

UserDO

import lombok.Data;
import lombok.ToString;

import java.util.Date;

/** * @author james mu * @date 2019/10/22 */
@Data
@ToString
public class UserDO {
    private String name;
    private String password;
    private Integer age;
    private Date birthday;
    private String sex;
}


複製代碼

UserDTO

import lombok.Data;
import lombok.ToString;


/** * @author james mu * @date 2019/10/22 */
@Data
@ToString
public class UserDTO {
    private String name;
    private String age;
    private String birthday;
    private String gender;
}
複製代碼

UserConvertUtils

import com.sanshengshui.javabeanconvert.DO.UserDO;
import com.sanshengshui.javabeanconvert.DTO.UserDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/** * @author james mu * @date 2019/10/22 */
@Mapper
public interface UserConvertUtils {

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

    /** * 類型轉換 * * @param userDO UserDO數據持久層類 * @return 數據傳輸類 */
    @Mappings({
               @Mapping(target = "gender", source = "sex")
            })
    UserDTO doToDTO(UserDO userDO);
}


複製代碼
  1. DTO與DO中屬性名相同時候默認映射,(好比name),屬性名相同屬性類型不一樣也會映射,(好比birthday,一個Data,一個String)
  2. DTO與DO中屬性名不一樣的,須要經過@Mapping明確關係來造成映射(如sex對應gender)
  3. 無映射關係屬性被忽略(如UserEntity的password)

結果以下:

UserDO(name=snow, password=123, age=20, birthday=Tue Oct 22 17:10:19 CST 2019, sex=男)
+-+-+-+-+-+-+-+-+-+-+-
UserDTO(name=snow, age=20, birthday=19-10-22 下午5:10, gender=男)

複製代碼

MapStruct分析

上面中, 我寫了3個步驟來實現了從 UserDTOUserDO 的轉換。

那麼, 做爲一個註解處理器, 經過MapStruct 生成的代碼具備怎麼樣的優點呢?

高性能

Java反射原理和反射低的緣由:juejin.im/post/5da33b…

這是相對反射來講的, 反射須要去讀取字節碼的內容, 花銷會比較大。 而經過 MapStruct 來生成的代碼, 其相似於人手寫。 速度上能夠獲得保證。

前面例子中生成的代碼能夠在編譯後看到。 在 target/generated-sources/annotations 裏能夠看到。

對應的代碼

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2019-10-22T17:10:17+0800",
    comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_222 (Azul Systems, Inc.)"
)
public class UserConvertUtilsImpl implements UserConvertUtils {

    @Override
    public UserDTO doToDTO(UserDO userDO) {
        if ( userDO == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setGender( userDO.getSex() );
        userDTO.setName( userDO.getName() );
        if ( userDO.getAge() != null ) {
            userDTO.setAge( String.valueOf( userDO.getAge() ) );
        }
        if ( userDO.getBirthday() != null ) {
            userDTO.setBirthday( new SimpleDateFormat().format( userDO.getBirthday() ) );
        }

        return userDTO;
    }
}

複製代碼

能夠看到其生成了一個實現類, 而代碼也相似於咱們手寫, 通俗易懂。

性能比較

測試在兩個簡單的Bean之間轉換的耗時,執行次數分別爲十、100、1k、10k、100k,時間單位爲ms。

總結

雖然反射效率低,但這個時間是很小很小的。根據不一樣工具的性能及功能維度,我的建議當對象轉換操做較少或者應用對性能要求較高時,儘可能不採用工具,而是手寫getter/setter;在不考慮性能的狀況下,普通的對象轉換可使用Cglib.BeanCopier,複雜的對象轉換使用MapStruct。

相關文章
相關標籤/搜索