[BAEL-4056] Guide to MultipleBagFetchException (#10294)
* Adds Domain Models * Adds Integration Tests
This commit is contained in:
parent
06bebff4cb
commit
cd6f187e0a
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue