【问题标题】:How to fetch two-level deep @OneToMany association in Spring Data and avoid MultipleBagFetchException如何在 Spring Data 中获取两级深度 @OneToMany 关联并避免 MultipleBagFetchException
【发布时间】:2023-02-09 22:38:28
【问题描述】:

我仍然是 JPA 初学者,想知道如何最好地使用 Spring 强大的功能来获取以下简单结构(默认情况下关联是惰性的,但我有一个用例,整个结构应该在没有代理的情况下加载,可能使用生成的 SQL 查询数量最少)。有关的简化实体:

@Entity
public class Bundle {

    @Id
    private Long id;

    @OneToMany(mappedBy = "bundle", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Group> groups = new ArrayList<>();
}

...

@Entity
public class Group {

    @Id
    private Long id;

    @ManyToOne()
    @JoinColumn(name = "BUNDLE_ID")
    private Bundle bundle;

    @OneToMany(mappedBy = "group", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Element> elements = new ArrayList<>();

...

public class Element {

    @Id
    private Long id;

    @ManyToOne
    @JoinColumn(name = "GROUP_ID")
    private Group group;
}

我试图找到给定包下的所有组和元素(为了有效地处理它们并稍后在从端点返回之前转换为 DTO)是在 @Query 中获取

public interface BundleRepository extends JpaRepository<Bundle, Long> {

@Query("SELECT bundle FROM Bundle bundle "
           + "JOIN FETCH bundle.groups groups "
           + "JOIN FETCH groups.elements "
           + "WHERE bundle.id = :id")
    Optional<Bundle> fetchBundle(@Param("id") long id);
}

但是,这会导致 org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags。我对这个主题做了一些阅读,发现将 Lists 更改为 Sets 可能会奏效,另一方面,一些消息来源不鼓励这样做。

这个双@OneToMany结构看起来很普通,多个JOINs也没有什么不寻常的,但无论如何我想请你指出正确的方法。也许分别为一个包获取组,然后为每个组获取其元素?这将是1 + 组数查询,是不是有点浪费?如果以这种方式将此视为一种权衡是朝着好的方向迈出的一步,请告诉我。

【问题讨论】:

    标签: java spring spring-data-jpa spring-data


    【解决方案1】:

    假设一个。集合中不允许重复,b。您不关心 groupselements 实体对象集合中实体的迭代顺序(在使用它们的某些属性转换为 DTO 时您仍然可以强制排序),那么标题答案是:

    1. 是的,从 List 更改为 Set,它既与调用者通信,又与不允许重复且迭代顺序可以未定义的 JPA 实现通信(这也将解决包异常 - 但代价是巨大的交叉连接)。

    2. 为避免同时加载两个集合可能导致的巨大交叉连接,请在 2 个查询中加载图形(只要两者都参与同一事务):

      @Query("SELECT bundle FROM Bundle bundle "
                 + "JOIN FETCH bundle.groups groups "
                 + "WHERE bundle.id = :id")
          Optional<Bundle> fetchBundle(@Param("id") long id);
      }
      

      然后第二次调用以填充组中的元素:

      @Query("SELECT groups FROM Group groups "
                 + "JOIN FETCH groups.elements "
                 + "WHERE groups.bundle.id = :id")
          Collection<Group> fetchBundleGroups(@Param("id") long id);
      }
      

      请注意,您实际上可以忽略第二次调用的响应值,只需执行...

      Optional<Bundle> maybeBundle = repository.fetchBundle(id);
      repository.fetchBundleGroups(id);
      // now, maybeBundle.get().groups.elements will all be populated
      

      由于 JPA 规范的一个有用特性,它要求在给定的 JPA EntityManager 持久性上下文范围内(通常与事务范围相同),为具有相同 ID 的实体返回相同的对象实例,因此来自第二个查询(填充 Group 对象上的元素)将使用从第一个查询返回的相同实例。

      值得注意的是,如果其中一个实体上有多个集合,同样的技术也适用——例如Bundle.authors - 您只需添加第三个查询来填充 Bundle 上的作者集合并调用它以避免 authorsgroups 之间的交叉连接。

    【讨论】:

      猜你喜欢
      • 2011-05-09
      • 1970-01-01
      • 2015-05-07
      • 2021-12-27
      • 1970-01-01
      • 2014-10-08
      • 2016-12-12
      • 2017-10-07
      • 2016-09-25
      相关资源
      最近更新 更多