[BAEL-4056] Guide to MultipleBagFetchException (#10294)

* Adds Domain Models
* Adds Integration Tests
This commit is contained in:
Emmanuel Yasa 2020-12-01 15:22:46 +08:00 committed by GitHub
parent 06bebff4cb
commit cd6f187e0a
10 changed files with 572 additions and 0 deletions

View File

@ -0,0 +1,52 @@
package com.baeldung.jpa.multiplebagfetchexception;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@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<User> followers;
Album(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Album album = (Album) o;
return Objects.equals(id, album.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
protected Album() {
}
}

View File

@ -0,0 +1,56 @@
package com.baeldung.jpa.multiplebagfetchexception;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Entity
class Artist {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@OneToMany(mappedBy = "artist")
private List<Song> songs;
@OneToMany(mappedBy = "artist", cascade = CascadeType.PERSIST)
private List<Offer> offers;
Artist(String name) {
this.name = name;
this.offers = new ArrayList<>();
}
void createOffer(String description) {
this.offers.add(new Offer(description, this));
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Artist artist = (Artist) o;
return Objects.equals(id, artist.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
protected Artist() {
}
}

View File

@ -0,0 +1,43 @@
package com.baeldung.jpa.multiplebagfetchexception;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.List;
import java.util.Objects;
@Entity
class DummyEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ElementCollection
private List<String> collection1;
@ElementCollection
private List<String> collection2;
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
DummyEntity that = (DummyEntity) o;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
protected DummyEntity() {
}
}

View File

@ -0,0 +1,52 @@
package com.baeldung.jpa.multiplebagfetchexception;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import java.util.Objects;
@Entity
class FavoriteSong {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne
private Song song;
@ManyToOne
private User user;
@Column(name = "arrangement_index", nullable = false)
private int arrangementIndex;
FavoriteSong(Song song, User user) {
this.song = song;
this.user = user;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
FavoriteSong likedSong = (FavoriteSong) o;
return Objects.equals(id, likedSong.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
protected FavoriteSong() {
}
}

View File

@ -0,0 +1,46 @@
package com.baeldung.jpa.multiplebagfetchexception;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
class Offer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToOne
private Artist artist;
Offer(String name, Artist artist) {
this.name = name;
this.artist = artist;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Offer offer = (Offer) o;
return id != null ? id.equals(offer.id) : offer.id == null;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
protected Offer() {
}
}

View File

@ -0,0 +1,47 @@
package com.baeldung.jpa.multiplebagfetchexception;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import java.util.Objects;
@Entity
class Playlist {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToOne
private User createdBy;
Playlist(String name, User createdBy) {
this.name = name;
this.createdBy = createdBy;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Playlist playlist = (Playlist) o;
return Objects.equals(id, playlist.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
protected Playlist() {
}
}

View File

@ -0,0 +1,55 @@
package com.baeldung.jpa.multiplebagfetchexception;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import java.util.Objects;
@Entity
class Song {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToOne
private Album album;
@ManyToOne
private Artist artist;
Song(String name, Artist artist) {
this.name = name;
this.artist = artist;
}
void assignToAlbum(Album album) {
this.album = album;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Song song = (Song) o;
return Objects.equals(id, song.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
protected Song() {
}
}

View File

@ -0,0 +1,74 @@
package com.baeldung.jpa.multiplebagfetchexception;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.OrderColumn;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@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;
@ManyToMany
private Set<Album> followingAlbums;
User(String name) {
this.name = name;
this.playlists = new ArrayList<>();
this.favoriteSongs = new ArrayList<>();
this.followingAlbums = new HashSet<>();
}
void followAlbum(Album album) {
this.followingAlbums.add(album);
}
void createPlaylist(String name) {
this.playlists.add(new Playlist(name, this));
}
void addSongToFavorites(Song song) {
this.favoriteSongs.add(new FavoriteSong(song, this));
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
User user = (User) o;
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
protected User() {
}
}

View File

@ -55,4 +55,27 @@
<property name="hibernate.temp.use_jdbc_metadata_defaults" value="false" />
</properties>
</persistence-unit>
<persistence-unit name="jpa-h2-multiple-bag-fetch-exception">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>com.baeldung.jpa.multiplebagfetchexception.DummyEntity</class>
<class>com.baeldung.jpa.multiplebagfetchexception.Album</class>
<class>com.baeldung.jpa.multiplebagfetchexception.Song</class>
<class>com.baeldung.jpa.multiplebagfetchexception.User</class>
<class>com.baeldung.jpa.multiplebagfetchexception.Artist</class>
<class>com.baeldung.jpa.multiplebagfetchexception.Offer</class>
<class>com.baeldung.jpa.multiplebagfetchexception.Playlist</class>
<class>com.baeldung.jpa.multiplebagfetchexception.FavoriteSong</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
<property name="show_sql" value="true"/>
<property name="hibernate.temp.use_jdbc_metadata_defaults" value="false"/>
</properties>
</persistence-unit>
</persistence>

View File

@ -0,0 +1,124 @@
package com.baeldung.jpa.multiplebagfetchexception;
import org.hibernate.jpa.QueryHints;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class MultipleBagFetchExceptionIntegrationTest {
private static EntityManager entityManager;
@BeforeAll
public static void setup() {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa-h2-multiple-bag-fetch-exception");
entityManager = factory.createEntityManager();
populateH2DB();
}
@Test
public void whenFetchingMoreThanOneBag_thenThrowAnException() {
IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class, () -> {
String jpql = "SELECT dummy FROM DummyEntity dummy "
+ "JOIN FETCH dummy.collection1 "
+ "JOIN FETCH dummy.collection2 ";
entityManager.createQuery(jpql);
});
final String expectedMessagePart = "MultipleBagFetchException";
final String actualMessage = exception.getMessage();
assertTrue(actualMessage.contains(expectedMessagePart));
}
@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());
}
@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());
}
@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());
}
private static void populateH2DB() {
entityManager.getTransaction().begin();
Album album = new Album("album-name");
Artist artist1 = new Artist("artist-name-1");
Artist artist2 = new Artist("artist-name-2");
artist2.createOffer("offer-name-1");
artist2.createOffer("offer-name-2");
entityManager.persist(album);
entityManager.persist(artist1);
entityManager.persist(artist2);
Song song1 = new Song("song-name-1", artist2);
song1.assignToAlbum(album);
entityManager.persist(song1);
User user1 = new User("user-name-1");
user1.followAlbum(album);
entityManager.persist(user1);
User user2 = new User("user-name-2");
user2.followAlbum(album);
entityManager.persist(user2);
User user3 = new User("user-name-3");
user3.createPlaylist("playlist-name");
user3.addSongToFavorites(song1);
entityManager.persist(user3);
entityManager.getTransaction().commit();
}
}