1. 程式人生 > >DO-DTO相互轉換時的性能優化

DO-DTO相互轉換時的性能優化

映射 star getclass 對象 tde desc mem bean 我們

一般情況下,DO是用來映射數據庫記錄的實體類,DTO是用來在網絡上傳輸的實體類。兩者的不同除了適用場景不同外還有就是DTO需要實現序列化接口。從DB查詢到數據之後,ORM框架會把數據轉換成DO對象,通常我們需要再把DO對象轉換為DTO對象。同樣的,插入數據到DB之前需要將DTO對象轉換為DO對象然後交給ORM框架去執行JDBC。

通常用到的轉換工具類BeanUtils是通過反射來實現的,實現源碼如下

public static <T> T convertObject(Object sourceObj, Class<T> targetClz) {
    if (sourceObj == null
) { return null; } if (targetClz == null) { throw new IllegalArgumentException("parameter clz shoud not be null"); } try { Object targetObj = targetClz.newInstance(); BeanUtils.copyProperties(sourceObj, targetObj); return (T) targetObj; } catch
(Exception e) { throw new RuntimeException(e); } } private static void copyProperties(Object source, Object target, Class<?> editable, String[] ignoreProperties) throws BeansException { Assert.notNull(source, "Source must not be null"); Assert.notNull(target, "Target must not be null"
); Class<?> actualEditable = target.getClass(); if (editable != null) { if (!editable.isInstance(target)) { throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]"); } actualEditable = editable; } PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); List<String> ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null; for (PropertyDescriptor targetPd : targetPds) { if (targetPd.getWriteMethod() != null && (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) { PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); if (sourcePd != null && sourcePd.getReadMethod() != null) { try { Method readMethod = sourcePd.getReadMethod(); if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); } Object value = readMethod.invoke(source); Method writeMethod = targetPd.getWriteMethod(); if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } writeMethod.invoke(target, value); } catch (Throwable ex) { throw new FatalBeanException("Could not copy properties from source to target", ex); } } } } }

也可以通過mapstruct來實現,這種方式是在Mapper接口的包中生成一個對應mapper的實現類,實現類的源碼如下。顯然這種方式的實現更為普通,看起來沒有BeanUtils的實現那麽復雜。不過BeanUtils通過反射實現更為通用,可以為各種類型的DTO實現轉換。而mapstruct只是幫我們生產了我們不想寫的代碼。

public Task doToDTO(TaskDO taskDO) {
        if (taskDO == null) {
            return null;
        } else {
            Task task = new Task();
            task.setId(taskDO.getId());
            //其他字段的set
            task.setGmtCreate(taskDO.getGmtCreate());
            task.setGmtModified(taskDO.getGmtModified());
            return task;
        }
    }

對比以上兩種方式,顯然使用BeanUtils來進行轉換時需要寫的代碼更少,內部的通過反射便可以進行get/set操作。而mapstruct實現上需要寫的代碼稍微多一點,但是這種方式的性能比通過反射實現更好。下面寫一段代碼來測試兩種方式實現的性能差別。

public void testConvert() {
    System.out.println("####testConvert");
    int num = 100000;
    TaskDO taskDO = new TaskDO();
    long start = System.currentTimeMillis();
    for (int i = 0; i < num; i ++) {
        Task task = ObjectConvertor.convertObject(taskDO, Task.class);
    }
    System.out.println(System.currentTimeMillis() - start);
    //---------------------------------------------
    start = System.currentTimeMillis();
    for (int i = 0; i < num; i ++) {
        Task task = taskMapper.doToDTO(taskDO);
    }
    System.out.println(System.currentTimeMillis() - start);
}

以上測試代碼中分別使用兩種方式對同一個DO對象進行n次轉換,兩次轉換的耗時統計如下。

1 10 100 1000 10000 100000 1000000 10000000
Mapstruct 0 1 1 1 2 4 8 8
BeanUtil 9 7 11 26 114 500 1469 14586

可見當轉換數量級增加時,使用BeanUtil的耗時急劇上升,而使用Mapstruct的耗時則保持在比較低的水平。

在一個系統中,ORM對DB的各種操作幾乎都會涉及到DO和DTO之間的轉換,參考以上表格的統計結果,更推薦使用Mapstruct。

DO-DTO相互轉換時的性能優化