【java】代码设计优化(1)查询优化

发布于:2025-06-25 ⋅ 阅读:(20) ⋅ 点赞:(0)

        

目录

一、问题引出

二、常规方法

三、代码优化

总结


        大家好,我是jstart千语。今天给大家来分享一下代码设计优化的问题,代码设计方面在评审时是非常重要的,大家在平时练习的时候可能不太会注意,只要结果运行出来就行了。但如果要维护好一个项目的话,代码还是要追求一些性能的,所以我建议大家在平时书写时也要注意一下优化,特别是涉及到多层循环时的问题。

一、问题引出

从数据库中分页查询图片时,得到如Page<Picture>。我们要进行转换成Page<PictureVO>,也就是要将Picture脱敏后转成VO类型返回给用户。

Picture和PictureVO的区别:

        Picture里有userId属性。PictureVo里多了一个UserVO属性。

问题:

        所以我们要将Picture转成PictureVO时,要将Picture的userId取出来,然后去数据库里面查询得出User,然后还要将User转成UserVO,最后再塞给PictureVO。


二、常规方法

我们很容易想到,就是将Page<Picture>取出List<Picture>,然后直接用stream流,依次拿出userId去数据库查询,最后将User收集起来。

示例代码如下:

public Page<PictureVO> PicturePageToVOPage(Page<Picture> picturePage) {
        List<Picture> pictureList = picturePage.getRecords();

        Page<PictureVO> pictureVOPage = new Page<>(picturePage.getCurrent(), picturePage.getSize(), picturePage.getTotal());
        if (CollUtil.isEmpty(pictureList)) {
            return pictureVOPage;
        }

        //逐个查询数据库
        List<User> userList = pictureList.stream().map(picture -> {
            Long userId = picture.getUserId();
            return userService.getById(userId);
        }).collect(Collectors.toList());

        // 将Picture初步转成PictureVO
        List<PictureVO> pictureVOList = pictureList.stream()
                .map(PictureVO::objToVo)
                .collect(Collectors.toList());

        //填充PictureVO里的UserVO属性
        pictureVOList.forEach(pictureVO -> {

            //找到与userId匹配的用户
            Long userId = pictureVO.getUserId();
            User user = userList.stream()
                    .filter(u -> u.getId().equals(userId))
                    .findFirst()
                    .orElse(null);
            //将用户信息转为VO
            if (user != null) { 
                UserVO userVO = new UserVO();
                BeanUtils.copyProperties(user, userVO);
                pictureVO.setUserVO(userVO);
            } else {
                // 如果没有找到用户,抛出异常
                throw new BusinessException(ResultEnum.SYSTEM_ERROR, "picture转VO错误");
            }
        });
       
        //最后将完整信息放回到page对象中
        pictureVOPage.setRecords(pictureVOList);
        return pictureVOPage;
}

三、代码优化

问题:

       (1) 从上面的代码可以看到,每次都遍历出userId之后就去查询数据库了。如果数据量很大的时候,就会造成数据的压力。

        (2)并且在填充PictureVO里的UserVO属性时,先循环遍历了PictureVOList,然后又在循环里面继续使用stream流遍历了userList。相当于嵌套了两层循环。

那么优化的角度就是:

(1)如何减少访问数据库的次数?

(2)如何避免循环嵌套?

先看代码:

    /**
     * 获取分页图片封装(分页实体转分页VO)
     */
    @Override
    public Page<PictureVO> PicturePageToVOPage(Page<Picture> picturePage) {
        List<Picture> pictureList = picturePage.getRecords();

        //给返回值的Page对象赋值
        Page<PictureVO> pictureVOPage = new Page<>(picturePage.getCurrent(), picturePage.getSize(), picturePage.getTotal());
        if (CollUtil.isEmpty(pictureList)) {
            return pictureVOPage;
        }
        // 对象列表 => 封装对象列表
        List<PictureVO> pictureVOList = pictureList.stream()
                .map(PictureVO::objToVo) //picture转VO
                .collect(Collectors.toList()); //收集成List
        // 1. 取出pictureVOList中所有的用户id,并去重
        Set<Long> userIdSet = pictureList.stream()
                .map(Picture::getUserId)
                .collect(Collectors.toSet());
        /**
         * User::getId 是一个方法引用,表示根据 User 对象的 getId() 方法返回的 ID 对用户进行分组。
         * groupingBy() 会生成一个 Map,其中每个键是用户的 ID,值是该 ID 对应的 User 对象的列表。
         * 例如,如果用户 ID 为 1 的用户有多个,返回的 Map<Long, List<User>> 中,
         * 键为 1 的值将是一个包含所有 ID 为 1 的用户对象的列表。
         */
        Map<Long, List<User>> userIdUserListMap = userService
                .listByIds(userIdSet)//一次性查出userId集合中所有的用户User对象
                .stream()
                .collect(Collectors.groupingBy(User::getId));//根据User的id属性分组
        // 2. 循环遍历pictureVOList,给里面的UserVO属性赋值
        pictureVOList.forEach(pictureVO -> {
            Long userId = pictureVO.getUserId();
            User user = null;
            //如果有这个userId对应的对象就赋值,一般都是有的,因为上面查的时候就是根据这些userId来查询的,除非上面转成set集合时没有放上去
            if (userIdUserListMap.containsKey(userId)) {
                user = userIdUserListMap.get(userId).get(0);//这里因为id在User里是唯一的,所以map里的第二个泛型是List里面其实也是只有一个数据
            } else
                throw new BusinessException(ErrorEnum.SYSTEM_ERROR, "picture转VO错误");
            UserVO userVO = new UserVO();
            BeanUtils.copyProperties(user, userVO);//User转UserVO
            pictureVO.setUserVO(userVO);
        });
        pictureVOPage.setRecords(pictureVOList);//给分页对象赋值。
        return pictureVOPage;
    }

解释:

(1)上面的代码中,先将Picture对象里的userId统一收集起来,而且是收集到set集合里面,这样就天然地去重了。然后再统一地,一次性地携带集合去请求数据库,得到的也是一个user集合。这样就解决了大量请求数据库的情况。

(2)user集合再使用stream流进行分组,相当于一个map,key的话就使用userId,value就是user对象本身。这样到下面需要匹配userId对应的user时,就直接从map里面取就可以了,所以这就避免了再嵌套一层循环了。


总结:

        经过两个方面的优化,这个接口的性能大大提高。这只是一个示例,在平时编写代码时可能会遇到更复杂的情况,但思想还是一样的。


网站公告

今日签到

点亮在社区的每一天
去签到