在实际业务中,分类树(Category Tree)往往是内容管理、权限体系等系统的核心数据结构。随着业务规模不断扩张,原本毫无压力的树形查询,突然变成了系统性能的瓶颈。
1. 传统实现方式
通常是递归调用 getChildren(),每次访问数据库查询子节点。
java
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) 时间复杂度;结果缓存 ,避免重复构建。
- 通用树构建器
java
@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());
}
}
}
}
- 树节点基类
java
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();
}
}
- 实体类
java
@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实现
java
@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 倍提速。