JPA :如何定义3个级别的@NamedEntityGraph?

2022-09-02 13:36:10

我有3个实体。分支,主题,主题。分支具有主题列表,主题具有主题列表。此外,subjectList 和 topicList 都是懒惰的。我想在单个查询中获取所有分支,包括其主题和主题。

1.

@Entity
public class Branch implements Serializable 
{
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    private String name;

    @OneToMany(mappedBy = "branch")
    private List<Subject> subjectList;
    //Getters and Setters
}

2.

@Entity
public class Subject implements Serializable 
{
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String name;

    @ManyToOne()
    private Branch branch;

    @OneToMany(mappedBy = "subject")
    private List<Topic> topicList;
    //Getters and Setters       
}

3.

@Entity
public class Topic implements Serializable 
{
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    private String name;

    @ManyToOne()
    private Subject subject;
    //Getters and Setters
}

我尝试了下面的方法,但它不起作用。

@NamedEntityGraph(name="branch_subject", 
attributeNodes = {
    @NamedAttributeNode(value="name"),
    @NamedAttributeNode(value="subjectList", subgraph = "subjectListGraph")
},
subgraphs = {
    @NamedSubgraph(name="subjectListGraph",
            attributeNodes = {
                @NamedAttributeNode(value="name"),
                @NamedAttributeNode(value = "topicList", subgraph = "topicListGraph")
            }
    ),
    @NamedSubgraph(name="topicListGraph",
            attributeNodes = {
                    @NamedAttributeNode("name")
            }
    )
}
)

下面的代码也用于从数据库获取数据,我使用JPQL,如下所示

    EntityGraph branchEntityGraph = entityManager
                .getEntityGraph("branch_subject");

        Branch branch = entityManager
                .createQuery("SELECT b from Branch b WHERE b.id=:ID",
                        Branch.class)
                .setHint("javax.persistence.loadgraph", branchEntityGraph)
                .setParameter("ID", branch1.getId()).getResultList().get(0);

这给出了以下例外情况

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags

答案 1

Hibernate不允许你获取多个袋子,因为它最终会获取笛卡尔积。

M → N → P 一对多或多对多关系

对于多嵌套层次结构,只要列表映射为 ,就可以在多个集合上使用。JOIN FETCHSet

M → N 和 M → P 一对多或多对多关系

对于同级集合(如 M → N 和 M → P),不要切换到 using 而不是 。SetList

使用 a 而不是 a 来避免 MultipleBagFetchException 是一个非常糟糕的主意,因为你仍然会得到一个笛卡尔积,这会导致性能问题,因为你要获取记录。SetListM x N x P

在这种情况下,更好的方法是使用第一个查询获取一个集合,并对其余集合使用其他查询:

List<Post> _posts = entityManager.createQuery("""
    select distinct p
    from Post p
    left join fetch p.comments
    where p.id between :minId and :maxId
    """, Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

_posts = entityManager.createQuery("""
    select distinct p
    from Post p
    left join fetch p.tags t
    where p in :posts
    """, Post.class)
.setParameter("posts", _posts)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

此策略允许您通过提取记录来避免结果集。M x N x PM x (N + P)

从子端获取到父端

如果在获取子集合时必须使用 INNER JOIN,则可以简单地 [从最里面的子集合提取到根][3],然后重新组合结构。这要高效得多,因为查询如下所示:

select t 
from Topic t
join t.subject s
join s.branch b

答案 2

该解决方案相当简单,使用JPA 2.1,您只需要在JPA存储库中添加entiry grapgh

@Repository
public interface BranchRepo extends JpaRepository< Branch, Long> {

@EntityGraph(attributePaths = {"subjectList" , 
"subjectList.topicList"})
List<Product> findAll();
}

这将在 Branch-> SubjectList-> TopicList 的双向映射之间创建 Join

块引用您需要用ID覆盖每个实体类中的等于方法,否则这将不起作用。您可以在不需要的每个实体类上摆脱复杂的多级命名实体图


推荐