递归JPA查询?

时间:2010-09-03 17:25:51

标签: java orm jpa-2.0 jpql recursive-query

JPA 2是否有任何运行递归查询的机制?

这是我的情况:我有一个实体E,它包含一个整数字段x。它也可能有类型E的子节点,通过@OneToMany映射。我想做的是通过主键找到一个E,并获得它的x值以及它所有后代的x值。有没有办法在单个查询中执行此操作?

我正在使用Hibernate 3.5.3,但我不希望对Hibernate API有任何明确的依赖。


编辑:根据this项目,Hibernate 具有此功能,或者至少在3月没有。所以JPA似乎不太可能拥有它,但我想确定。

4 个答案:

答案 0 :(得分:24)

使用简单的邻接模型,其中每行包含对其父项的引用,这将引用同一表中的另一行,但与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'

所以要获得Node'1'及其所有子节点的查询是:

SELECT * FROM node WHERE id = 1 OR path LIKE '1.%';

要将此映射到JPA,只需将“path”列设为持久对象的属性即可。但是,您必须进行簿记才能使“路径”字段保持最新状态。 JPA / Hibernate不会为你做这件事。例如。如果将节点移动到另一个父节点,则必须更新父节点引用并从新父节点确定新路径值。

另一种方法称为嵌套集模型,这有点复杂。它的创始人可能是最好的described(而不是我逐字添加)。

还有第三种方法称为嵌套间隔模型,但是这很大程度上依赖于存储过程来实现。

The Art of SQL的第7章描述了对这个问题的更完整的解释。

答案 1 :(得分:5)

这篇文章中的最佳答案对我来说似乎是一次大规模的破解。我已经不得不处理数据模型,其中杰出的工程师认为在数据库领域中编写树形大亨的代码是一个好主意,例如:“Europe | Uk | Shop1 | John”以及这些表中的大量数据。不足为奇,MyHackedTreeField LIKE'parentHierharchy%'形式的查询表现为杀手。 解决这类问题最终需要创建树层次结构的内存缓存以及其他许多...

如果您需要运行递归查询并且数据量不大......让您的生活变得简单,只需加载运行计划所需的数据库字段即可。并在java中编写递归代码。 除非你有充分的理由这样做,否则不要在DB中创建它。

即使您拥有的数据量很大,您也很可能将问题细分为独立的递归树批次,并在不需要一次加载所有数据的情况下及时处理这些数据。

答案 2 :(得分:0)

我遇到这样的问题,从一个表中查询菜单节点, 我建立的方式是这样的: 假设我们有一个名为Node的类,创建了一个Unidirectional One-to-Many关联,如下所示:

    @OneToMany(  fetch = FetchType.EAGER)
    @JoinColumn(name = "parent_id", referencedColumnName = "id")
    private List<Node> subNodeList;

在实体中也有一个名为boolean isRoot的字段,以提及此节点是否为根菜单项, 然后,通过查询存在节点是否为true的节点,我们仅获得顶部节点,并且由于FetchType.EAGER,我们还获得了List中的子节点。 这将导致多个查询,但是对于小菜单之类的事情来说可以。

答案 3 :(得分:0)

我知道这个问题很旧,但是由于它与另一个问题相关联,因此我想对此进行更新,因为Blaze-Persistence支持在JPA模型之上使用递归CTE。< / p>

Blaze-Persistence是JPA之上的查询构建器,它支持JPA模型之上的许多高级DBMS功能。要在此处建模CTE或递归CTE,首先需要引入一个对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();

这将呈现为如下所示的SQL

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