JPA 2是否有运行递归查询的机制?
这是我的情况:我有一个实体E,其中包含一个名为x的整数字段。 它还可能拥有类型为E的子级,通过@OneToMany进行映射。 我想做的是通过主键找到一个E,并获取其x值及所有后代的x值。 是否有办法在单个查询中完成此操作?
我正在使用Hibernate 3.5.3,但我不希望显式依赖于Hibernate API。
编辑:根据此文章,Hibernate没有此功能,至少在三月份没有。 因此,JPA似乎也不会有这个功能,但我想确保。
使用简单的邻接模型,其中每一行包含对其父项的引用,该引用将引用同一表中的另一行,与JPA不协作良好。这是因为JPA不支持使用Oracle CONNECT BY子句或SQL标准WITH语句生成查询。如果没有这两个子句中的任何一个,那么就无法真正地发挥邻接模型的用途。
但是,还有一些其他方法可以应用于这个问题的建模。第一种方法是材料化路径模型。这是将节点的完整路径展开到单个列中的模型。表定义如下:
CREATE TABLE node (id INTEGER,
path VARCHAR,
parent_id INTEGER REFERENCES node(id));
插入一棵节点树的代码大致如下:
INSERT INTO node VALUES (1, '1', NULL); -- Root Node
INSERT INTO node VALUES (2, '1.2', 1); -- 1st Child of '1'
INSERT INTO node VALUES (3, '1.3', 1); -- 2nd Child of '1'
INSERT INTO node VALUES (4, '1.3.4', 3); -- Child of '3'
要获取节点'1'及其所有子级,查询语句如下:
SELECT * FROM node WHERE id = 1 OR path LIKE '1.%';
要将此映射到JPA,只需将“path”列作为持久化对象的属性。但是您将需要进行簿记以保持“path”字段的最新状态。 JPA / Hibernate不会为您执行此操作。例如,如果将节点移动到不同的父级,则必须同时更新父级引用并从新父对象确定新路径值。
另一种方法称为“嵌套集模型”,但稍微复杂一些。可能最好由其发明者进行描述(而不是由我直接添加)。
还有第三种方法称为嵌套间隔模型,但是这种方法对于实现具有重大依赖性存储过程。
关于此问题的更完整解释,请参阅The Art of SQL的第7章。
我知道这个问题很久了,但因为它被链接到另一个问题中,所以我想就此问题进行更新。现在Blaze-Persistence支持在JPA模型上使用递归CTE。
Blaze-Persistence是一个建立在JPA之上的查询构建器,支持许多高级的DBMS功能。要建模CTEs或递归CTEs(即您在此处所需的内容),首先需要引入一个CTE实体来建模CTE的结果类型。
@CTE
@Entity
public class GroupCTE {
@Id Integer id;
}
List<Group> groups = criteriaBuilderFactory.create(entityManager, Group.class)
.withRecursive(GroupCTE.class)
.from(Group.class, "g1")
.bind("id").select("g1.id")
.where("g1.parent").isNull()
.unionAll()
.from(Group.class, "g2")
.innerJoinOn(GroupCTE.class, "cte")
.on("cte.id").eq("g2.parent.id")
.end()
.bind("id").select("g2.id")
.end()
.from(Group.class, "g")
.fetch("groups")
.where("g.id").in()
.from(GroupCTE.class, "c")
.select("c.id")
.end()
.getResultList();
WITH RECURSIVE GroupCTE(id) AS (
SELECT g1.id
FROM Group g1
WHERE g1.parent_group_id IS NULL
UNION ALL
SELECT g2.id
FROM Group g2
INNER JOIN GroupCTE cte ON g2.parent_group_id = cte.id
)
SELECT *
FROM Group g
LEFT JOIN Group gsub ON gsub.parent_group_id = g.id
WHERE g.id IN (
SELECT c.id
FROM GroupCTE c
)
您可以在文档中了解有关递归CTE的更多信息:https://persistence.blazebit.com/documentation/core/manual/en_US/index.html#recursive-ctes
我曾遇到过这样的问题,需要从一个表中查询菜单节点。解决方法是:假设我们有一个名为Node的类,创建了一个单向一对多的关联,如下所示:
@OneToMany( fetch = FetchType.EAGER)
@JoinColumn(name = "parent_id", referencedColumnName = "id")
private List<Node> subNodeList;
boolean isRoot
的字段,以说明此节点是否为根菜单项,然后通过查询 isRoot
为 true 的节点,我们就可以获得顶级节点,因为使用了 FetchType.EAGER
,我们也会获取到列表中的子节点。
这将导致多个查询,但对于小型菜单之类的东西来说,这应该没问题。