递归查询树型结构数据的性能优化方案

C: 在日常开发中,像系统菜单、文件目录、多级分类这样的树型结构业务数据,我们往往会采用递归的方式来完成数据的查询处理。

递归查询数据的确很方便,但稍微不注意就会造成较大的性能损耗,今天笔者就简单介绍一种优化方案。

SQL递归查询方案

查询思路:

  1. SQL 查询一级数据
  2. 遍历一级数据
  3. 通过一级数据 递归 SQL 查询二级…等子级数据

/**
 * 根据字典码查询树型字典
 *
 * @param code 字典码
 * @return 树型字典
 */
public List<KeyValueItemVo> listDictTreeByCode(String code) {
    // #####开始计时#####
    TimeInterval timer = DateUtil.timer();
    
    // 1、获取一级字典
    List<SysDict> oneLevelDictList = this.listDictByCodeAndParentId(code, null);
    if (CollUtil.isEmpty(oneLevelDictList)) {
        return null;
    }

    // 2、遍历一级字典
    List<KeyValueItemVo> resultList = new ArrayList<>();
    oneLevelDictList.forEach(oneLevelDict -> {
        KeyValueItemVo result = new KeyValueItemVo(oneLevelDict.getDictKey(), oneLevelDict.getDictValue());
        // 3、递归获取子级字典
        result.setItems(getChildren(oneLevelDict.getDictId()));
        resultList.add(result);
    });
    
    // #####打印计时#####
    System.out.println("总耗时:" + timer.interval() + "ms");
    return resultList;
}

/**
 * 根据父ID查询子字典树
 *
 * @param parentId 父ID
 * @return 子字典树
 */
private List<KeyValueItemVo> getChildren(Long parentId) {
    List<SysDict> children = this.listDictByCodeAndParentId(null, parentId);
    if (CollUtil.isEmpty(children)) {
        return null;
    }

    List<KeyValueItemVo> resultList = new ArrayList<>();
    children.forEach(child -> {
        KeyValueItemVo result = new KeyValueItemVo(child.getDictKey(), child.getDictValue());
        result.setItems(getChildren(child.getDictId()));
        resultList.add(result);
    });
    return resultList;
}

/**
 * 根据字典码和父ID查询字典列表
 *
 * @param code     字典码
 * @param parentId 父ID
 * @return 字典列表
 */
public List<SysDict> listDictByCodeAndParentId(String code, Long parentId) {
    LambdaQueryWrapper<SysDict> queryWrapper = Wrappers.<SysDict>lambdaQuery()
        .eq(StrUtil.isNotBlank(code), SysDict::getCode, code)
        .orderByAsc(SysDict::getSort);
    if (parentId == null) {
        queryWrapper.isNull(SysDict::getParentId);
    } else {
        queryWrapper.eq(SysDict::getParentId, parentId);
    }
    return dictMapper.selectList(queryWrapper);
}

这种方式执行后,控制台打印了好几屏幕的查询 SQL,而且层级越深,每级的数据越多,产生的 SQL 查询也会越多,程序的执行效率自然就会很低。

在这里插入图片描述

程序递归处理方案

既然发现了问题,那就要想办法进行优化。而性能优化除了要学治本外还要善用治标,方案一效率低的原因是由于产生了过多的 SQL 查询,对症下药自然就要减少 SQL 查询次数。

优化查询思路:

  1. SQL 查询出所有数据
  2. 在程序中对数据进行整理
    1. 过滤出一级数据
    2. 遍历一级数据
    3. 通过一级数据 递归 过滤二级…等子级数据
/**
 * 根据字典码查询树型字典
 *
 * @param code 字典码
 * @return 树型字典
 */
public List<KeyValueItemVo> listDictTreeByCode(String code) {
    // #####开始计时#####
    TimeInterval timer = DateUtil.timer();
    
    // 1、获取所有字典列表
    LambdaQueryWrapper<SysDict> queryWrapper = Wrappers.<SysDict>lambdaQuery()
        .eq(SysDict::getCode, code)
        .orderByAsc(SysDict::getSort);
    List<SysDict> dictList = dictMapper.selectList(queryWrapper);
    if (CollUtil.isEmpty(dictList)) {
        return null;
    }

    // 2、获取一级字典
    List<SysDict> oneLevelDictList = dictList.stream()
        .filter(dict -> Objects.isNull(dict.getParentId()))
        .collect(Collectors.toList());
    List<KeyValueItemVo> resultList = new ArrayList<>();
    oneLevelDictList.forEach(oneLevelDict -> {
        KeyValueItemVo result = new KeyValueItemVo(oneLevelDict.getDictKey(), oneLevelDict.getDictValue());
        // 3、递归整理子级字典
        result.setItems(getChildren(oneLevelDict.getDictId(), dictList));
        resultList.add(result);
    });
    
    // #####打印计时#####
    System.out.println("总耗时:" + timer.interval() + "ms");
    return resultList;
}

/**
 * 根据父ID查询子字典树
 *
 * @param parentId 父ID
 * @param dictList 所有该类型子字典数据
 * @return 子字典树
 */
private List<KeyValueItemVo> getChildren(Long parentId, List<SysDict> dictList) {
    List<SysDict> children = dictList.stream()
        .filter(dict -> Objects.isNotNull(dict.getParentId()) && dict.getParentId().equals(parentId))
        .collect(Collectors.toList());
    if (CollUtil.isEmpty(children)) {
        return null;
    }

    List<KeyValueItemVo> resultList = new ArrayList<>();
    children.forEach(child -> {
        KeyValueItemVo result = new KeyValueItemVo(child.getDictKey(), child.getDictValue());
        result.setItems(getChildren(child.getDictId(), dictList));
        resultList.add(result);
    });
    return resultList;
}

优化查询后,我们再来看一下执行效果,控制台仅打印了一条查询 SQL,程序执行效率也有了很大的提升。

在这里插入图片描述

涉及的 VO 类结构:

/**
 * 单级键值对VO
 *
 * @author Charles7c
 * @date 2022/9/7 21:23
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class KeyValueVo {
    /**
     * 文本
     */
    private Object label;
    /**
     * 值
     */
    private Object value;
}
/**
 * 多级键值对VO
 *
 * @author Charles7c
 * @date 2022/9/7 21:23
 */
@Data
@NoArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class KeyValueItemVo extends KeyValueVo {

    /**
     * 子项
     */
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private List<KeyValueItemVo> items;

    public KeyValueItemVo(Object label, Object value, List<KeyValueItemVo> items) {
        super(label, value);
        this.items = items;
    }

    public KeyValueItemVo(Object label, Object value) {
        super(label, value);
    }
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

查尔斯-BUG万象集

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值