Hibernate中如何将递归查询结果自动映射为完整Category树形实体
问题描述
我们有一个Category实体,每个分类可包含0个或多个子分类(对应subcategories集合字段)。通过**公共表表达式(CTE)**编写的递归查询已能正确返回指定根节点下的全部分类列表,但希望找到一种优雅的方式,将查询结果自动映射为包含所有层级子分类的单个根Category实体,而非手动处理结果。已知@SqlResultSetMapping或实体图无法实现此需求。
简化代码示例
分类实体类
@Entity @Table(name = "CATEGORY") public class CategoryEntity { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) @Column(name = "C_I_IDF") protected Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="C_PARENTCATEGORY") private CategoryEntity parentCategory; @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentCategory", cascade = { CascadeType.MERGE, CascadeType.REMOVE }) @Fetch(value = FetchMode.SELECT) @BatchSize(size=10) private List<CategoryEntity> subCategories = new ArrayList<>(); // 省略getter/setter方法 }
递归查询语句
WITH categoryrecursive (id) AS ( SELECT C_I_IDF FROM CATEGORY parent WHERE parent.C_I_IDF = <根节点ID> UNION ALL SELECT sub.C_I_IDF FROM categoryrecursive, CATEGORY sub WHERE sub.C_PARENTCATEGORY = categoryrecursive.id ) SELECT cat.* FROM categoryrecursive rc INNER JOIN CATEGORY cat ON cat.C_I_IDF = rc.id
优雅解决方案
方案1:利用JPA持久化上下文自动关联
JPA的持久化上下文(EntityManager)会自动维护实体间的关联关系。当你通过查询加载所有分类实体后,上下文会根据parentCategory的映射自动填充subCategories集合:
- 执行CTE查询获取全部分类:
List<CategoryEntity> allCategories = entityManager.createNativeQuery( "WITH categoryrecursive (id) AS (...)" // 填入完整CTE语句 , CategoryEntity.class ) .setParameter("rootId", rootId) .getResultList();
- 从结果中筛选根节点:
CategoryEntity root = allCategories.stream() .filter(cat -> cat.getParentCategory() == null || cat.getId().equals(rootId)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("根节点不存在"));
此时根节点的subCategories已自动组装好完整层级结构,无需手动处理。
注意:需确保实体的
equals/hashCode实现正确,且查询全程使用同一个EntityManager实例。
方案2:自定义Hibernate ResultTransformer
如果使用Hibernate作为JPA实现,可以自定义结果转换器直接组装树形结构:
public class CategoryTreeTransformer implements ResultTransformer { private final Map<Long, CategoryEntity> categoryMap = new HashMap<>(); private CategoryEntity root; @Override public Object transformTuple(Object[] tuple, String[] aliases) { CategoryEntity category = (CategoryEntity) tuple[0]; categoryMap.put(category.getId(), category); // 关联子分类到父分类 if (category.getParentCategory() != null) { CategoryEntity parent = categoryMap.get(category.getParentCategory().getId()); if (parent != null) { parent.getSubCategories().add(category); } } else { root = category; // 无父分类的即为根节点 } return category; } @Override public List transformList(List collection) { return Collections.singletonList(root); } }
使用方式:
CategoryEntity root = (CategoryEntity) entityManager.createNativeQuery( "WITH categoryrecursive (id) AS (...)" , CategoryEntity.class ) .setParameter("rootId", rootId) .unwrap(org.hibernate.query.Query.class) .setResultTransformer(new CategoryTreeTransformer()) .getSingleResult();
方案3:使用Blaze-Persistence实现自动树形映射
Blaze-Persistence是增强JPA的工具库,支持将递归CTE查询直接映射为树形实体结构,无需手动组装:
CTE<CategoryEntity> recursiveCte = cte(CategoryEntity.class) .withRecursive("categoryrecursive") .from(CategoryEntity.class, "parent") .where("parent.id").eq(rootId) .select("parent.id") .unionAll() .from(CategoryEntity.class, "sub") .innerJoinOn("categoryrecursive", "rc").on("sub.parentCategory.id").eqExpression("rc.id") .select("sub.id") .end(); CategoryEntity root = criteriaBuilder.from(CategoryEntity.class, "cat") .innerJoinOn(recursiveCte, "rc").on("cat.id").eqExpression("rc.id") .fetch("subCategories", JoinType.LEFT) .where("cat.id").eq(rootId) .getSingleResult();
内容的提问来源于stack exchange,提问作者Bruno Vandekerkhove
相关产品推荐
相关产品推荐

