【问题标题】:Recursive JPA query?递归 JPA 查询?
【发布时间】:2011-04-07 23:23:46
【问题描述】:

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

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

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


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

【问题讨论】:

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


    【解决方案1】:

    使用简单的邻接模型,其中每一行都包含对其父级的引用,该父级将引用同一表中的另一行,这与 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 不会为您执行此操作。例如。如果将节点移动到不同的父节点,则必须同时更新父引用并确定新父对象的新路径值。

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

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

    The Art of SQL 的第 7 章对此问题进行了更完整的解释。

    【讨论】:

    • 这比我要找的要复杂得多,但感谢您提供的信息丰富的回复。物化路径模型还有一个更简单的替代方案 - 让每个节点存储它所属的树的根节点的 ID。这意味着您只能使用根节点作为查询的起点,但在我的具体情况下,这不是问题 - 所以这可能就是我要做的。
    • 您可以使用通过 JPA 管理的邻接列表,但使用本机查询来使用递归 SQL 功能。它会有点恶心,而且不便携,但它会是两全其美的东西。嵌套集是可移植的,但很繁琐,并且对于一些相当常见的查询类型表现不佳。
    • JPA 2.0 有什么改变...例如,我使用 Native NamedQuery 在我的查询中有一个“CONNECT BY”子句。我已经尝试过了,它确实返回了孩子,但不是以分层列表的形式......只是扁平的孩子。有什么想法吗?
    • 我将使用需要假根的 JPA 持久上下文 tikalk.com/java/load-a-tree-with-jpa-and-hibernate
    • 嘿。你能更新Nested Set Model的链接吗?
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:

        我知道这个问题已经过时了,但由于它与另一个问题相关联,因此我想对此进行更新,因为 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

        【讨论】:

          猜你喜欢
          • 2015-03-14
          • 2018-10-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-29
          • 2011-03-17
          • 2020-07-01
          • 2017-08-13
          相关资源
          最近更新 更多