15

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

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

我使用的是 Hibernate 3.5.3,但我不希望对 Hibernate API 有任何显式依赖。


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

4

4 回答 4

29

使用简单的邻接模型,其中每一行都包含对其父级的引用,该父级将引用同一表中的另一行,这与 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,只需将“路径”列作为持久对象的属性。但是,您必须进行簿记以使“路径”字段保持最新。JPA/Hibernate 不会为您执行此操作。例如,如果您将节点移动到不同的父级,您将必须更新父级引用并确定来自新父级对象的新路径值。

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

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

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

于 2010-09-03T21:00:22.727 回答
9

这篇文章中的最佳答案对我来说似乎是一个巨大的变通方法。我已经不得不处理数据模型,出色的工程师认为将 DB 字段中的 Tree Hiarchies 编码为文本是一个好主意,例如:“Europe|Uk|Shop1|John”,并且这些表中有大量数据. 不出所料,MyHackedTreeField LIKE 'parentHierharchy%' 形式的查询性能在哪里杀手。解决此类问题最终需要创建树层次结构的内存缓存以及许多其他问题......

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

即使您拥有的数据量很大,您也很可能可以将问题细分为独立的递归树批次并一次处理这些批次,而无需一次加载所有数据。

于 2016-02-02T13:48:19.797 回答
1

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

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

在实体中也有一个名为 boolean isRoot 的文件,以提及该节点是否为根菜单项,然后通过查询存在 isRoot 为 true 的节点,我们只获得顶部节点,并且由于FetchType.EAGER,我们还获得子节点列表。这将导致多次查询,但对于小菜单之类的东西就可以了。

于 2019-09-22T15:18:50.917 回答
1

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

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

于 2020-05-25T07:25:23.287 回答