SpringBoot优化树形结构数据查询

发布于:2025-09-09 ⋅ 阅读:(24) ⋅ 点赞:(0)

在实际业务中,分类树(Category Tree)往往是内容管理、权限体系等系统的核心数据结构。随着业务规模不断扩张,原本毫无压力的树形查询,突然变成了系统性能的瓶颈。

1. 传统实现方式

通常是递归调用 getChildren(),每次访问数据库查询子节点。

    public List<PubRegion> getRegionRange(String regionCode) {
        List<PubRegion> result = new ArrayList<>();
        getChildren(regionCode, result);
        return result;
    }

    private void getChildren(String code, List<PubRegion> result) {
        // 查询给定 code 值的区域
        Query query = new Query(Criteria.where("code").is(code));
        List<PubRegion> regions = mongoTemplate.find(query, PubRegion.class);
        result.addAll(regions);

        // 查询给定 code 值的区域的子区域
        query = new Query(Criteria.where("parent_code").is(code));
        List<PubRegion> subRegions = mongoTemplate.find(query, PubRegion.class);
        for (PubRegion subRegion : subRegions) {
            getChildren(subRegion.getCode(), result);
        }
    }

看似简洁,实则暗藏杀机。

问题分析:1 万节点 = 1 万次 SQL,单次查询平均 2ms → 总耗时 20 秒。

数据库连接池被瞬间榨干,内存中反复创建对象,GC 压力暴增。

结果就是:分类树几乎不可用。

2. 优化方案

优化思路:一次查询 + O(n) 算法

核心理念:数据库只查一次 ,获取所有节点;HashMap 建索引 ,O(1) 查找父子关系;单次遍历构建树 ,O(n) 时间复杂度;结果缓存 ,避免重复构建。

  • 通用树构建器
@Component
public class TreeBuilder {

    public <T extends TreeNode<T>> List<T> buildTree(List<T> nodes, Object rootValue) {
        if (CollectionUtils.isEmpty(nodes)) {
            return new ArrayList<>();
        }

        // 1. 建立快速索引 O(n)
        Map<Object, T> nodeMap = nodes.stream()
            .collect(Collectors.toMap(TreeNode::getId, node -> node));

        List<T> rootNodes = new ArrayList<>();

        // 2. 单次遍历构建父子关系 O(n)
        for (T node : nodes) {
            Object parentId = node.getParentId();
            if (Objects.equals(parentId, rootValue)) {
                rootNodes.add(node);
            } else {
                T parent = nodeMap.get(parentId);
                if (parent != null) {
                    parent.addChild(node);
                }
            }
        }

        // 3. 可选:递归排序 O(n log n)
        sortTree(rootNodes);

        return rootNodes;
    }

    private <T extends TreeNode<T>> void sortTree(List<T> nodes) {
        if (CollectionUtils.isEmpty(nodes)) {
            return;
        }
//        nodes.sort(Comparator.comparing(TreeNode::getSortOrder,
//            Comparator.nullsLast(Comparator.naturalOrder())));
        for (T node : nodes) {
            if (node.hasChildren()) {
                sortTree(node.getChildren());
            }
        }
    }
}
  • 树节点基类
public interface TreeNode<T> {
    Object getId();
    Object getParentId();
    //Integer getSortOrder();
    List<T> getChildren();
    void setChildren(List<T> children);

    default void addChild(T child) {
        if (getChildren() == null) {
            setChildren(new ArrayList<>());
        }
        getChildren().add(child);
    }

    default boolean hasChildren() {
        return getChildren() != null && !getChildren().isEmpty();
    }
}
  • 实体类
@Data
@NoArgsConstructor
@SuperBuilder
@ApiModel(description = "区域表")
@Document(collection = "t_region")
public class PubRegion  implements TreeNode<PubRegion> {

    private ObjectId id;

    @ApiModelProperty("区域名称")
    private String name;

    @ApiModelProperty("区域拼音")
    private String pinyin;

    @ApiModelProperty("区域等级:省:0,市:1,区/镇:2")
    private Integer type;

    @ApiModelProperty("地区编号")
    private String code;

    @ApiModelProperty("上级地区")
    @Field("parent_code")
    private String parentCode;

    @ApiModelProperty("邮编")
    @Field("zip_code")
    private String zipCode;

    @ApiModelProperty("首字母")
    @Field("first_letter")
    private String firstLetter;

    private List<PubRegion> children;

    @Override
    public Object getId() {
        return code;
    }

    @Override
    public Object getParentId() {
        return parentCode;
    }

//涉及排序可以加上
//    @Override
//    public String getSortOrder() {
//        return firstLetter;
//    }
}

  • Controller实现
@RestController
@Slf4j
@RequestMapping("/region")
public class RegionController {

    @Resource
    private RegionService regionService;

    @Resource
    private TreeBuilder treeBuilder;

    @Resource
    private MongoTemplate mongoTemplate;

    @GetMapping("/getRegionRange")
    public ResponseEntity<List<PubRegion>> getRegionRange(@RequestParam String regionCode) {
        List<PubRegion> region = regionService.findAll();
        return ResponseEntity.ok(treeBuilder.buildTree(region, regionCode));
    }
}

最终通过一次性查询 + 高效内存构建树的方式,将性能从 3 秒直接压缩到 30 毫秒,实现了 100 倍提速。


网站公告

今日签到

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