最近开始负责一个数据量比较大的业务模块,要求把相关数据全部查出来,不分页,要组树结构,数据从dao层到service由entity对象到Vo对象给前端展示。那么就涉及到对象拷贝,开始的时候用的Spring的BeanUtils做对象转换,并没有什么问题,后来到了测试那里,加大数据量,发现接口越来越慢,开始以为数据库查询问题,把sql搬到数据库运行,发现并不慢,因为关键字段基本都走了索引,不会很慢,后来一步一步找,发现是BeanUtils耗时引起的,然后就有了下面的关于三种对象拷贝方式的实践

实践:Apache的BeanUtils、Spring的BeanUtils、Mapstruct

这里可能很多小伙伴只用过Spring的BeanUtils,其余的两种没用过,不过没关系,接下来来个简单的测试

  • 引入maven依赖,为了测试方便这里直接创建的SpringBoot项目,用的junit测试
org.springframework.boot            spring-boot-starter        org.projectlombok            lombok            trueorg.springframework.boot            spring-boot-starter-test            testorg.junit.vintage                    junit-vintage-engine                commons-beanutils            commons-beanutils            1.8.3org.mapstruct            mapstruct-jdk8            1.2.0.Finalorg.mapstruct            mapstruct-processor            1.2.0.Final复制代码
  • 类结构



java构造方法拷贝别的类的参数是什么_System


  • 4个简单类
/** * @description user 实体 * @author Wanm * @date 2020/7/8 21:37 */@Data@NoArgsConstructor@AllArgsConstructorpublic class User {    private String id;    private String name;    private Integer age;    private String address;    private String sex;}/** * @description userVo * @author Wanm * @date 2020/7/8 21:37 */@Data@NoArgsConstructor@AllArgsConstructorpublic class UserVo {    private String id;    private String name;    private Integer age;    private String address;    private String sex;}/** * @description UserTransfer * @author Wanm * @date 2020/7/8 21:37 */@Mapperpublic interface UserTransfer {    /**     * entity转vo     * @param user     * @return     */    List entityToVo(List user);}/** * 测试类 */@SpringBootTestclass MapstructApplicationTests {    @Test    void contextLoads() {        //这里拿100w数据做数据初始化        List userList = new ArrayList();        for (int i = 0; i < 1000000; i++) {            User user = new User(UUID.randomUUID().toString(),UUID.randomUUID().toString(),i,UUID.randomUUID().toString(),UUID.randomUUID().toString());            userList.add(user);        }        System.out.println(userList.get(0));        System.out.println("开始拷贝---------------------------------------");        testBeanUtils(userList);        testSpringBeanUtils(userList);        testMapStruct(userList);    }    /**     *  Apache的BeanUtils     * @param userList     */    public static void testBeanUtils(List userList){        long start = System.currentTimeMillis();        List userVos = new ArrayList<>();        userList.forEach(item->{            UserVo userVo  = new UserVo();            try {                BeanUtils.copyProperties(userVo,item);                userVos.add(userVo);            } catch (Exception e) {                e.printStackTrace();            }        });        long end = System.currentTimeMillis();        System.out.println(userVos.get(0));        System.out.println("集合大小参数验证"+userVos.size()+"Apache的BeanUtils耗时:"+(end-start)+"ms");    }    /**     * Spring的BeanUtils     * @param userList     */    public static void testSpringBeanUtils(List userList){        long start = System.currentTimeMillis();        List userVos = new ArrayList<>();        userList.forEach(item->{            UserVo userVo  = new UserVo();            try {                org.springframework.beans.BeanUtils.copyProperties(item,userVo);                userVos.add(userVo);            } catch (Exception e) {                e.printStackTrace();            }        });        long end = System.currentTimeMillis();        System.out.println(userVos.get(0));        System.out.println("集合大小参数验证"+userVos.size()+"Spring的BeanUtils耗时:"+(end-start)+"ms");    }    /**     * mapStruct拷贝     * @param userList     */    public  void testMapStruct(List userList){        long start = System.currentTimeMillis();        List userVos = Mappers.getMapper(UserTransfer.class).entityToVo(userList);        long end = System.currentTimeMillis();        System.out.println(userVos.get(0));        System.out.println("集合大小参数验证"+userVos.size()+"mapStruct耗时:"+(end-start)+"ms");    }}复制代码
  • 实际开发中vo类属性字段会比实体类少很多。这里只做测试,下面看测试结果


java构造方法拷贝别的类的参数是什么_java构造方法拷贝别的类的参数是什么_02


  • 有没有看到MapStruct的耗时41ms,完胜有木有,数据量越大越能看到差异,下图是针对不同数据量的耗时做具体分析:

Apache Spring MapStruct 1000 67ms 10ms 2ms 1w 174ms 35ms 3ms 10w 808ms 69ms 9ms 100w 5620ms 336ms 42ms


由此可以看出数据量越大MapStruct>Spring>Apache,这个性能优势越来越明显,日常开发中对象拷贝只是代码中的一小部分逻辑,如果数据量大的话还是建议大家使用MapStruct的方式,提高接口的性能。数据量不大的话Spring的BeanUtils也行,还是看实际业务场景!!!

原理:MapStruct使用注解处理器生成实现类,实现类内部是原生的new对象,然后SetXxx/getXxx方式赋值进行数据拷贝的,类似lombok,看实现类的.class

public class UserTransferImpl implements UserTransfer {    public UserTransferImpl() {    }    public List entityToVo(List user) {        if (user == null) {            return null;        } else {            List list = new ArrayList(user.size());            Iterator var3 = user.iterator();            while(var3.hasNext()) {                User user1 = (User)var3.next();                list.add(this.userToUserVo(user1));            }            return list;        }    }    protected UserVo userToUserVo(User user) {        if (user == null) {            return null;        } else {            UserVo userVo = new UserVo();            userVo.setId(user.getId());            userVo.setName(user.getName());            userVo.setAge(user.getAge());            userVo.setAddress(user.getAddress());            userVo.setSex(user.getSex());            return userVo;        }    }}复制代码

Spring和Apache的BeanUtils则是用到了反射机制,内部实现也是有区别的,特别校验这块,至于他们之间的详细区别,交给小伙伴们自己去探寻啦~


总结:通过这次简单的测试,掌握了三种拷贝方式的实现方式和性能差异,不过使这块还是建议小伙伴们根据实际场景使用,MapStruct的性能确实好,当是需要引入第三方依赖,如果数据量这块不大Spring自带的BeanUtils够用。

作者:颜青