JPA / Hibernate加载延迟子实体并获取子孙实体

时间:2018-01-25 17:02:45

标签: hibernate jpa

我想如果有一种方法可以微调JPA / Hibernate来管理以下用例。 我有以下对象模型:

class Parent {
    ...
    @OneToMany(mappedBy = "definizione", fetch = FetchType.LAZY)
    private List<Child> childs; 
    ... 
}

class Child {
    ...
    @ManyToOne(fetch = FetchType.LAZY)
    private GrandChild grandChild;
    ...
}


class GrandChild {
 ...
}

然后我执行以下代码:

Parent parent = entityManager.find(id, Parent.class); // (1) 
List<Child> childs = parent.getChilds(); // (2)
GrandChild grandChild = null;
for(Child child : childs) {
     grandChild = child.getGrandChild(); // (3)
    //do somthing with childs
}

我想要达到的目标是:

  1. 基于用例调整hibernate,即不更改实体类;
  2. 让hibernate执行两个查询:

    select parent0_.* -- all Parent columns
    from PARENTS parent0_ 
    where parent0_.ID=?
    
    SELECT 
        childs0_.* -- all Child columns
        grandchild1_.* -- all GrandChild columns
    FROM childs childs0_
    LEFT OUTER JOIN grand_childs grandchild1_ ON childs0_.grand_child_id = grandchild1_.id
    WHERE childs0_.parent_id =?
    
  3. 上面使用Child - GrandChild懒惰提取的代码段的基本行为是:

    • (1)执行Parent实体的一个查询;
    • (2)执行Child实体的所有Parent实体的一个查询;
    • (3)n查询,每个GrandChild实体一个。

    通过阅读Hibernate Fetching,我找到了以下解决方案,但没有一个达到我想要的目的:

    更改实体类提取策略

    class Child {
        ...
        @ManyToOne(fetch = FetchType.EAGER)
        private GrandChild grandChild;
        ...
    }
    

    PROS :执行的查询数量是所需的数量;
    缺点:此解决方案影响其他用例,由于某些原因我不想更改实体类级别的抓取策略。

    通过查询动态提取

    这种情况对jpql和Crieria查询都有效。

    final Parent parent = entityManager.createQuery(
            "SELECT p FROM Parent p LEFT JOIN FETCH p.childs c JOIN FETCH c.grandChild WHERE p.id = :id",
            Parent.class
    )
            .setParameter("id", id)
            .getSingleResult();
    
    List<Child> childs = parent.getChilds(); 
    GrandChild grandChild = null;
    for (Child child : childs) {
        grandChild = child.getGrandChild();
        //do somthing with childs
    }
    

    执行的查询是:

    SELECT parent0_.*,    -- all Parent fields
        childs1_.*,    -- all Child fields
        grandchild2_.* -- all GrandChild fields
    FROM parents parent0_
        LEFT OUTER JOIN childs childs1_ ON parent0_.id = childs1_.parent_id
        LEFT JOIN grand_childs grandchild2_ ON childs1_.grand_child_id = grandchild2_.id
    WHERE parent0_.id =?
    

    PROS :只执行了一次查询 CONS :从db加载了大量重复数据,我不想再加载一次Parent实体。

    通过JPA实体图提取动态

    @Entity 
    @NamedEntityGraph(
            name = "parent.childs.grandchild",
            attributeNodes = {
                    @NamedAttributeNode(value = "childs", subgraph = "childs.grandchild")
            },
            subgraphs = {
                    @NamedSubgraph(
                            name = "childs.grandchild",
                            attributeNodes = {
                                    @NamedAttributeNode(value = "grandChild")
                            }
                    )
            }
    )
    public class Parent extends BaseEntity{
    ...
    }
    

    要加载的代码:

        final Parent parent = entityManager.find(
                Parent.class,
                id,
                Collections.singletonMap(
                        "javax.persistence.fetchgraph",
                        entityManager.getEntityGraph( "parent.childs.grandchild" )
                )
        );
        List<Child> childs = parent.getChilds();
        GrandChild grandChild = null;
        for (Child child : childs) {
            grandChild = child.getGrandChild();
            //do somthing with childs
        }   
    

    执行的查询与通过查询动态提取相同,因此优缺点相同。

1 个答案:

答案 0 :(得分:2)

您可以使用JOIN FETCH执行查询并一次性获取所有内容。

String hql = "SELECT parent FROM Parent parent " +
            "LEFT JOIN FETCH parent.child child " +
            "JOIN FETCH child.grandChild " +
            "WHERE parent.id = :parentId";

Parent parent = entityManager.createQuery(hql, Parent.class).getSingleResult()
                             .setInteger("parentId", parentId);
List<Child> childs = parent.getChilds();

for(Child child : childs) {
    GrandChild grandChild = child.getGrandChild();
    //more code...
}

查看此文档:https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/fetching/Fetching.html