From e98503a80721de7513c6b5adffebdbc2d5bfee8e Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 2 Feb 2024 18:32:11 +0100 Subject: [PATCH] HHH-17704 Query using detached Proxy as parameter fails with LazyInitializationException --- .../descriptor/java/spi/EntityJavaType.java | 14 +++- .../test/proxy/ProxyAsQueryParameterTest.java | 84 ++++++++++++++----- 2 files changed, 74 insertions(+), 24 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/EntityJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/EntityJavaType.java index a5c7e6760508..50445dae386a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/EntityJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/EntityJavaType.java @@ -6,7 +6,7 @@ */ package org.hibernate.type.descriptor.java.spi; -import org.hibernate.Hibernate; +import org.hibernate.proxy.LazyInitializer; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.AbstractClassJavaType; import org.hibernate.type.descriptor.java.IncomparableComparator; @@ -14,6 +14,8 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; + /** * Uses object identity for {@code equals}/{@code hashCode} as we ensure that internally. * @@ -44,7 +46,15 @@ public boolean areEqual(T one, T another) { @Override public boolean isInstance(Object value) { - return getJavaTypeClass().isAssignableFrom( Hibernate.getClassLazy( value ) ); + final LazyInitializer lazyInitializer = extractLazyInitializer( value ); + final Class javaTypeClass = getJavaTypeClass(); + if ( lazyInitializer != null ) { + return javaTypeClass.isAssignableFrom( lazyInitializer.getPersistentClass() ) + || javaTypeClass.isAssignableFrom( lazyInitializer.getImplementationClass() ); + } + else { + return javaTypeClass.isAssignableFrom( value.getClass() ); + } } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/proxy/ProxyAsQueryParameterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/proxy/ProxyAsQueryParameterTest.java index b870897bccb1..51776c7ccf92 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/proxy/ProxyAsQueryParameterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/proxy/ProxyAsQueryParameterTest.java @@ -6,6 +6,8 @@ */ package org.hibernate.orm.test.proxy; +import java.util.List; + import org.hibernate.Hibernate; import org.hibernate.testing.orm.junit.DomainModel; @@ -23,15 +25,20 @@ import static org.assertj.core.api.Assertions.assertThat; -@DomainModel( annotatedClasses = { +@DomainModel(annotatedClasses = { ProxyAsQueryParameterTest.Product.class, ProxyAsQueryParameterTest.Vendor.class, ProxyAsQueryParameterTest.CarVendor.class, + ProxyAsQueryParameterTest.LuxuryCarVendor.class, ProxyAsQueryParameterTest.Producer.class, -} ) +}) @SessionFactory -@Jira( "https://hibernate.atlassian.net/browse/HHH-17467" ) +@Jira("https://hibernate.atlassian.net/browse/HHH-17467") public class ProxyAsQueryParameterTest { + + private static final Integer PRODUCT_ID = 1; + private static final Integer LUXURY_PRODUCT_ID = 2; + @BeforeAll public void setUp(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -39,8 +46,13 @@ public void setUp(SessionFactoryScope scope) { session.persist( vendor ); final Producer producer = new Producer( 1, "producer_1" ); session.persist( producer ); - final Product product = new Product( 1, vendor, producer ); + final Product product = new Product( PRODUCT_ID, vendor, producer ); session.persist( product ); + + final LuxuryCarVendor luxuryCarVendor = new LuxuryCarVendor( 2, "vendor_2", "luxury" ); + session.persist( luxuryCarVendor ); + final Product luxuryProduct = new Product( LUXURY_PRODUCT_ID, luxuryCarVendor, producer ); + session.persist( luxuryProduct ); } ); } @@ -55,29 +67,31 @@ public void tearDown(SessionFactoryScope scope) { @Test public void testProxyParam(SessionFactoryScope scope) { scope.inTransaction( session -> { - final Product product = session.createQuery( "from Product p", Product.class ).getSingleResult(); + final Product product = session.createQuery( "from Product p where p.id = :productId", Product.class ) + .setParameter( "productId", PRODUCT_ID ) + .getSingleResult(); assertThat( Hibernate.isInitialized( product.getProducer() ) ).isFalse(); - final Product result = session.createQuery( + final List results = session.createQuery( "from Product p where p.producer = :producer", Product.class - ).setParameter( "producer", product.getProducer() ).getSingleResult(); - // The proxy should not have been initialized since Producer doesn't have subclasses - assertThat( Hibernate.isInitialized( product.getProducer() ) ).isFalse(); - assertThat( result.getProducer().getId() ).isEqualTo( product.getProducer().getId() ); + ).setParameter( "producer", product.getProducer() ).getResultList(); + assertThat( results.size() ).isEqualTo( 2 ); + assertThat( results.get( 0 ).getProducer().getId() ).isEqualTo( product.getProducer().getId() ); + assertThat( results.get( 1 ).getProducer().getId() ).isEqualTo( product.getProducer().getId() ); } ); } @Test public void testProxyParamWithSubclasses(SessionFactoryScope scope) { scope.inTransaction( session -> { - final Product product = session.createQuery( "from Product p", Product.class ).getSingleResult(); + final Product product = session.createQuery( "from Product p where p.id = :productId", Product.class ) + .setParameter( "productId", PRODUCT_ID ) + .getSingleResult(); assertThat( Hibernate.isInitialized( product.getVendor() ) ).isFalse(); final Product result = session.createQuery( "from Product p where p.vendor = :vendor", Product.class ).setParameter( "vendor", product.getVendor() ).getSingleResult(); - // The proxy will have been initialized since Vendor has subclasses - assertThat( Hibernate.isInitialized( product.getVendor() ) ).isTrue(); assertThat( result.getVendor().getId() ).isEqualTo( product.getVendor().getId() ); } ); } @@ -85,19 +99,34 @@ public void testProxyParamWithSubclasses(SessionFactoryScope scope) { @Test public void testSubclassProxyParam(SessionFactoryScope scope) { scope.inTransaction( session -> { - final Product product = session.createQuery( "from Product p", Product.class ).getSingleResult(); + final Product product = session.createQuery( "from Product p where p.id = :productId", Product.class ) + .setParameter( "productId", PRODUCT_ID ) + .getSingleResult(); assertThat( Hibernate.isInitialized( product.getVendor() ) ).isFalse(); final CarVendor result = session.createQuery( "from CarVendor v where v = :vendor", CarVendor.class ).setParameter( "vendor", product.getVendor() ).getSingleResult(); - // The proxy should have been initialized since Vendor has subclasses - assertThat( Hibernate.isInitialized( product.getVendor() ) ).isTrue(); assertThat( result.getId() ).isEqualTo( product.getVendor().getId() ); } ); } - @Entity( name = "Producer" ) + @Test + public void testSubSubclassProxyParam(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Product product = session.createQuery( "from Product p where p.id = :productId", Product.class ) + .setParameter( "productId", LUXURY_PRODUCT_ID ) + .getSingleResult(); + assertThat( Hibernate.isInitialized( product.getVendor() ) ).isFalse(); + final LuxuryCarVendor result = session.createQuery( + "from CarVendor v where v = :vendor", + LuxuryCarVendor.class + ).setParameter( "vendor", product.getVendor() ).getSingleResult(); + assertThat( result.getId() ).isEqualTo( product.getVendor().getId() ); + } ); + } + + @Entity(name = "Producer") public static class Producer { @Id private Integer id; @@ -120,7 +149,7 @@ public String getName() { } } - @Entity( name = "Vendor" ) + @Entity(name = "Vendor") public static class Vendor { @Id private Integer id; @@ -143,7 +172,7 @@ public String getName() { } } - @Entity( name = "CarVendor" ) + @Entity(name = "CarVendor") public static class CarVendor extends Vendor { private String dealership; @@ -160,7 +189,18 @@ public String getDealership() { } } - @Entity( name = "Product" ) + @Entity(name = "LuxuryCarVendor") + public static class LuxuryCarVendor extends CarVendor { + + public LuxuryCarVendor() { + } + + public LuxuryCarVendor(int id, String name, String dealership) { + super( id, name, dealership ); + } + } + + @Entity(name = "Product") public static final class Product { private Integer id; private Vendor vendor; @@ -184,7 +224,7 @@ public void setId(Integer id) { this.id = id; } - @ManyToOne( fetch = FetchType.LAZY ) + @ManyToOne(fetch = FetchType.LAZY) public Vendor getVendor() { return vendor; } @@ -193,7 +233,7 @@ public void setVendor(Vendor vendor) { this.vendor = vendor; } - @ManyToOne( fetch = FetchType.LAZY ) + @ManyToOne(fetch = FetchType.LAZY) public Producer getProducer() { return producer; }