java - 如何使用 JPA 获取整个实体图

标签 java jpa orm openjpa entitygraph

我使用 JPA 2.0 和 OpenJPA 作为底层实现。 我有一个实体映射到自身以表示实体之间的父子层次结构。一个实体可以有多个子实体,但最多只有一个父实体。因此,没有父实体的实体位于层次结构的顶部。 我的目标是从数据表中获取所有层次结构。所以我有如下查询:

SELECT e FROM MyEntity e where e.parent is null

在 MyEntity 中,我完成了如下映射:

@ManyToOne
@JoinColumn(name="PARENT")
private MyEntity parent;

@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
private List<MyEntity> children;

当程序运行时,层次结构顶部的实体填充了它们的所有子实体,但子实体没有获取它们的子实体。我认为 EAGER fetch 会填充整个实体图。但它是不是这样。 在 JPA 2.1 中有 EntityGraph ASAIK 的特性。但是如何在 JPA 2.0 中实现呢?

最佳答案

EAGER 仅在一个级别上工作,并非旨在获取树状结构。

我建议您将一对多提取更改为 LAZY,因为 EAGER fetching is a code smell ,并像这样获取根实体:

SELECT e 
FROM MyEntity e 
LEFT JOIN FETCH e.children 
where e.parent is null

您使用递归来获取所有带有附加子选择的图表。

void fetchAll(MyEntity root) {
    for(MyEntity child : root.children) {
        fetchAll(child);
    }
}

一种更有效的方法是完全放弃子集合并使用 recursive CTE在 FK 关联上获取给定树中所有实体的所有 ID。然后使用第二个 JPA 查询,您可以通过 ID 获取所有实体,并通过 matching the parents 重建树。 .

更新实际解决方案

我添加了一个 test on GitHub为此提供解决方案。 考虑以下实体:

@Entity(name = "Node")
public class Node  {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "parent_id")
    private Node parent;

    //This must be transient as otherwise it will trigger an additional collection fetch
    //@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    @Transient
    private List<Node> children = new ArrayList<>();

    public Node() {}

    public Node getParent() {
        return parent;
    }

    public List<Node> getChildren() {
        return children;
    }

    public void addChild(Node child) {
        children.add(child);
        child.parent = this;
    }
}

下面的transformer可以按照你的意愿重构整棵树

Node root = (Node) doInHibernate(session -> {
    return session
        .createSQLQuery(
                "SELECT * " +
                "FROM Node " +
                "CONNECT BY PRIOR id = parent_id " +
                "START WITH parent_id IS NULL ")
        .addEntity(Node.class)
        .setResultTransformer(new ResultTransformer() {
            @Override
            public Object transformTuple(Object[] tuple, String[] aliases) {
                Node node = (Node) tuple[0];
                if(node.parent != null) {
                    node.parent.addChild(node);
                }
                return node;
            }

            @Override
            public List transformList(List collection) {
                return Collections.singletonList(collection.get(0));
            }
        })
        .uniqueResult();
});

关于java - 如何使用 JPA 获取整个实体图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34371128/

相关文章:

jakarta-ee - "JBAS016069: Error injecting persistence unit into CDI managed bean."部署依赖于 EJB-JAR 的 WAR 时

java - 映射父类(super class)和命名查询

java - 如何获取 JPA 自定义查询的结果作为页面

grails - 使用正确的Grails ORM(GORM)实现是什么?

java - 如何模拟特定的阅读文件?

java - 使用 Google App Engine 的新手

java - 高效的数据库操作

java - 如何在java中获得小数点位置的最大数字

java - 我得到一个 PersistenceException,我不明白为什么

c# - yield 是如何实现延迟加载的模式的?