如何在Java中实现树形Person结构的ID列表过滤?
需求:基于ID列表过滤树形结构,保留目标节点及所有父节点
已实现Person实体类的树形结构生成接口,现需添加过滤功能:传入ID列表后,返回的树形仅包含列表中ID对应的节点,以及它们的所有关联父节点。
未过滤响应
[ { "id": 1, "fullName": "William", "children": [ { "id": 2, "fullName": "Henry", "children": [ { "id": 3, "fullName": "Matt", "children": [ { "id": 7, "fullName": "Sophi", "children": [], "rootParentId": 1 } ], "rootParentId": 1 }, { "id": 4, "fullName": "Alisa", "children": [ { "id": 6, "fullName": "Alexa", "children": [], "rootParentId": 1 } ], "rootParentId": 1 } ], "rootParentId": 1 }, { "id": 5, "fullName": "May", "children": [], "rootParentId": 1 } ], "rootParentId": 1 }, { "id": 8, "fullName": "Olivia", "children": [ { "id": 9, "fullName": "John", "children": [ { "id": 11, "fullName": "Oliver", "children": [], "rootParentId": 8 }, { "id": 12, "fullName": "Mia", "children": [], "rootParentId": 8 } ], "rootParentId": 8 }, { "id": 10, "fullName": "Mary", "children": [ { "id": 13, "fullName": "Evelyn", "children": [], "rootParentId": 8 } ], "rootParentId": 8 } ], "rootParentId": 8 }, { "id": 14, "fullName": "Eric", "children": [], "rootParentId": null } ]
过滤ID列表
filterList = [7, 12]
预期过滤响应
[ { "id": 1, "fullName": "William", "children": [ { "id": 2, "fullName": "Henry", "children": [ { "id": 3, "fullName": "Matt", "children": [ { "id": 7, "fullName": "Sophi", "children": [], "rootParentId": 1 } ], "rootParentId": 1 } ], "rootParentId": 1 } ], "rootParentId": 1 }, { "id": 8, "fullName": "Olivia", "children": [ { "id": 9, "fullName": "John", "children": [ { "id": 12, "fullName": "Mia", "children": [], "rootParentId": 8 } ], "rootParentId": 8 } ], "rootParentId": 8 } ]
现有代码
实体类(Person)
@Entity @JsonIgnoreProperties({"hibernate_lazy_initializer", "handler"}) @EqualsAndHashCode(onlyExplicitlyIncluded = true) public class Person { @Id @Getter @Setter @EqualsAndHashCode.Include private Long id; @Getter @Setter private String fullName; @ManyToOne(fetch = FetchType.LAZY) @Getter @Setter @JsonIgnore private Person parent; @ManyToOne(fetch = FetchType.LAZY) @Getter @Setter @JsonIgnore private Person rootParent; @Transient @Getter @Setter public List<Person> children = new ArrayList<>(); public Long getRootParentId() { if (parent != null) { return parent.getRootParentId(); } else if (children != null && !children.isEmpty()) { return id; } else return null; } }
仓库接口(PersonRepo)
@Repository public interface PersonRepo extends JpaRepository<Person, Long> { @Query("SELECT p FROM Person p " + " WHERE p.parent.id IS NULL") List<Person> findRoots(); @Query("SELECT p FROM Person p" + " WHERE p.rootParent.id IN :rootIds ") List<Person> findChildrenInRoots(@Param("rootIds") List<Long> rootIds); }
控制器(PersonController)
@RestController @RequestMapping("/api/v1/person") public class PersonController { @Autowired private PersonRepo personRepo; @GetMapping("/tree") @Transactional(readOnly = true) public List<Person> getChildren() { List<Person> rootCategories = personRepo.findRoots(); List<Long> rootCategoryIds = rootCategories.stream().map(Person::getId).collect(Collectors.toList()); List<Person> children = personRepo.findChildrenInRoots(rootCategoryIds); children.forEach(subCategory -> { subCategory.getParent().getChildren().add(subCategory); }); return rootCategories; } }
数据插入语句
insert into PERSON values (1,'William',null, null); insert into PERSON values (2,'Henry',1,1); insert into PERSON values (3,'Matt',2,1); insert into PERSON values (4,'Alisa',2,1); insert into PERSON values (5,'May',1,1); insert into PERSON values (6,'Alexa',4,1); insert into PERSON values (7,'Sophi',3,1); insert into PERSON values (8,'Olivia',null,null); insert into PERSON values (9,'John',8,8); insert into PERSON values (10,'Mary',8,8); insert into PERSON values (11,'Oliver',9,8); insert into PERSON values (12,'Mia',9,8); insert into PERSON values (13,'Evelyn',10,8); insert into PERSON values (14,'Eric',null,null);
实现方案
1. 修改控制器逻辑,添加过滤参数与处理逻辑
改造原接口,接收过滤ID列表,收集目标节点及所有祖先节点的ID,再重新构建符合要求的树形结构:
@RestController @RequestMapping("/api/v1/person") public class PersonController { @Autowired private PersonRepo personRepo; @GetMapping("/tree") @Transactional(readOnly = true) public List<Person> getChildren(@RequestParam(required = false) List<Long> filterIds) { Set<Long> requiredIds = new HashSet<>(); List<Person> allPersons; // 处理过滤逻辑:收集目标节点及所有祖先ID if (filterIds != null && !filterIds.isEmpty()) { for (Long targetId : filterIds) { Person target = personRepo.findById(targetId).orElse(null); if (target == null) continue; requiredIds.add(targetId); // 向上遍历收集所有父节点ID Person parent = target.getParent(); while (parent != null) { requiredIds.add(parent.getId()); parent = parent.getParent(); } } // 查询所有需要保留的节点 allPersons = personRepo.findAllById(requiredIds); } else { // 原逻辑:返回完整树形 List<Person> roots = personRepo.findRoots(); List<Long> rootIds = roots.stream().map(Person::getId).collect(Collectors.toList()); allPersons = new ArrayList<>(roots); allPersons.addAll(personRepo.findChildrenInRoots(rootIds)); } // 构建节点映射,重新组装树形 Map<Long, Person> personMap = allPersons.stream() .peek(p -> p.setChildren(new ArrayList<>())) .collect(Collectors.toMap(Person::getId, p -> p)); List<Person> resultRoots = new ArrayList<>(); for (Person person : allPersons) { if (person.getParent() == null) { resultRoots.add(person); } else { Person parent = personMap.get(person.getParent().getId()); if (parent != null) { parent.getChildren().add(person); } } } // 递归过滤子节点,确保仅保留需要的节点 filterChildren(resultRoots, requiredIds); return resultRoots; } // 递归过滤子节点 private void filterChildren(List<Person> persons, Set<Long> requiredIds) { Iterator<Person> iterator = persons.iterator(); while (iterator.hasNext()) { Person person = iterator.next(); if (!requiredIds.contains(person.getId())) { iterator.remove(); } else { filterChildren(person.getChildren(), requiredIds); } } } }
2. 优化实体类getRootParentId方法
利用已存储的rootParent字段简化逻辑,避免递归调用:
public Long getRootParentId() { if (rootParent != null) { return rootParent.getId(); } // 根节点的情况,返回自身ID return parent == null ? id : null; }
3. 性能优化可选方案
如果数据量较大,可在PersonRepo中添加原生递归SQL查询,一次性获取所有祖先节点,避免循环查询数据库:
@Query(value = "WITH RECURSIVE ancestor AS (" + "SELECT id, parent_id FROM person WHERE id = :nodeId " + "UNION ALL " + "SELECT p.id, p.parent_id FROM person p JOIN ancestor a ON p.id = a.parent_id " + ") SELECT * FROM ancestor", nativeQuery = true) List<Person> findAllAncestors(@Param("nodeId") Long nodeId);
内容的提问来源于stack exchange,提问作者Cleiton Freitas
相关产品推荐
相关产品推荐

