HHH-17704 Query using detached Proxy as parameter fails with LazyInitializationException

This commit is contained in:
Andrea Boriero 2024-02-02 18:32:11 +01:00 committed by Marco Belladelli
parent 9421b94bca
commit ecd0acb735
2 changed files with 74 additions and 24 deletions

View File

@ -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.java.MutabilityPlan;
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 class EntityJavaType<T> extends AbstractClassJavaType<T> {
@Override
public boolean isInstance(Object value) {
return getJavaTypeClass().isAssignableFrom( Hibernate.getClassLazy( value ) );
final LazyInitializer lazyInitializer = extractLazyInitializer( value );
final Class<T> javaTypeClass = getJavaTypeClass();
if ( lazyInitializer != null ) {
return javaTypeClass.isAssignableFrom( lazyInitializer.getPersistentClass() )
|| javaTypeClass.isAssignableFrom( lazyInitializer.getImplementationClass() );
}
else {
return javaTypeClass.isAssignableFrom( value.getClass() );
}
}
@Override

View File

@ -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 jakarta.persistence.ManyToOne;
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 class ProxyAsQueryParameterTest {
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 class ProxyAsQueryParameterTest {
@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<Product> 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 class ProxyAsQueryParameterTest {
@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 class ProxyAsQueryParameterTest {
}
}
@Entity( name = "Vendor" )
@Entity(name = "Vendor")
public static class Vendor {
@Id
private Integer id;
@ -143,7 +172,7 @@ public class ProxyAsQueryParameterTest {
}
}
@Entity( name = "CarVendor" )
@Entity(name = "CarVendor")
public static class CarVendor extends Vendor {
private String dealership;
@ -160,7 +189,18 @@ public class ProxyAsQueryParameterTest {
}
}
@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 class ProxyAsQueryParameterTest {
this.id = id;
}
@ManyToOne( fetch = FetchType.LAZY )
@ManyToOne(fetch = FetchType.LAZY)
public Vendor getVendor() {
return vendor;
}
@ -193,7 +233,7 @@ public class ProxyAsQueryParameterTest {
this.vendor = vendor;
}
@ManyToOne( fetch = FetchType.LAZY )
@ManyToOne(fetch = FetchType.LAZY)
public Producer getProducer() {
return producer;
}