DO-DTO相互转换时的性能优化

一般情况下,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次转换,两次转换的耗时统计如下。单位: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。

posted @ 2018-03-14 22:47  商商-77  阅读(3698)  评论(0编辑  收藏  举报