Hibernate中的MultipleBagFetchException指南

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我們討論了必要的詞彙表和此異常的原因。然後,我們對其進行了仿真。在那之後,我們討論了一個簡單的音樂應用程序的領域,以針對我們的每個變通方法和理想的解決方案提供不同的方案。最後,我們建立了幾個集成測試來驗證每種方法。