diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/Author.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/Author.java new file mode 100644 index 0000000000..c8e52f1165 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/Author.java @@ -0,0 +1,25 @@ +package org.hibernate.orm.test.query.criteria.internal.hhh14916; + +import java.util.ArrayList; +import java.util.List; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; + +@Entity +public class Author { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long authorId; + + @Column + public String name; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "author", orphanRemoval = true, cascade = CascadeType.ALL) + public List books = new ArrayList<>(); +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/Book.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/Book.java new file mode 100644 index 0000000000..1f1d56b37d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/Book.java @@ -0,0 +1,32 @@ +package org.hibernate.orm.test.query.criteria.internal.hhh14916; + +import java.util.ArrayList; +import java.util.List; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; + +@Entity +public class Book { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long bookId; + + @Column + public String name; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "author_id", nullable = false) + public Author author; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "book", orphanRemoval = true, cascade = CascadeType.ALL) + public List chapters = new ArrayList<>(); +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/Chapter.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/Chapter.java new file mode 100644 index 0000000000..e12193ab0d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/Chapter.java @@ -0,0 +1,22 @@ +package org.hibernate.orm.test.query.criteria.internal.hhh14916; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +@Entity +public class Chapter { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long chapterId; + + public String name; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "book_id", nullable = false) + public Book book; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/HHH14916Test.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/HHH14916Test.java new file mode 100644 index 0000000000..5d2e51e603 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/internal/hhh14916/HHH14916Test.java @@ -0,0 +1,73 @@ +package org.hibernate.orm.test.query.criteria.internal.hhh14916; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.ListJoin; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@TestForIssue(jiraKey = "HHH-14916") +@Jpa( + annotatedClasses = { Author.class, Book.class, Chapter.class } +) +public class HHH14916Test { + + @BeforeEach + public void before(EntityManagerFactoryScope scope) { + populateData( scope ); + } + + @Test + public void testJoinOnFetchNoExceptionThrow(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + + final CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + final CriteriaQuery query = builder.createQuery( Author.class ); + + final Root root = query.from( Author.class ); + final ListJoin authorBookJoin = (ListJoin) root.fetch( "books", JoinType.LEFT ); + + final ListJoin bookChapterJoin = authorBookJoin.joinList( "chapters", JoinType.LEFT ); + + final Predicate finalPredicate = builder.equal( bookChapterJoin.get( "name" ), "Overview of HTTP" ); + query.where( finalPredicate ); + + Author author = entityManager.createQuery( query ).getSingleResult(); + + assertEquals( "David Gourley", author.name ); + assertEquals( "HTTP Definitive guide", author.books.get( 0 ).name ); + assertEquals( "Overview of HTTP", author.books.get( 0 ).chapters.get( 0 ).name ); + } ); + } + + public void populateData(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + // Insert data + Chapter chapter = new Chapter(); + chapter.name = "Overview of HTTP"; + + Book book = new Book(); + book.name = "HTTP Definitive guide"; + + Author author = new Author(); + author.name = "David Gourley"; + + book.chapters.add( chapter ); + author.books.add( book ); + + chapter.book = book; + book.author = author; + + entityManager.persist( author ); + } ); + } +} diff --git a/test-case-guide.adoc b/test-case-guide.adoc index 1d801e89e0..fef27c472f 100644 --- a/test-case-guide.adoc +++ b/test-case-guide.adoc @@ -9,7 +9,7 @@ This is meant as a guide for writing test cases to be attached to bug reports in There are a number of tenants that make up a good test case as opposed to a poor one. In fact there are a few guides for this across the web including (http://stackoverflow.com/help/mcve[MCVE]) and (http://sscce.org/[SSCCE]). These guides all assert the same ideas albeit using different terms. Given the ubiquity of StackOverflow and the fact that the MCVE guidelines were written specifically for StackOverflow, we will use those terms here as we assume most developers have seen them before: * (M)inimal - Provide just the minimal information needed. If second level caching is irrelevant to the bug report then the test should not use second level caching. If entity inheritance is irrelevant then do not use it in the test. If your application uses Spring Data, remove Spring Data from the test. -* (C)omplete - Provide all information needed to reproduce the problem. If a bug only occurs when using bytecode enhancement, then the test should include bytecode enhancement. In other words the test should be self-contained. +* \(C)omplete - Provide all information needed to reproduce the problem. If a bug only occurs when using bytecode enhancement, then the test should include bytecode enhancement. In other words the test should be self-contained. * (V)erifiable - The test should actually reproduce the problem being reported.