拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Hibernate中的MultipleBagFetchException指南

Hibernate中的MultipleBagFetchException指南

白鹭 - 2021-11-24 450 0 0

1.概述

在本教程中,我们将讨论*MultipleBagFetchException 。*我们将从了解必要的术语开始,然后探索一些解决方法,直到找到理想的解决方案。

我们将创建一个简单的音乐应用程序域,以演示每种解决方案。

2.什么是Hibernate包?

与List相似,Bag是一个可以包含重复元素的集合。但是,这是不正常的。而且,Bag是Hibernate术语,不是Java Collections Framework的一部分。

给定较早的定义,值得强调的是List和Bag都使用java.util.List 。尽管在Hibernate中,两者的处理方式有所不同。为了区分Bag和List ,让我们在实际代码中对其进行查看。

一个包:

// @ any collection mapping annotation

 private List<T> collection;

List

// @ any collection mapping annotation

 @OrderColumn(name = "position")

 private List<T> collection;

3. MultipleBagFetchException

同时在一个实体上提取两个或多个Bags可以形成笛卡尔积。由于Bag没有顺序,因此Hibernate无法将正确的列映射到正确的实体。因此,在这种情况下,它将引发MultipleBagFetchException

我们来看一些导致MultipleBagFetchException的具体示例。

对于第一个示例,让我们尝试创建一个简单的实体,其中包含2个bag,并且两个bag都带有EAGER获取类型。艺术家可能是一个很好的例子。它可以包含歌曲优惠的集合。

鉴于此,让我们创建Artist实体:

@Entity

 class Artist {



 @Id

 @GeneratedValue(strategy = GenerationType.AUTO)

 private Long id;



 private String name;



 @OneToMany(mappedBy = "artist", fetch = FetchType.EAGER)

 private List<Song> songs;



 @OneToMany(mappedBy = "artist", fetch = FetchType.EAGER)

 private List<Offer> offers;



 // constructor, equals, hashCode

 }

如果尝试运行测试,我们将立即遇到MultipleBagFetchException ,它将无法构建Hibernate SessionFactory话虽如此,我们不要这样做。

相反,让我们将集合的一个或两个获取类型转换为惰性:

@OneToMany(mappedBy = "artist")

 private List<Song> songs;



 @OneToMany(mappedBy = "artist")

 private List<Offer> offers;

现在,我们将能够创建和运行测试。虽然,如果我们尝试同时获取这两个bag集合,则仍然会导致MultipleBagFetchException

4.模拟MultipleBagFetchException

在上一节中,我们已经看到了MultipleBagFetchException的原因。在这里,让我们通过创建集成测试来验证这些声明。

为简单起见,让我们使用之前创建Artist

现在,让我们创建集成测试,让我们尝试使用JPQL同时songsoffers

@Test

 public void whenFetchingMoreThanOneBag_thenThrowAnException() {

 IllegalArgumentException exception =

 assertThrows(IllegalArgumentException.class, () -> {

 String jpql = "SELECT artist FROM Artist artist "

 + "JOIN FETCH artist.songs "

 + "JOIN FETCH artist.offers ";



 entityManager.createQuery(jpql);

 });



 final String expectedMessagePart = "MultipleBagFetchException";

 final String actualMessage = exception.getMessage();



 assertTrue(actualMessage.contains(expectedMessagePart));

 }

从断言中,我们遇到了一个IllegalArgumentException 其根本原因是MultipleBagFetchException

5.领域模型

在寻求可能的解决方案之前,让我们看一下必要的领域模型,稍后将用作参考。

假设我们正在处理音乐应用程序的域。鉴于此,让我们将注意力集中在某些实体上:专辑,艺术家用户。

我们已经看到了Artist实体,因此让我们继续其他两个实体。

首先,让我们看一下Album实体:

@Entity

 class Album {



 @Id

 @GeneratedValue(strategy = GenerationType.AUTO)

 private Long id;



 private String name;



 @OneToMany(mappedBy = "album")

 private List<Song> songs;



 @ManyToMany(mappedBy = "followingAlbums")

 private Set<Follower> followers;



 // constructor, equals, hashCode



 }

专辑有一组songs ,同时也可以有一组followers

接下来,这是User实体:

@Entity

 class User {



 @Id

 @GeneratedValue(strategy = GenerationType.AUTO)

 private Long id;



 private String name;



 @OneToMany(mappedBy = "createdBy", cascade = CascadeType.PERSIST)

 private List<Playlist> playlists;



 @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)

 @OrderColumn(name = "arrangement_index")

 private List<FavoriteSong> favoriteSongs;



 // constructor, equals, hashCode

 }

用户可以创建许多playlists 。此外,用户具有一个单独的“ favoriteSongs List ,其中其顺序基于排列索引。

6.解决方法:在单个JPQL查询中Set

