When there are multiple many-to-many relations with FetchMode.JOIN, session.find returns a result with a child relation of type List containing duplicates. To avoid MultipleBagFetchException: cannot simultaneously fetch multiple bags when multiple @ManyToMany collections have @Fetch(FetchMode.JOIN), only one collection has type List and others have type Set.
@Entity
@Data
public class Book implements Serializable {
@Id
@GeneratedValue
private Long id;
private String isbn;
private String title;
private LocalDate publicationDate;
@ManyToMany
@Fetch(FetchMode.JOIN)
private List<Author> authors = new ArrayList<>();
@ManyToMany
@Fetch(FetchMode.JOIN)
private Set<Category> categories = new LinkedHashSet<>();
}
With the following test data
softwareDevelopment = new Category("Software development");
session.save(softwareDevelopment);
systemDesign = new Category("System design");
session.save(systemDesign);
martinFowler = new Author("Martin Fowler");
session.save(martinFowler);
gregorHohpe = new Author("Gregor Hohpe");
session.save(gregorHohpe);
gregorHohpe = new Author();
gregorHohpe.setFullName("Gregor Hohpe");
session.save(gregorHohpe);
bobbyWoolf = new Author();
bobbyWoolf.setFullName("Bobby Woolf");
session.save(bobbyWoolf);
poeaa = new Book();
poeaa.setIsbn("007-6092019909");
poeaa.setTitle("Patterns of Enterprise Application Architecture");
poeaa.setPublicationDate(LocalDate.parse("2002-11-15"));
poeaa.getAuthors().addAll(List.of(martinFowler));
poeaa.getCategories().addAll(List.of(softwareDevelopment, systemDesign));
session.save(poeaa);
eip = new Book();
eip.setIsbn("978-0321200686");
eip.setTitle("Enterprise Integration Patterns");
eip.setPublicationDate(LocalDate.parse("2003-10-20"));
eip.getAuthors().addAll(List.of(gregorHohpe, bobbyWoolf));
eip.getCategories().addAll(List.of(softwareDevelopment, systemDesign));
session.save(eip);
a Book entity found by ID contains duplicates in List<Author> authors
@Test
void testFindByIdNoTrans() {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
Book poeaa = session.find(Book.class, this.poeaa.getId());
Book eip = session.find(Book.class, this.eip.getId());
assertThat(poeaa.getTitle()).isEqualTo(this.poeaa.getTitle());
assertThatHasAuthors(poeaa, martinFowler.getFullName());
assertThat(eip.getTitle()).isEqualTo(this.eip.getTitle());
assertThatHasAuthors(eip, gregorHohpe.getFullName(), bobbyWoolf.getFullName());
}
}
List<Author> authors = ["Martin Fowler", "Martin Fowler"]
Probably, because categories has size 2:
Set<Category> categories = ["Software development", "System design"]
When there are 2 authors and 2 categories there are no duplicates in child relations. Beginning transaction with session.beginTransaction() doesn't have any effect on the duplicates. See the test com.example.hibernate.BookFetchModeJoinWithSetTests. Using Spring Data JPA and making the test transactional with @org.springframework.transaction.annotation.Transactional solves the problem with duplicates. Spring Boot version - 2.2.1.RELEASE, Spring Data JPA version - 2.2.1.RELEASE, Hibernate version - 5.4.8.Final. See the test com.example.spring.data.jpa.BookFetchModeJoinWithSetTests. |