futuretask本身不推导树形结构层级,仅异步执行封装的遍历与更新逻辑:加载子树、构内存树、bfs/dfs计算depth/level、批量持久化。

在文件目录树的持久化层点击保存时,FutureTask 并不直接参与树形结构的深度与层级推导——它只是一个用于异步执行和结果获取的并发工具,本身不具备解析树结构或计算层级的能力。真正起作用的是你封装在 FutureTask 中的逻辑:即对目录树节点进行遍历、分析并更新深度/层级字段的业务代码。
为什么用 FutureTask?
在保存操作中引入 FutureTask,通常是为了:
- 避免阻塞主线程(如 Web 请求线程或 UI 线程),提升响应性
- 将耗时的树遍历、层级重算、数据库批量更新等操作异步化
- 统一管理异步任务的生命周期(取消、超时、结果获取)
如何设计推导逻辑(核心步骤)
你需要在 Callable 实现中完成树形结构的层级推导。假设节点实体为 DirNode,含 id、parentId、depth、level(或 path)等字段:
- 加载全量待保存子树:根据根节点 ID 查询所有后代节点(可借助闭包表、路径字段或递归 CTE,避免 N+1)
-
构建成内存树结构:用
Map<long dirnode></long>存储,并建立父子引用关系(或仅靠 parentId 关系做拓扑排序) -
自顶向下 BFS 或 DFS 计算 depth/level:
- 根节点
depth = 0或1(按需定义) - 子节点
depth = parent.depth + 1 - 可同时生成规范路径(如
/1/5/23)用于排序或查询优化
- 根节点
-
批量更新数据库:使用
JdbcTemplate.batchUpdate或 MyBatis<foreach></foreach>批量设置depth和level
FutureTask 的典型用法示例
以下为轻量级示意(省略异常处理与事务):
Callable<integer> computeAndPersist = () -> {
List<dirnode> allNodes = dirNodeMapper.selectDescendants(rootId);
Map<long dirnode> nodeMap = allNodes.stream()
.collect(Collectors.toMap(DirNode::getId, Function.identity()));
Queue<dirnode> queue = new LinkedList();
DirNode root = nodeMap.get(rootId);
root.setDepth(0); // 或 1
queue.offer(root);
while (!queue.isEmpty()) {
DirNode parent = queue.poll();
for (DirNode child : allNodes) {
if (Objects.equals(child.getParentId(), parent.getId())) {
child.setDepth(parent.getDepth() + 1);
queue.offer(child);
}
}
}
dirNodeMapper.batchUpdateDepthAndLevel(allNodes);
return allNodes.size();
};
FutureTask<integer> task = new FutureTask(computeAndPersist);
executor.submit(task); // 提交到线程池
// …… 可在需要时 task.get() 获取结果或判断是否完成
</integer></dirnode></long></dirnode></integer>
注意事项与替代建议
实际落地中需注意:
-
事务边界:异步任务无法自动继承调用方事务,
batchUpdate应在独立事务中执行(如@Transactional(propagation = REQUIRES_NEW)) - 数据一致性:若保存前有并发修改,应加锁(如 select for update)或采用乐观锁(version 字段)
-
更优替代方案:对高频读写场景,可考虑:
- 使用闭包表(Closure Table)预存所有祖先-后代关系,免去运行时遍历
- 用 Materialized Path(如
path = "/1/5/23")直接通过字符串分割计算深度 - 数据库端用递归 CTE(PostgreSQL/Oracle/SQL Server)一次性计算并更新










