通常狀況下,DO是用來映射數據庫記錄的實體類,DTO是用來在網絡上傳輸的實體類。二者的不一樣除了適用場景不一樣外還有就是DTO須要實現序列化接口。從DB查詢到數據以後,ORM框架會把數據轉換成DO對象,一般咱們須要再把DO對象轉換爲DTO對象。一樣的,插入數據到DB以前須要將DTO對象轉換爲DO對象而後交給ORM框架去執行JDBC。java
一般用到的轉換工具類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實現上須要寫的代碼稍微多一點,可是這種方式的性能比經過反射實現更好。下面寫一段代碼來測試兩種方式實現的性能差異。app
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次轉換,兩次轉換的耗時統計以下。單位:ms框架
次數 | 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。性能