• 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。

  • 相关阅读:
    while练习题
    流程控制之for循环
    流程控制之while循环
    流程控制之if判断
    作业
    基本运算符
    输入输出
    基本数据类型
    变量part2
    JDBC中创建表
  • 原文地址:https://www.cnblogs.com/umgsai/p/8570652.html
Copyright © 2020-2023  润新知