DO-DTO相互轉換時的性能優化
阿新 • • 發佈:2018-03-15
映射 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相互轉換時的性能優化