首先,让我们强调一下,这种方法将生成笛卡尔乘积,这仅是一种解决方法。 这是因为我们将在单个JPQL查询中同时获取两个集合。相反,使用Set并没有错。如果我们不需要我们的集合有订单或任何重复的元素,则这是适当的选择。

为了演示这种方法,让我们从域模型中引用Album实体。

一个Album实体有两个集合: songsfollowerssongs的收集是袋子的类型。但是,对于**followers,我们使用的是****Set.**话虽如此,即使我们尝试同时获取两个集合,**也不会遇到**MultipleBagFetchException

使用集成测试,让我们尝试在单个JPQL查询中获取其两个集合的同时,按ID Album

@Test

 public void whenFetchingOneBagAndSet_thenRetrieveSuccess() {

 String jpql = "SELECT DISTINCT album FROM Album album "

 + "LEFT JOIN FETCH album.songs "

 + "LEFT JOIN FETCH album.followers "

 + "WHERE album.id = 1";



 Query query = entityManager.createQuery(jpql)

 .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false);



 assertEquals(1, query.getResultList().size());

 }

如我们所见,我们已经成功检索了Album 。这是因为只有songs列表是Bag 。另一方面, followers的集合是Set

附带说明一下,值得强调的是,我们正在使用QueryHints.HINT_PASS_DISTINCT_THROUGH.由于我们使用的是实体JPQL查询,因此可以防止DISTINCT关键字包含在实际的SQL查询中。因此,我们还将在其余方法中使用此查询提示。

7.解决方法:在单个JPQL查询中List

与上一节类似,这还会生成笛卡尔积,这可能会导致性能问题。同样,将List, Set,或Bag用作数据类型也没有错。本节的目的是进一步说明,如果Bag类型不多,则Hibernate可以同时获取集合。

对于这种方法,让我们使用域模型中User

如前所述, User有两个集合: playlistsfavoriteSongs该**playlists都没有定义的顺序,使其成为一个袋子收集。但是,对于favoriteSongs List** ,其顺序取决于User排列方式。如果我们仔细查看FavoriteSong实体,则arrangementIndex属性使之成为可能。

同样,使用单个JPQL查询,让我们尝试验证是否能够在同时获取playlistsfavoriteSongs歌曲的同时检索所有用户。

为了演示,让我们创建一个集成测试:

@Test

 public void whenFetchingOneBagAndOneList_thenRetrieveSuccess() {

 String jpql = "SELECT DISTINCT user FROM User user "

 + "LEFT JOIN FETCH user.playlists "

 + "LEFT JOIN FETCH user.favoriteSongs ";



 List<User> users = entityManager.createQuery(jpql, User.class)

 .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)

 .getResultList();



 assertEquals(3, users.size());

 }

从该断言中,我们可以看到我们已经成功检索了所有用户。而且,我们没有遇到MultipleBagFetchException 。这是因为即使我们同时获取两个集合,但只有playlists才是包集合。

8.理想的解决方案:使用多个查询

从前面的变通方法中,我们已经看到了使用单个JPQL查询来同时检索集合。不幸的是,它会生成笛卡尔积。我们知道这并不理想。所以在这里,让我们解决MultipleBagFetchException而不用牺牲性能。

假设我们正在处理一个实体,该实体具有多个bag collection。在我们的情况下,它是**Artist实体。它有两个手袋收藏: songsoffers 。**

在这种情况下,我们甚至无法使用单个JPQL查询同时获取两个集合。这样做将导致MultipleBagFetchException 。相反,让我们将其分为两个JPQL查询。

通过这种方法,我们期望一次成功地获取两个手袋集合。

同样,最后一次,让我们快速创建一个集成测试以检索所有艺术家:

@Test

 public void whenUsingMultipleQueries_thenRetrieveSuccess() {

 String jpql = "SELECT DISTINCT artist FROM Artist artist "

 + "LEFT JOIN FETCH artist.songs ";



 List<Artist> artists = entityManager.createQuery(jpql, Artist.class)

 .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)

 .getResultList();



 jpql = "SELECT DISTINCT artist FROM Artist artist "

 + "LEFT JOIN FETCH artist.offers "

 + "WHERE artist IN :artists ";



 artists = entityManager.createQuery(jpql, Artist.class)

 .setParameter("artists", artists)

 .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)

 .getResultList();



 assertEquals(2, artists.size());

 }

从测试中,我们首先获取所有歌手,同时获取其songs

然后,我们创建了另一个查询以获取艺术家的offers

使用这种方法,我们避免了MultipleBagFetchException以及笛卡尔积的形成。

9.结论

在本文中,我们详细MultipleBagFetchException我们讨论了必要的词汇表和此异常的原因。然后,我们对其进行了仿真。在那之后,我们讨论了一个简单的音乐应用程序的领域,以针对我们的每个变通方法和理想的解决方案提供不同的方案。最后,我们建立了几个集成测试来验证每种方法。

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *