From 9a593026ef08523cae84e2bcb546a2266085a9b9 Mon Sep 17 00:00:00 2001 From: Jim Simon Date: Fri, 10 Jan 2025 11:35:05 -0500 Subject: [PATCH] HHH-19017: Address ClassCastException for PersistentAttributeInterceptable (cherry picked from commit 98ec951623232d1501ccb1a00b9d6d7202ff64d4) --- .../EntityDelayedFetchInitializer.java | 48 ++++--- .../LazyOneToOneWithEntityGraphTest.java | 117 ++++++++++++++++++ 2 files changed, 145 insertions(+), 20 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/lazyonetoone/LazyOneToOneWithEntityGraphTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchInitializer.java index 665b26c171ee..7f081d1ecc41 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchInitializer.java @@ -15,6 +15,7 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.AppliedGraph; @@ -200,27 +201,34 @@ public void resolveInstance(EntityDelayedFetchInitializerData data) { if ( referencedModelPart.isLazy() ) { instance = UNFETCHED_PROPERTY; } - else if ( getParent().isEntityInitializer() && isLazyByGraph( rowProcessingState ) ) { - // todo : manage the case when parent is an EmbeddableInitializer - final Object resolvedInstance = getParent().asEntityInitializer() - .getResolvedInstance( rowProcessingState ); - final LazyAttributeLoadingInterceptor persistentAttributeInterceptor = (LazyAttributeLoadingInterceptor) ManagedTypeHelper - .asPersistentAttributeInterceptable( resolvedInstance ).$$_hibernate_getInterceptor(); - - persistentAttributeInterceptor.addLazyFieldByGraph( navigablePath.getLocalName() ); - instance = UNFETCHED_PROPERTY; - } else { - instance = concreteDescriptor.loadByUniqueKey( - uniqueKeyPropertyName, - data.entityIdentifier, - session - ); - - // If the entity was not in the Persistence Context, but was found now, - // add it to the Persistence Context - if ( instance != null ) { - persistenceContext.addEntity( euk, instance ); + // Try to load a PersistentAttributeInterceptable. If we get one, we can add the lazy + // field to the interceptor. If we don't get one, we load the entity by unique key. + PersistentAttributeInterceptable persistentAttributeInterceptable = null; + if ( getParent().isEntityInitializer() && isLazyByGraph( rowProcessingState ) ) { + final Object resolvedInstance = + getParent().asEntityInitializer().getResolvedInstance( rowProcessingState ); + persistentAttributeInterceptable = + ManagedTypeHelper.asPersistentAttributeInterceptableOrNull( resolvedInstance ); + } + + if ( persistentAttributeInterceptable != null ) { + final LazyAttributeLoadingInterceptor persistentAttributeInterceptor = (LazyAttributeLoadingInterceptor) persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + persistentAttributeInterceptor.addLazyFieldByGraph( navigablePath.getLocalName() ); + instance = UNFETCHED_PROPERTY; + } + else { + instance = concreteDescriptor.loadByUniqueKey( + uniqueKeyPropertyName, + data.entityIdentifier, + session + ); + + // If the entity was not in the Persistence Context, but was found now, + // add it to the Persistence Context + if ( instance != null ) { + persistenceContext.addEntity( euk, instance ); + } } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/lazyonetoone/LazyOneToOneWithEntityGraphTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/lazyonetoone/LazyOneToOneWithEntityGraphTest.java new file mode 100644 index 000000000000..817ab91217c7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/lazyonetoone/LazyOneToOneWithEntityGraphTest.java @@ -0,0 +1,117 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.lazyonetoone; + +import java.util.List; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced; +import org.hibernate.testing.orm.junit.DomainModel; +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 static org.hibernate.Hibernate.isInitialized; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DomainModel( + annotatedClasses = { + LazyOneToOneWithEntityGraphTest.Company.class, + LazyOneToOneWithEntityGraphTest.Employee.class, + LazyOneToOneWithEntityGraphTest.Project.class + } +) +@SessionFactory +@BytecodeEnhanced(runNotEnhancedAsWell = true) +public class LazyOneToOneWithEntityGraphTest { + @BeforeAll + void setUp(SessionFactoryScope scope) { + scope.inTransaction(session -> { + // Create company + Company company = new Company(); + company.id = 1L; + company.name = "Hibernate"; + session.persist(company); + + // Create project + Project project = new Project(); + project.id = 1L; + session.persist(project); + + // Create employee + Employee employee = new Employee(); + employee.id = 1L; + employee.company = company; + employee.projects = List.of(project); + session.persist(employee); + }); + } + + @AfterAll + void tearDown(SessionFactoryScope scope) { + scope.inTransaction(session -> { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + }); + } + + + @Test + void reproducerTest(SessionFactoryScope scope) { + scope.inTransaction(session -> { + // Load employee using entity graph + Employee employee = session.createQuery( + "select e from Employee e where e.id = :id", Employee.class) + .setParameter("id", 1L) + .setHint("javax.persistence.fetchgraph", session.getEntityGraph("employee.projects")) + .getSingleResult(); + + assertTrue(isInitialized(employee.projects)); + assertEquals("Hibernate", employee.company.name); + }); + } + + @Entity(name = "Company") + public static class Company { + @Id + private Long id; + + private String name; + } + + @Entity(name = "Employee") + @NamedEntityGraph( + name = "employee.projects", + attributeNodes = @NamedAttributeNode("projects") + ) + public static class Employee { + @Id + private Long id; + + @OneToOne + @JoinColumn(name = "company_name", referencedColumnName = "name") + private Company company; + + @OneToMany(fetch = FetchType.LAZY) + private List projects; + } + + @Entity(name = "Project") + public static class Project { + @Id + private Long id; + } +}