diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Album.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Album.java new file mode 100644 index 0000000000..96615b4ee2 --- /dev/null +++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Album.java @@ -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 songs; + + @ManyToMany(mappedBy = "followingAlbums") + private Set 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() { + } + +} diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Artist.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Artist.java new file mode 100644 index 0000000000..b87de27629 --- /dev/null +++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Artist.java @@ -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 songs; + + @OneToMany(mappedBy = "artist", cascade = CascadeType.PERSIST) + private List 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() { + } +} diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/DummyEntity.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/DummyEntity.java new file mode 100644 index 0000000000..8eb9c95724 --- /dev/null +++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/DummyEntity.java @@ -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 collection1; + + @ElementCollection + private List 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() { + } +} diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/FavoriteSong.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/FavoriteSong.java new file mode 100644 index 0000000000..43bd39487e --- /dev/null +++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/FavoriteSong.java @@ -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() { + } + +} diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Offer.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Offer.java new file mode 100644 index 0000000000..54f355dfff --- /dev/null +++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Offer.java @@ -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() { + } + +} diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Playlist.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Playlist.java new file mode 100644 index 0000000000..7fa96c3355 --- /dev/null +++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Playlist.java @@ -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() { + } + +} diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Song.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Song.java new file mode 100644 index 0000000000..8cb4ca0ae4 --- /dev/null +++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/Song.java @@ -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() { + } + +} + \ No newline at end of file diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/User.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/User.java new file mode 100644 index 0000000000..d475baddab --- /dev/null +++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/multiplebagfetchexception/User.java @@ -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 playlists; + + @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST) + @OrderColumn(name = "arrangement_index") + private List favoriteSongs; + + @ManyToMany + private Set 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() { + } +} diff --git a/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml b/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml index 1a53fb8e82..46090c51d7 100644 --- a/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml +++ b/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml @@ -55,4 +55,27 @@ + + org.hibernate.jpa.HibernatePersistenceProvider + com.baeldung.jpa.multiplebagfetchexception.DummyEntity + com.baeldung.jpa.multiplebagfetchexception.Album + com.baeldung.jpa.multiplebagfetchexception.Song + com.baeldung.jpa.multiplebagfetchexception.User + com.baeldung.jpa.multiplebagfetchexception.Artist + com.baeldung.jpa.multiplebagfetchexception.Offer + com.baeldung.jpa.multiplebagfetchexception.Playlist + com.baeldung.jpa.multiplebagfetchexception.FavoriteSong + true + + + + + + + + + + + + diff --git a/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/multiplebagfetchexception/MultipleBagFetchExceptionIntegrationTest.java b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/multiplebagfetchexception/MultipleBagFetchExceptionIntegrationTest.java new file mode 100644 index 0000000000..f607bbeae4 --- /dev/null +++ b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/multiplebagfetchexception/MultipleBagFetchExceptionIntegrationTest.java @@ -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 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 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(); + } + +}