From d15f2447c8e16c0fbe18216b092f3f0e0e60e4e1 Mon Sep 17 00:00:00 2001 From: Miroslav Silhavy Date: Mon, 12 May 2025 21:13:29 +0200 Subject: [PATCH 1/2] HHH-19387 - Fix concrete descriptor when left join returns null in a cached query --- .../internal/EntityInitializerImpl.java | 4 + ...hCollectionReloadCacheInheritanceTest.java | 178 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java index 5ed94e2cae28..31fff7fc04df 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java @@ -1634,6 +1634,10 @@ public void resolveState(EntityInitializerData data) { castNonNull( discriminatorAssembler ), entityDescriptor ); + if (data.concreteDescriptor == null) { + // Return because the value is null + return; + } } } resolveEntityState( data ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java new file mode 100644 index 000000000000..5493cbf3aef3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java @@ -0,0 +1,178 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.querycache; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Access; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; + +import static jakarta.persistence.AccessType.FIELD; +import static jakarta.persistence.EnumType.STRING; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author miroslav silhavy + */ +@DomainModel(annotatedClasses = { + EntityWithCollectionReloadCacheInheritanceTest.HighSchoolStudent.class, + EntityWithCollectionReloadCacheInheritanceTest.DefaultSubject.class, + EntityWithCollectionReloadCacheInheritanceTest.EnglishSubject.class +}) +@SessionFactory +@Jira("https://hibernate.atlassian.net/browse/HHH-19387") +public class EntityWithCollectionReloadCacheInheritanceTest { + + @Test + public void test(SessionFactoryScope scope) { + scope.inTransaction( session -> { + List list = session.createQuery( + "select s" + + " from HighSchoolStudent s left join fetch s.subjects m" + + " where s.name in :names", HighSchoolStudent.class + ) + .setParameter( "names", Arrays.asList( "Brian" ) ) + .setCacheable( true ) + .list(); + + assertThat( list ).hasSize( 1 ); + + list = session.createQuery( + "select s" + + " from HighSchoolStudent s left join fetch s.subjects m" + + " where s.name in :names", HighSchoolStudent.class + ) + .setParameter( "names", Arrays.asList( "Andrew", "Brian" ) ) + .setCacheable( true ) + .list(); + + assertThat( list ).hasSize( 2 ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HighSchoolStudent s1 = new HighSchoolStudent(); + s1.setName( "Andrew" ); + session.persist( s1 ); + + HighSchoolStudent s2 = new HighSchoolStudent(); + s2.setName( "Brian" ); + session.persist( s2 ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } + + @Entity(name = "HighSchoolStudent") + @Access(FIELD) + static class HighSchoolStudent { + + @Id + @GeneratedValue + @Column(name = "id") + private Long id; + + @Column(name = "name") + private String name; + + @ManyToMany(targetEntity = DefaultSubject.class, fetch = FetchType.LAZY) + @JoinTable(name = "STUDENT_SUBJECT", + joinColumns = { @JoinColumn(name = "student_id") }, + inverseJoinColumns = { @JoinColumn(name = "subject_id") } + ) + private Set subjects; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getSubjects() { + return subjects; + } + + public void setMajors(Set subjects) { + this.subjects = subjects; + } + + } + + @Entity(name = "DefaultSubject") + @DiscriminatorValue("DEFAULT") + @DiscriminatorColumn(name = "TYPE", length = 20) + @Access(FIELD) + static class DefaultSubject { + + enum SubjectType { + DEFAULT, + ENGLISH + } + + @Column(name = "TYPE", nullable = false, length = 20, insertable = false, updatable = false) + @Enumerated(STRING) + @JdbcTypeCode(SqlTypes.VARCHAR) + private SubjectType type; + + @Id + @GeneratedValue + @Column(name = "id") + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + } + + @Entity(name = "EnglishSubject") + @DiscriminatorValue("ENGLISH") + @Access(FIELD) + static class EnglishSubject extends DefaultSubject { + } + +} From 4eecaaad0ff6e4dfdc4f39939f9cf701af97c21b Mon Sep 17 00:00:00 2001 From: Miroslav Silhavy Date: Thu, 15 May 2025 22:59:37 +0200 Subject: [PATCH 2/2] HHH-19387 - Reformat and reword test entities --- .../internal/EntityInitializerImpl.java | 4 +- ...hCollectionReloadCacheInheritanceTest.java | 80 +++++++++---------- 2 files changed, 40 insertions(+), 44 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java index 31fff7fc04df..8b0d9b88b7ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java @@ -1634,8 +1634,8 @@ public void resolveState(EntityInitializerData data) { castNonNull( discriminatorAssembler ), entityDescriptor ); - if (data.concreteDescriptor == null) { - // Return because the value is null + if ( data.concreteDescriptor == null ) { + // this should imply the entity is missing return; } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java index 5493cbf3aef3..03977b12df7f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java @@ -4,22 +4,6 @@ */ package org.hibernate.orm.test.querycache; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - -import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.Jira; -import org.hibernate.testing.orm.junit.SessionFactory; -import org.hibernate.testing.orm.junit.SessionFactoryScope; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import jakarta.persistence.Access; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorColumn; import jakarta.persistence.DiscriminatorValue; @@ -28,20 +12,34 @@ import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.type.SqlTypes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; -import static jakarta.persistence.AccessType.FIELD; import static jakarta.persistence.EnumType.STRING; import static org.assertj.core.api.Assertions.assertThat; /** - * @author miroslav silhavy + * @author Miroslav Silhavy */ @DomainModel(annotatedClasses = { EntityWithCollectionReloadCacheInheritanceTest.HighSchoolStudent.class, - EntityWithCollectionReloadCacheInheritanceTest.DefaultSubject.class, + EntityWithCollectionReloadCacheInheritanceTest.Subject.class, EntityWithCollectionReloadCacheInheritanceTest.EnglishSubject.class }) @SessionFactory @@ -51,7 +49,7 @@ public class EntityWithCollectionReloadCacheInheritanceTest { @Test public void test(SessionFactoryScope scope) { scope.inTransaction( session -> { - List list = session.createQuery( + List highSchoolStudents = session.createQuery( "select s" + " from HighSchoolStudent s left join fetch s.subjects m" + " where s.name in :names", HighSchoolStudent.class @@ -60,9 +58,9 @@ public void test(SessionFactoryScope scope) { .setCacheable( true ) .list(); - assertThat( list ).hasSize( 1 ); + assertThat( highSchoolStudents ).hasSize( 1 ); - list = session.createQuery( + highSchoolStudents = session.createQuery( "select s" + " from HighSchoolStudent s left join fetch s.subjects m" + " where s.name in :names", HighSchoolStudent.class @@ -71,7 +69,7 @@ public void test(SessionFactoryScope scope) { .setCacheable( true ) .list(); - assertThat( list ).hasSize( 2 ); + assertThat( highSchoolStudents ).hasSize( 2 ); } ); } @@ -94,7 +92,6 @@ public void tearDown(SessionFactoryScope scope) { } @Entity(name = "HighSchoolStudent") - @Access(FIELD) static class HighSchoolStudent { @Id @@ -105,12 +102,12 @@ static class HighSchoolStudent { @Column(name = "name") private String name; - @ManyToMany(targetEntity = DefaultSubject.class, fetch = FetchType.LAZY) + @ManyToMany(targetEntity = Subject.class, fetch = FetchType.LAZY) @JoinTable(name = "STUDENT_SUBJECT", joinColumns = { @JoinColumn(name = "student_id") }, inverseJoinColumns = { @JoinColumn(name = "subject_id") } ) - private Set subjects; + private Set subjects; public Long getId() { return id; @@ -128,37 +125,32 @@ public void setName(String name) { this.name = name; } - public Set getSubjects() { + public Set getSubjects() { return subjects; } - public void setMajors(Set subjects) { + public void setSubjects(Set subjects) { this.subjects = subjects; } } - @Entity(name = "DefaultSubject") + @Entity(name = "Subject") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorValue("DEFAULT") @DiscriminatorColumn(name = "TYPE", length = 20) - @Access(FIELD) - static class DefaultSubject { + static class Subject { - enum SubjectType { - DEFAULT, - ENGLISH - } + @Id + @GeneratedValue + @Column(name = "id") + private Long id; @Column(name = "TYPE", nullable = false, length = 20, insertable = false, updatable = false) @Enumerated(STRING) @JdbcTypeCode(SqlTypes.VARCHAR) private SubjectType type; - @Id - @GeneratedValue - @Column(name = "id") - private Long id; - public Long getId() { return id; } @@ -171,8 +163,12 @@ public void setId(Long id) { @Entity(name = "EnglishSubject") @DiscriminatorValue("ENGLISH") - @Access(FIELD) - static class EnglishSubject extends DefaultSubject { + static class EnglishSubject extends Subject { + } + + enum SubjectType { + DEFAULT, + ENGLISH } }