HHH-18194 - Remove @Proxy

This commit is contained in:
Steve Ebersole 2024-07-30 20:49:34 -05:00
parent 53bca2467d
commit 4309cffb4d
38 changed files with 131 additions and 1247 deletions

View File

@ -453,50 +453,6 @@ when executing an HQL or JPQL which selects from the `AccountSummary` entity,
Hibernate will trigger a Persistence Context flush if there are pending `Account`, `Client` or `AccountTransaction` entity state transitions.
====
[[entity-proxy]]
==== Define a custom entity proxy
By default, when it needs to use a proxy instead of the actual POJO, Hibernate is going to use a Bytecode manipulation library like
https://bytebuddy.net/[Byte Buddy].
However, if the entity class is final, a proxy will not be created; you will get a POJO even when you only need a proxy reference.
In this case, you could proxy an interface that this particular entity implements, as illustrated by the following example.
NOTE: Supplying a custom proxy class has been allowed historically, but has never seen much use. Also, setting the `lazy` property to `false`, effectively disallowing proxy creation for an entity type, can easily lead to performance degradation due to the N + 1 query issue. As of 6.2 `@Proxy` has been formally deprecated.
See the <<concrete-proxy,@ConcreteProxy>> paragraph if you wish to resolve the concrete type of proxies for the purpose of `instanceof` checks and typecasts.
[[entity-proxy-interface-mapping]]
.Final entity class implementing the `Identifiable` interface
====
[source,java]
----
include::{example-dir-proxy}/ProxyInterfaceTest.java[tag=entity-proxy-interface-mapping,indent=0]
----
====
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Proxy.html[`@Proxy`]
annotation is used to specify a custom proxy implementation for the current annotated entity.
When loading the `Book` entity proxy, Hibernate is going to proxy the `Identifiable` interface instead as illustrated by the following example:
[[entity-proxy-persist-mapping]]
.Proxying the final entity class implementing the `Identifiable` interface
====
[source,java]
----
include::{example-dir-proxy}/ProxyInterfaceTest.java[tag=entity-proxy-persist-mapping,indent=0]
----
[source,sql]
----
include::{extrasdir}/entity/entity-proxy-persist-mapping.sql[]
----
====
As you can see in the associated SQL snippet, Hibernate issues no SQL SELECT query since the proxy can be
constructed without needing to fetch the actual entity POJO.
[[concrete-proxy]]
==== Create proxies that resolve their inheritance subtype

View File

@ -1,35 +0,0 @@
/*
* 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.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Allows the proxy class of an entity class to be explicitly specified.
*
* @deprecated This annotation is almost never useful.
*
* @author Emmanuel Bernard
*/
@Deprecated(since = "6.2")
@Target(TYPE)
@Retention(RUNTIME)
public @interface Proxy {
/**
* Whether this class may be proxied. Default to true.
*/
boolean lazy() default true;
/**
* Proxy class or interface used. Default is to the annotated entity class itself.
*/
Class<?> proxyClass() default void.class;
}

View File

@ -37,7 +37,6 @@ import org.hibernate.annotations.NaturalIdCache;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OptimisticLockType;
import org.hibernate.annotations.OptimisticLocking;
import org.hibernate.annotations.Proxy;
import org.hibernate.annotations.QueryCacheLayout;
import org.hibernate.annotations.RowId;
import org.hibernate.annotations.SQLDelete;
@ -1590,27 +1589,9 @@ public class EntityBinder {
}
public void bindProxy() {
final Proxy proxy = annotatedClass.getAnnotationUsage( Proxy.class, getSourceModelContext() );
if ( proxy != null ) {
lazy = proxy.lazy();
proxyClass = lazy ? resolveProxyClass( proxy, annotatedClass, getSourceModelContext() ) : null;
}
else {
//needed to allow association lazy loading.
lazy = true;
proxyClass = annotatedClass;
}
}
private static ClassDetails resolveProxyClass(
Proxy proxy,
ClassDetails annotatedClass,
SourceModelBuildingContext sourceModelContext) {
final Class<?> explicitProxyClass = proxy.proxyClass();
if ( explicitProxyClass == void.class ) {
return annotatedClass;
}
return sourceModelContext.getClassDetailsRegistry().resolveClassDetails( explicitProxyClass.getName() );
//needed to allow association lazy loading.
lazy = true;
proxyClass = annotatedClass;
}
public void bindConcreteProxy() {

View File

@ -488,10 +488,6 @@ public interface HibernateAnnotations {
PartitionKey.class,
PartitionKeyAnnotation.class
);
OrmAnnotationDescriptor<Proxy,ProxyAnnotation> PROXY = new OrmAnnotationDescriptor<>(
Proxy.class,
ProxyAnnotation.class
);
OrmAnnotationDescriptor<PropertyRef,PropertyRefAnnotation> PROPERTY_REF = new OrmAnnotationDescriptor<>(
PropertyRef.class,
PropertyRefAnnotation.class

View File

@ -1,74 +0,0 @@
/*
* 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.boot.models.annotations.internal;
import java.lang.annotation.Annotation;
import org.hibernate.annotations.Proxy;
import org.hibernate.boot.models.HibernateAnnotations;
import org.hibernate.models.spi.SourceModelBuildingContext;
import org.jboss.jandex.AnnotationInstance;
import static org.hibernate.boot.models.internal.OrmAnnotationHelper.extractJandexValue;
@SuppressWarnings({ "ClassExplicitlyAnnotation", "unused" })
@jakarta.annotation.Generated("org.hibernate.orm.build.annotations.ClassGeneratorProcessor")
public class ProxyAnnotation implements Proxy {
private boolean lazy;
private java.lang.Class<?> proxyClass;
/**
* Used in creating dynamic annotation instances (e.g. from XML)
*/
public ProxyAnnotation(SourceModelBuildingContext modelContext) {
this.lazy = true;
this.proxyClass = void.class;
}
/**
* Used in creating annotation instances from JDK variant
*/
public ProxyAnnotation(Proxy annotation, SourceModelBuildingContext modelContext) {
this.lazy = annotation.lazy();
this.proxyClass = annotation.proxyClass();
}
/**
* Used in creating annotation instances from Jandex variant
*/
public ProxyAnnotation(AnnotationInstance annotation, SourceModelBuildingContext modelContext) {
this.lazy = extractJandexValue( annotation, HibernateAnnotations.PROXY, "lazy", modelContext );
this.proxyClass = extractJandexValue( annotation, HibernateAnnotations.PROXY, "proxyClass", modelContext );
}
@Override
public Class<? extends Annotation> annotationType() {
return Proxy.class;
}
@Override
public boolean lazy() {
return lazy;
}
public void lazy(boolean value) {
this.lazy = value;
}
@Override
public java.lang.Class<?> proxyClass() {
return proxyClass;
}
public void proxyClass(java.lang.Class<?> value) {
this.proxyClass = value;
}
}

View File

@ -16,7 +16,6 @@ import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.Immutable;
import org.hibernate.annotations.Proxy;
import org.hibernate.annotations.ResultCheckStyle;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLInsert;
@ -40,7 +39,6 @@ import jakarta.persistence.Cacheable;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import static org.hibernate.boot.models.categorize.internal.CategorizationHelper.toClassDetails;
import static org.hibernate.internal.util.StringHelper.EMPTY_STRINGS;
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
import static org.hibernate.internal.util.StringHelper.unqualify;
@ -106,29 +104,9 @@ public class EntityTypeMetadataImpl
this.customUpdateMap = extractCustomSql( classDetails, SQLUpdate.class );
this.customDeleteMap = extractCustomSql( classDetails, SQLDelete.class );
//noinspection deprecation
final Proxy proxyAnnotation = classDetails.getDirectAnnotationUsage( Proxy.class );
if ( proxyAnnotation != null ) {
this.isLazy = proxyAnnotation.lazy();
if ( this.isLazy ) {
final ClassDetails proxyClassDetails = toClassDetails( proxyAnnotation.proxyClass(), modelContext.getClassDetailsRegistry() );
if ( proxyClassDetails != null ) {
this.proxy = proxyClassDetails.getName();
}
else {
this.proxy = null;
}
}
else {
this.proxy = null;
}
}
else {
// defaults are that it is lazy and that the class itself is the proxy class
this.isLazy = true;
this.proxy = getEntityName();
}
// defaults are that it is lazy and that the class itself is the proxy class
this.isLazy = true;
this.proxy = getEntityName();
final DiscriminatorValue discriminatorValueAnn = classDetails.getDirectAnnotationUsage( DiscriminatorValue.class );
if ( discriminatorValueAnn != null ) {
@ -175,29 +153,9 @@ public class EntityTypeMetadataImpl
this.customUpdateMap = extractCustomSql( classDetails, SQLUpdate.class );
this.customDeleteMap = extractCustomSql( classDetails, SQLDelete.class );
//noinspection deprecation
final Proxy proxyAnnotation = classDetails.getDirectAnnotationUsage( Proxy.class );
if ( proxyAnnotation != null ) {
this.isLazy = proxyAnnotation.lazy();
if ( this.isLazy ) {
final ClassDetails proxyClassDetails = toClassDetails( proxyAnnotation.proxyClass(), modelContext.getClassDetailsRegistry() );
if ( proxyClassDetails != null ) {
this.proxy = proxyClassDetails.getName();
}
else {
this.proxy = null;
}
}
else {
this.proxy = null;
}
}
else {
// defaults are that it is lazy and that the class itself is the proxy class
this.isLazy = true;
this.proxy = getEntityName();
}
// defaults are that it is lazy and that the class itself is the proxy class
this.isLazy = true;
this.proxy = getEntityName();
final DiscriminatorValue discriminatorValueAnn = classDetails.getDirectAnnotationUsage( DiscriminatorValue.class );
if ( discriminatorValueAnn != null ) {

View File

@ -228,27 +228,24 @@ public class BasicHibernateAnnotationsTest extends BaseCoreFunctionalTestCase {
}
@Test
public void testNonLazy() throws Exception {
Session s;
Transaction tx;
s = openSession();
tx = s.beginTransaction();
Forest f = new Forest();
Tree t = new Tree();
t.setName( "Basic one" );
s.persist( f );
s.persist( t );
tx.commit();
s.close();
public void testLoading() throws Exception {
final Forest created = fromTransaction( (session) -> {
Forest f = new Forest();
session.persist( f );
return f;
} );
s = openSession();
tx = s.beginTransaction();
f = (Forest) s.load( Forest.class, f.getId() );
t = (Tree) s.load( Tree.class, t.getId() );
assertFalse( "Default should be lazy", Hibernate.isInitialized( f ) );
assertTrue( "Tree is not lazy", Hibernate.isInitialized( t ) );
tx.commit();
s.close();
// getReference
inTransaction( (session) -> {
final Forest reference = session.getReference( Forest.class, created.getId() );
assertFalse( Hibernate.isInitialized( reference ) );
} );
// find
inTransaction( (session) -> {
final Forest reference = session.find( Forest.class, created.getId() );
assertTrue( Hibernate.isInitialized( reference ) );
} );
}
@Test

View File

@ -7,19 +7,15 @@
//$Id$
package org.hibernate.orm.test.annotations.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import org.hibernate.annotations.Proxy;
/**
* Non lazy entity
*
* @author Emmanuel Bernard
*/
@Entity
@Proxy(lazy = false)
public class Tree {
private Integer id;
private String name;

View File

@ -7,12 +7,12 @@
//$
package org.hibernate.orm.test.annotations.idmanytoone;
import java.io.Serializable;
import jakarta.persistence.Basic;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.JoinColumn;
@ -21,7 +21,6 @@ import jakarta.persistence.Table;
@Entity
@Table(name="BasketItems")
@org.hibernate.annotations.Proxy(lazy=false)
@IdClass(BasketItemsPK.class)
public class BasketItems implements Serializable {

View File

@ -7,7 +7,9 @@
//$
package org.hibernate.orm.test.annotations.idmanytoone;
import java.io.Serializable;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@ -17,7 +19,6 @@ import jakarta.persistence.Table;
@Entity
@Table(name="Customers")
@org.hibernate.annotations.Proxy(lazy=false)
public class Customers implements Serializable {
private static final long serialVersionUID = -885167444315163039L;

View File

@ -7,12 +7,12 @@
//$
package org.hibernate.orm.test.annotations.idmanytoone;
import java.io.Serializable;
import jakarta.persistence.Basic;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.JoinColumn;
@ -22,7 +22,6 @@ import jakarta.persistence.Table;
@Entity
@Table(name="ShoppingBasket")
@org.hibernate.annotations.Proxy(lazy=false)
@IdClass(ShoppingBasketsPK.class)
public class ShoppingBaskets implements Serializable {

View File

@ -17,7 +17,6 @@ import jakarta.persistence.Table;
@Entity
@Inheritance( strategy = InheritanceType.JOINED )
@org.hibernate.annotations.Proxy( proxyClass = A.class )
@Table( name = "ENTITYA" )
public class AImpl implements A {
private static final long serialVersionUID = 1L;

View File

@ -11,7 +11,6 @@ import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@org.hibernate.annotations.Proxy( proxyClass = B.class )
@Table( name = "ENTITYB" )
public class BImpl extends AImpl implements B {
private static final long serialVersionUID = 1L;

View File

@ -20,7 +20,6 @@ import jakarta.persistence.Table;
@Entity
@Inheritance( strategy = InheritanceType.JOINED )
@org.hibernate.annotations.Proxy( proxyClass = Z.class )
@Table( name = "ENTITYZ" )
public class ZImpl implements Z {
private static final long serialVersionUID = 1L;

View File

@ -7,7 +7,6 @@ import org.hibernate.Hibernate;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Proxy;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.orm.junit.DomainModel;
@ -15,6 +14,7 @@ import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@ -29,7 +29,6 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -104,7 +103,11 @@ public class BatchEntityWithDisabledProxyTest {
s.getSessionFactory().getCache().evictAllRegions();
Product product = s.getReference( Product.class, 3 );
assertTrue( Hibernate.isInitialized(product) );
assertFalse( Hibernate.isInitialized( product) );
Hibernate.initialize( product );
assertTrue( Hibernate.isInitialized( product) );
} );
}
@ -137,7 +140,6 @@ public class BatchEntityWithDisabledProxyTest {
}
@Entity(name = "Product")
@Proxy(lazy = false)
@BatchSize(size = 512)
@Cacheable
public static class Product {

View File

@ -7,7 +7,6 @@ import org.hibernate.Hibernate;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Proxy;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
@ -151,7 +150,6 @@ public class BatchEntityOneToManyWithDisabledProxyTest {
@Entity(name = "Product")
@BatchSize(size = 512)
@Proxy(lazy = false)
@Cacheable
public static class Product {
@Id

View File

@ -6,7 +6,6 @@ import org.hibernate.Hibernate;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Proxy;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
@ -183,7 +182,6 @@ public class BatchEntityWithSelectFetchWithDisableProxyTest {
@Entity(name = "Product")
@BatchSize(size = 512)
@Cacheable
@Proxy(lazy = false)
public static class Product {
@Id
Long id;

View File

@ -5,7 +5,6 @@ import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Proxy;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
@ -109,7 +108,6 @@ public class AbstractManyToOneNoProxyTest {
@Entity
@Table(name = "users")
@Proxy(lazy = false)
@BatchSize(size = 512)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Cacheable
@ -144,7 +142,6 @@ public class AbstractManyToOneNoProxyTest {
}
@Entity
@Proxy(lazy = false)
@BatchSize(size = 512)
@DiscriminatorValue(value = "USER")
public static class User extends Actor {
@ -166,7 +163,6 @@ public class AbstractManyToOneNoProxyTest {
}
@Entity
@Proxy(lazy = false)
@DiscriminatorValue("USERS")
@BatchSize(size = 256)
public static class UserGroup extends ActorGroup<User> {
@ -176,7 +172,6 @@ public class AbstractManyToOneNoProxyTest {
}
@Entity
@Proxy(lazy = false)
@Table(name = "actor_group")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE")

View File

@ -5,7 +5,6 @@ import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Proxy;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
@ -110,7 +109,6 @@ public class ManyToOneNoProxyTest {
@Entity
@Table(name = "users")
@Proxy(lazy = false)
@BatchSize(size = 512)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Cacheable
@ -145,7 +143,6 @@ public class ManyToOneNoProxyTest {
}
@Entity
@Proxy(lazy = false)
@BatchSize(size = 512)
@DiscriminatorValue(value = "USER")
public static class User extends Actor {
@ -167,7 +164,6 @@ public class ManyToOneNoProxyTest {
}
@Entity
@Proxy(lazy = false)
@DiscriminatorValue("USERS")
@BatchSize(size = 256)
public static class UserGroup extends ActorGroup<User> {
@ -177,7 +173,6 @@ public class ManyToOneNoProxyTest {
}
@Entity
@Proxy(lazy = false)
@Table(name = "actor_group")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE")

View File

@ -1,6 +1,6 @@
package org.hibernate.orm.test.bytecode.enhancement.lazy;
import org.hibernate.annotations.Proxy;
import org.hibernate.annotations.ConcreteProxy;
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
import org.hibernate.testing.jdbc.SQLStatementInspector;
@ -74,9 +74,7 @@ public class LazyAbstractManyToOneNoProxyTest {
assertThat( user ).isNotNull();
assertThat( user.getName() ).isEqualTo( USER_1_NAME );
// The User#team type has subclasses so even if it is lazy we need to initialize it because we do not know
// the real type and Proxy creation is disabled
assertThat( statementInspector.getSqlQueries().size() ).isEqualTo( 2 );
assertThat( statementInspector.getSqlQueries().size() ).isEqualTo( 1 );
statementInspector.clear();
@ -84,14 +82,15 @@ public class LazyAbstractManyToOneNoProxyTest {
assertThat( team ).isInstanceOf( UserGroup.class );
UserGroup userGroup = (UserGroup) team;
assertThat( userGroup.getName() ).isEqualTo( USER_GROUP_1_NAME );
assertThat( statementInspector.getSqlQueries().size() ).isEqualTo( 0 );
// accessing the name
assertThat( statementInspector.getSqlQueries().size() ).isEqualTo( 1 );
}
);
}
@Entity(name = "User")
@Table(name = "usr_tbl")
@Proxy(lazy = false)
public static class User {
@Id
Long id;
@ -127,7 +126,7 @@ public class LazyAbstractManyToOneNoProxyTest {
@Entity(name = "ActorGroup")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE")
@Proxy(lazy = false)
@ConcreteProxy
public abstract static class ActorGroup {
@Id
Long id;
@ -141,7 +140,6 @@ public class LazyAbstractManyToOneNoProxyTest {
}
@Entity(name = "UserGroup")
@Proxy(lazy = false)
@DiscriminatorValue("USERS")
public static class UserGroup extends ActorGroup {

View File

@ -1,7 +1,5 @@
package org.hibernate.orm.test.bytecode.enhancement.lazy;
import org.hibernate.annotations.Proxy;
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
@ -85,7 +83,6 @@ public class LazyManyToOneNoProxyTest {
@Entity(name = "User")
@Table(name = "usr_tbl")
@Proxy(lazy = false)
public static class User {
@Id
Long id;
@ -119,7 +116,6 @@ public class LazyManyToOneNoProxyTest {
}
@Entity(name = "UserGroup")
@Proxy(lazy = false)
public static class UserGroup {
@Id
Long id;

View File

@ -10,7 +10,6 @@ import org.hibernate.Session;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Immutable;
import org.hibernate.annotations.Proxy;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.engine.spi.EntityEntry;
@ -18,6 +17,7 @@ import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
@ -227,7 +227,6 @@ public class ByteCodeEnhancedImmutableReferenceCacheTest extends BaseCoreFunctio
@Immutable
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
@Proxy(lazy = false)
@SuppressWarnings("UnusedDeclaration")
public static class MyEnhancedReferenceData implements ManagedEntity {
@Id

View File

@ -6,22 +6,20 @@
*/
package org.hibernate.orm.test.cache;
import jakarta.persistence.Cacheable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.Session;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Immutable;
import org.hibernate.annotations.Proxy;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import jakarta.persistence.Cacheable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@ -91,7 +89,6 @@ public class ReferenceCacheTest extends BaseCoreFunctionalTestCase {
@Immutable
@Cacheable
@Cache( usage = CacheConcurrencyStrategy.READ_ONLY )
@Proxy( lazy = false )
@SuppressWarnings("UnusedDeclaration")
public static class MyReferenceData {
@Id

View File

@ -1,204 +0,0 @@
package org.hibernate.orm.test.caching;
import org.hibernate.Hibernate;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Proxy;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Cacheable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PostLoad;
import static org.assertj.core.api.Assertions.assertThat;
@DomainModel( annotatedClasses = {
CachingBatchLoadNoProxiesAndCircularRelationshipTest.Category.class,
CachingBatchLoadNoProxiesAndCircularRelationshipTest.CategoryHolder.class,
} )
@SessionFactory
@ServiceRegistry( settings = @Setting( name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true" ) )
@Jira( "https://hibernate.atlassian.net/browse/HHH-17918" )
@Jira( "https://hibernate.atlassian.net/browse/HHH-17983" )
public class CachingBatchLoadNoProxiesAndCircularRelationshipTest {
private static final int NUMBER_OF_CATEGORIES = 5;
@Test
public void recursiveBatchLoadingWithCircularRelationship(SessionFactoryScope scope) {
// Set the state of the 2nd-level cache, so it contains #1 (and potentially others) but not #0 or #3
scope.getSessionFactory().getCache().evict( Category.class );
scope.inTransaction( session -> session.getReference( Category.class, 1 ) );
scope.getSessionFactory().getCache().evict( Category.class, 0 );
scope.getSessionFactory().getCache().evict( Category.class, 3 );
scope.inSession( session -> {
final CategoryHolder result = session.createQuery(
"from CategoryHolder where id = 0",
CategoryHolder.class
).getSingleResult();
Category category = result.getLeftCategory();
for ( int i = 0; i < NUMBER_OF_CATEGORIES; i++ ) {
assertThat( category ).matches( Hibernate::isInitialized, "Category was not initialized" )
.extracting( Category::getId )
.isEqualTo( i );
if ( i == 3 ) {
assertThat( category ).isSameAs( result.getRightCategory() );
}
else if ( i == NUMBER_OF_CATEGORIES - 1 ) {
assertThat( category.getNextCategory() ).isSameAs( result.getLeftCategory() );
}
category = category.getNextCategory();
}
} );
}
@Test
public void recursiveBatchLoadingSameQueryTest(SessionFactoryScope scope) {
scope.getSessionFactory().getCache().evict( Category.class );
scope.inSession( session -> {
final CategoryHolder result = session.find( CategoryHolder.class, 1 );
Category category = result.getLeftCategory();
for ( int i = 0; i < NUMBER_OF_CATEGORIES; i++ ) {
assertThat( category ).matches( Hibernate::isInitialized, "Category was not initialized" )
.extracting( Category::getId )
.isEqualTo( i );
if ( i == NUMBER_OF_CATEGORIES - 1 ) {
assertThat( category ).isSameAs( result.getRightCategory() );
assertThat( category.getNextCategory() ).isSameAs( result.getLeftCategory() );
}
category = category.getNextCategory();
}
} );
}
@BeforeAll
public void setupEntities(SessionFactoryScope scope) {
scope.inTransaction( session -> {
final Category[] categories = new Category[NUMBER_OF_CATEGORIES];
for ( int i = 0; i < categories.length; i++ ) {
categories[i] = new Category( i, "category_" + i );
session.persist( categories[i] );
}
// Chain-link the categories (#n points to #n+1, last one points to #0)
for ( int i = 0; i < categories.length - 1; i++ ) {
categories[i].setNextCategory( categories[i + 1] );
}
categories[categories.length - 1].nextCategory = categories[0];
final CategoryHolder holder1 = new CategoryHolder( 0 );
holder1.leftCategory = categories[0];
holder1.rightCategory = categories[3];
session.persist( holder1 );
final CategoryHolder holder2 = new CategoryHolder( 1 );
holder2.leftCategory = categories[0];
holder2.rightCategory = categories[4];
session.persist( holder2 );
} );
}
@AfterAll
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction( session -> {
session.createMutationQuery( "delete from CategoryHolder" ).executeUpdate();
session.createQuery( "from Category", Category.class )
.getResultList()
.forEach( c -> c.setNextCategory( null ) );
session.createMutationQuery( "delete from Category" ).executeUpdate();
} );
}
@Proxy( lazy = false )
@Entity( name = "Category" )
@BatchSize( size = 10 )
@Cacheable
@Cache( usage = CacheConcurrencyStrategy.READ_WRITE )
static class Category {
@Id
private Integer id;
private String name;
@ManyToOne( fetch = FetchType.LAZY )
@Fetch( value = FetchMode.SELECT )
private Category nextCategory;
public Category() {
}
public Category(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public Category getNextCategory() {
return nextCategory;
}
public void setNextCategory(Category nextCategory) {
this.nextCategory = nextCategory;
}
public String getName() {
return name;
}
@PostLoad
public void postLoad() {
assertThat( name ).isNotNull().isEqualTo( "category_" + id );
assertThat( nextCategory.getId() ).isNotNull();
// note : nextCategory.name will be null here since the instance will not have been initialized yet
}
}
@Entity( name = "CategoryHolder" )
static class CategoryHolder {
@Id
private Integer id;
@ManyToOne( fetch = FetchType.LAZY )
@Fetch( value = FetchMode.SELECT )
private Category leftCategory;
@ManyToOne( fetch = FetchType.LAZY )
@Fetch( value = FetchMode.SELECT )
private Category rightCategory;
public CategoryHolder() {
}
public CategoryHolder(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public Category getLeftCategory() {
return leftCategory;
}
public Category getRightCategory() {
return rightCategory;
}
}
}

View File

@ -1,84 +0,0 @@
/*
* 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.classloader;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.annotations.Proxy;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests if instrumentation is done with the proper classloader for entities with proxy class. The classloader
* of {@link HibernateProxy} will not see {@link IPerson}, since it is only accessible from this package. But: the
* classloader of {@link IPerson} will see {@link HibernateProxy}, so instrumentation will only work if this classloader
* is chosen for creating the instrumented proxy class. We need to check the class of a loaded object though, since
* building the configuration will not fail, only log the error and fall back to using the entity class itself as a
* proxy.
*
* @author lgathy
*/
public class ProxyInterfaceClassLoaderTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { Person.class };
}
@Test
public void testProxyClassLoader() {
Session s = openSession();
Transaction t = s.beginTransaction();
IPerson p = new Person();
p.setId( 1 );
s.persist( p );
s.flush();
s.clear();
Object lp = s.load( Person.class, p.getId() );
Assert.assertTrue( "Loaded entity is not an instance of the proxy interface", IPerson.class.isInstance( lp ) );
Assert.assertFalse( "Proxy class was not created", Person.class.isInstance( lp ) );
s.remove( lp );
t.commit();
s.close();
}
public interface IPerson {
int getId();
void setId(int id);
}
@Entity( name = "Person" )
@Proxy(proxyClass = IPerson.class)
static class Person implements IPerson {
@Id
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
}

View File

@ -8,84 +8,55 @@ package org.hibernate.orm.test.fetchstrategyhelper;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Proxy;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.internal.FetchOptionsHelper;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.results.graph.FetchOptions;
import org.hibernate.type.AssociationType;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertEquals;
/**
* @author Gail Badner
*/
public class NoProxyFetchStrategyHelperTest extends BaseCoreFunctionalTestCase {
public class FetchStrategyDeterminationTests extends BaseCoreFunctionalTestCase {
@Test
public void testManyToOneDefaultFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntityDefault" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntityDefault" );
assertSame( org.hibernate.FetchMode.JOIN, fetchMode );
final FetchStyle fetchStyle = FetchOptionsHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.JOIN, fetchStyle );
final FetchTiming fetchTiming = FetchOptionsHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
final EntityPersister entityDescriptor = sessionFactory().getMappingMetamodel().getEntityDescriptor( AnEntity.class );
final AttributeMapping attributeMapping = entityDescriptor.findAttributeMapping( "otherEntityDefault" );
final FetchOptions mappedFetchOptions = attributeMapping.getMappedFetchOptions();
assertEquals( mappedFetchOptions.getTiming(), FetchTiming.IMMEDIATE );
assertEquals( mappedFetchOptions.getStyle(), FetchStyle.JOIN );
}
@Test
public void testManyToOneJoinFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntityJoin" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntityJoin" );
assertSame( org.hibernate.FetchMode.JOIN, fetchMode );
final FetchStyle fetchStyle = FetchOptionsHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.JOIN, fetchStyle );
final FetchTiming fetchTiming = FetchOptionsHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
final EntityPersister entityDescriptor = sessionFactory().getMappingMetamodel().getEntityDescriptor( AnEntity.class );
final AttributeMapping attributeMapping = entityDescriptor.findAttributeMapping( "otherEntityJoin" );
final FetchOptions mappedFetchOptions = attributeMapping.getMappedFetchOptions();
assertEquals( mappedFetchOptions.getTiming(), FetchTiming.IMMEDIATE );
assertEquals( mappedFetchOptions.getStyle(), FetchStyle.JOIN );
}
@Test
public void testManyToOneSelectFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntitySelect" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntitySelect" );
assertSame( org.hibernate.FetchMode.SELECT, fetchMode );
final FetchStyle fetchStyle = FetchOptionsHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.SELECT, fetchStyle );
final FetchTiming fetchTiming = FetchOptionsHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
// Proxies are not allowed, so it should be FetchTiming.IMMEDIATE
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
final EntityPersister entityDescriptor = sessionFactory().getMappingMetamodel().getEntityDescriptor( AnEntity.class );
final AttributeMapping attributeMapping = entityDescriptor.findAttributeMapping( "otherEntitySelect" );
final FetchOptions mappedFetchOptions = attributeMapping.getMappedFetchOptions();
assertEquals( mappedFetchOptions.getTiming(), FetchTiming.IMMEDIATE );
assertEquals( mappedFetchOptions.getStyle(), FetchStyle.SELECT );
}
private org.hibernate.FetchMode determineFetchMode(Class<?> entityClass, String path) {
@ -108,7 +79,7 @@ public class NoProxyFetchStrategyHelperTest extends BaseCoreFunctionalTestCase {
OtherEntity.class
};
}
@jakarta.persistence.Entity
@Entity
@Table(name="entity")
public static class AnEntity {
@Id
@ -129,9 +100,8 @@ public class NoProxyFetchStrategyHelperTest extends BaseCoreFunctionalTestCase {
// @Fetch(FetchMode.SUBSELECT) is not allowed for ToOne associations
}
@jakarta.persistence.Entity
@Entity
@Table(name="otherentity")
@Proxy(lazy = false)
public static class OtherEntity {
@Id
@GeneratedValue

View File

@ -9,6 +9,10 @@
package org.hibernate.orm.test.jpa.cascade;
import java.io.Serializable;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
@ -17,17 +21,12 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.Proxy;
/**
* @author Emmanuel Bernard
*/
@Entity
@Table(name = "portal_pk_docs_extraction")
@DynamicUpdate @DynamicInsert
@Proxy
public class ExtractionDocument implements Serializable {
private Long id;
private byte[] body;

View File

@ -1,161 +0,0 @@
/*
* 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.ops.genericApi;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Proxy;
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.Test;
/**
* @author Steve Ebersole
*/
@DomainModel(
annotatedClasses = ProxiedGetLoadAccessTest.UserImpl.class
)
@SessionFactory
public class ProxiedGetLoadAccessTest {
@Test
public void testIt(SessionFactoryScope scope) {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create a row
scope.inTransaction(
session ->
session.persist( new UserImpl( "steve" ) )
);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// test `get` access
scope.inTransaction(
session -> {
// THis technically works
session.get( UserImpl.class, 1 );
session.get( User.class, 1 );
}
);
scope.inTransaction(
session -> {
session.get( UserImpl.class, 1, LockMode.PESSIMISTIC_WRITE );
session.get( User.class, 1, LockMode.PESSIMISTIC_WRITE );
}
);
scope.inTransaction(
session -> {
session.get( UserImpl.class, 1, LockOptions.UPGRADE );
session.get( User.class, 1, LockOptions.UPGRADE );
}
);
scope.inTransaction(
session -> {
session.byId( UserImpl.class ).load( 1 );
session.byId( User.class ).load( 1 );
}
);
scope.inTransaction(
session -> {
session.byId( UserImpl.class ).with( LockOptions.UPGRADE ).load( 1 );
session.byId( User.class ).with( LockOptions.UPGRADE ).load( 1 );
}
);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// test `load` access
scope.inTransaction(
session -> {
session.load( UserImpl.class, 1 );
session.load( User.class, 1 );
}
);
scope.inTransaction(
session -> {
session.load( UserImpl.class, 1, LockMode.PESSIMISTIC_WRITE );
session.load( User.class, 1, LockMode.PESSIMISTIC_WRITE );
}
);
scope.inTransaction(
session -> {
session.load( UserImpl.class, 1, LockOptions.UPGRADE );
session.load( User.class, 1, LockOptions.UPGRADE );
}
);
scope.inTransaction(
session -> {
session.byId( UserImpl.class ).getReference( 1 );
session.byId( User.class ).getReference( 1 );
}
);
scope.inTransaction(
session -> {
session.byId( UserImpl.class ).with( LockOptions.UPGRADE ).getReference( 1 );
session.byId( User.class ).with( LockOptions.UPGRADE ).getReference( 1 );
}
);
}
public interface User {
Integer getId();
String getName();
void setName(String name);
}
@Entity(name = "User")
@Table(name = "my_user")
@Proxy(proxyClass = User.class)
public static class UserImpl implements User {
private Integer id;
private String name;
public UserImpl() {
}
public UserImpl(String name) {
this.name = name;
}
@Id
@GeneratedValue(generator = "increment")
@GenericGenerator(name = "increment", strategy = "increment")
@Override
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -1,111 +0,0 @@
/*
* 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.proxy;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.annotations.Proxy;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
*
* @author lgathy
*/
public class ProxyInterfaceTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { Book.class };
}
@Test
public void testProxyClassLoader() {
//tag::entity-proxy-persist-mapping[]
doInHibernate(this::sessionFactory, session -> {
Book book = new Book();
book.setId(1L);
book.setTitle("High-Performance Java Persistence");
book.setAuthor("Vlad Mihalcea");
session.persist(book);
});
doInHibernate(this::sessionFactory, session -> {
Identifiable book = session.getReference(Book.class, 1L);
assertTrue(
"Loaded entity is not an instance of the proxy interface",
book instanceof Identifiable
);
assertFalse(
"Proxy class was not created",
book instanceof Book
);
});
//end::entity-proxy-persist-mapping[]
}
//tag::entity-proxy-interface-mapping[]
public interface Identifiable {
Long getId();
void setId(Long id);
}
@Entity(name = "Book")
@Proxy(proxyClass = Identifiable.class)
public static final class Book implements Identifiable {
@Id
private Long id;
private String title;
private String author;
@Override
public Long getId() {
return id;
}
@Override
public void setId(Long id) {
this.id = id;
}
//Other getters and setters omitted for brevity
//end::entity-proxy-interface-mapping[]
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
//tag::entity-proxy-interface-mapping[]
}
//end::entity-proxy-interface-mapping[]
}

View File

@ -1,263 +0,0 @@
package org.hibernate.orm.test.proxy;
import jakarta.persistence.*;
import org.hibernate.annotations.Proxy;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author Oliver Henlich
*/
/**
* Test that demonstrates the intermittent {@link ClassCastException} that occurs
* when trying to call a method on a {@link HibernateProxy} where the parameter
* type is defined by generics.
*/
@JiraKey("HHH-17578")
@DomainModel(
annotatedClasses = {
ProxyWithGenericsTest.AbstractEntityImpl.class,
ProxyWithGenericsTest.AbstractShapeEntityImpl.class,
ProxyWithGenericsTest.CircleEntityImpl.class,
ProxyWithGenericsTest.SquareEntityImpl.class,
ProxyWithGenericsTest.CircleContainerEntityImpl.class,
ProxyWithGenericsTest.SquareContainerEntityImpl.class,
ProxyWithGenericsTest.MainEntityImpl.class
}
)
@SessionFactory
@SuppressWarnings("ALL")
public class ProxyWithGenericsTest {
@BeforeEach
void setUp(SessionFactoryScope scope) {
scope.inTransaction(session -> {
EntityManager em = session.unwrap(EntityManager.class);
// Shape 1
CircleEntityImpl cirlce1 = new CircleEntityImpl();
cirlce1.radius = BigDecimal.valueOf(1);
em.persist(cirlce1);
// Shape 2
SquareEntityImpl square1 = new SquareEntityImpl();
square1.width = BigDecimal.valueOf(1);
square1.height = BigDecimal.valueOf(2);
em.persist(square1);
// Container 1
CircleContainerEntity circleContainer1 = new CircleContainerEntityImpl();
em.persist(circleContainer1);
// Container 2
SquareContainerEntity squareContainer1 = new SquareContainerEntityImpl();
em.persist(squareContainer1);
// Main
MainEntityImpl main = new MainEntityImpl();
main.circleContainer = circleContainer1;
main.squareContainer = squareContainer1;
em.persist(main);
});
}
@Test
void test(SessionFactoryScope scope) throws Exception {
scope.inTransaction(session -> {
EntityManager em = session.unwrap(EntityManager.class);
MainEntityImpl main = em.find(MainEntityImpl.class, 1L);
assertNotNull(main);
CircleContainerEntity circleContainer = main.getCircleContainer();
assertNotNull(circleContainer);
assertTrue(circleContainer instanceof HibernateProxy);
CircleEntity circle1 = em.find(CircleEntityImpl.class, 1L);
circleContainer.add(circle1); // This method fails with ClassCastException without the fix
SquareContainerEntity squareContainer = main.getSquareContainer();
assertNotNull(squareContainer);
assertTrue(squareContainer instanceof HibernateProxy);
SquareEntity square1 = em.find(SquareEntityImpl.class, 2L);
squareContainer.add(square1); // This method fails with ClassCastException without the fix
});
}
// Shapes hierarchy -------------------------------------------------------
public interface ShapeEntity {
BigDecimal getArea();
}
public interface CircleEntity extends ShapeEntity {
BigDecimal getRadius();
}
public interface SquareEntity extends ShapeEntity {
BigDecimal getWidth();
BigDecimal getHeight();
}
@MappedSuperclass
@Access(AccessType.FIELD)
public static abstract class AbstractEntityImpl {
@Id
@GeneratedValue
@Column(name = "ID")
Long id;
}
@Entity
@Table(name = "SHAPE")
@Access(AccessType.FIELD)
@Proxy(proxyClass = ShapeEntity.class)
@DiscriminatorColumn(name = "TYPE", length = 20)
public static abstract class AbstractShapeEntityImpl
extends AbstractEntityImpl
implements ShapeEntity {
public abstract BigDecimal getArea();
}
@Entity
@Proxy(proxyClass = CircleEntity.class)
@Access(AccessType.FIELD)
@DiscriminatorValue("CIRCLE")
public static class CircleEntityImpl
extends AbstractShapeEntityImpl
implements CircleEntity {
@Column(name = "RADIUS", nullable = true)
private BigDecimal radius;
@Override
public BigDecimal getArea() {
return new BigDecimal(Math.PI).multiply(radius.pow(2));
}
@Override
public BigDecimal getRadius() {
return radius;
}
}
@Entity
@Proxy(proxyClass = SquareEntity.class)
@Access(AccessType.FIELD)
@DiscriminatorValue("SQUARE")
public static class SquareEntityImpl
extends AbstractShapeEntityImpl
implements SquareEntity {
@Column(name = "WIDTH", nullable = true)
private BigDecimal width;
@Column(name = "HEIGHT", nullable = true)
private BigDecimal height;
@Override
public BigDecimal getArea() {
return width.multiply(height);
}
@Override
public BigDecimal getWidth() {
return width;
}
@Override
public BigDecimal getHeight() {
return height;
}
}
// ShapeContainer hierarchy -----------------------------------------------
public interface ShapeContainerEntity<T extends ShapeEntity> {
void add(T shape);
}
public interface CircleContainerEntity extends ShapeContainerEntity<CircleEntity> {
}
public interface SquareContainerEntity extends ShapeContainerEntity<SquareEntity> {
}
@Entity
@Table(name = "CONTAINER")
@Access(AccessType.FIELD)
@Proxy(proxyClass = ShapeContainerEntity.class)
@DiscriminatorColumn(name = "TYPE", length = 20)
public static abstract class AbstractShapeContainerEntityImpl<T extends ShapeEntity>
extends AbstractEntityImpl
implements ShapeContainerEntity<T> {
}
@Entity
@Proxy(proxyClass = SquareContainerEntity.class)
@Access(AccessType.FIELD)
@DiscriminatorValue("SQUARE")
public static class SquareContainerEntityImpl
extends AbstractShapeContainerEntityImpl<SquareEntity>
implements SquareContainerEntity {
@Override
public void add(SquareEntity shape) {
}
}
@Entity
@Proxy(proxyClass = CircleContainerEntity.class)
@Access(AccessType.FIELD)
@DiscriminatorValue("CIRCLE")
public static class CircleContainerEntityImpl
extends AbstractShapeContainerEntityImpl<CircleEntity>
implements CircleContainerEntity {
@Override
public void add(CircleEntity shape) {
}
}
/**
* Main test entity that has lazy references to the two types of {@link ShapeContainerEntity containers}.
*/
@Entity
@Table(name = "Main")
@Access(AccessType.FIELD)
public static class MainEntityImpl
extends AbstractEntityImpl {
@ManyToOne(targetEntity = AbstractShapeContainerEntityImpl.class, optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "CIRCLE_CONTAINER_ID")
private CircleContainerEntity circleContainer;
@ManyToOne(targetEntity = AbstractShapeContainerEntityImpl.class, optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "SQUARE_CONTAINER_ID")
private SquareContainerEntity squareContainer;
public CircleContainerEntity getCircleContainer() {
return circleContainer;
}
public SquareContainerEntity getSquareContainer() {
return squareContainer;
}
}
}

View File

@ -7,20 +7,19 @@
package org.hibernate.orm.test.envers.entities;
import java.io.Serializable;
import org.hibernate.envers.Audited;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.annotations.Proxy;
import org.hibernate.envers.Audited;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@Table(name = "STR_TEST_NP")
@Proxy(lazy = false)
public class StrTestNoProxyEntity implements Serializable {
@Id
@GeneratedValue

View File

@ -6,6 +6,9 @@
*/
package org.hibernate.orm.test.envers.entities.onetoone;
import org.hibernate.envers.Audited;
import org.hibernate.envers.NotAudited;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
@ -13,16 +16,11 @@ import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import org.hibernate.annotations.Proxy;
import org.hibernate.envers.Audited;
import org.hibernate.envers.NotAudited;
/**
* Test class for issue HHH-3854. Restricting creation of proxy objects is essential.
*/
@Entity
@Audited
@Proxy(lazy = false)
// Class name is too long of an identifier for Oracle.
@Table(name = "EdOneToOne")
public final class BidirectionalEagerAnnotationRefEdOneToOne {

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.orm.test.envers.entities.onetoone;
import org.hibernate.envers.Audited;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
@ -15,15 +17,11 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import org.hibernate.annotations.Proxy;
import org.hibernate.envers.Audited;
/**
* Test class for issue HHH-3854. Restricting creation of proxy objects is essential.
*/
@Entity
@Audited
@Proxy(lazy = false)
//Class name is too long of an identifier for Oracle.
@Table(name = "IngOneToOne")
public final class BidirectionalEagerAnnotationRefIngOneToOne {

View File

@ -30,13 +30,9 @@ public class AccountNotAuditedOwners implements Serializable {
private String type;
@OneToOne(mappedBy = "account", optional = false)
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
private NotAuditedNoProxyPerson owner;
@OneToOne(mappedBy = "account", optional = false, fetch = FetchType.LAZY)
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
private NotAuditedProxyPerson coOwner;
private NotAuditedPerson owner;
public AccountNotAuditedOwners() {
}
@ -91,22 +87,14 @@ public class AccountNotAuditedOwners implements Serializable {
this.accountId = accountId;
}
public NotAuditedNoProxyPerson getOwner() {
public NotAuditedPerson getOwner() {
return owner;
}
public void setOwner(NotAuditedNoProxyPerson owner) {
public void setOwner(NotAuditedPerson owner) {
this.owner = owner;
}
public NotAuditedProxyPerson getCoOwner() {
return coOwner;
}
public void setCoOwner(NotAuditedProxyPerson coOwner) {
this.coOwner = coOwner;
}
public String getType() {
return type;
}

View File

@ -7,6 +7,10 @@
package org.hibernate.orm.test.envers.integration.onetoone.bidirectional.primarykeyjoincolumn;
import java.io.Serializable;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
@ -14,15 +18,10 @@ import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PrimaryKeyJoinColumn;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Proxy;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@Proxy(lazy = false)
public class NotAuditedNoProxyPerson implements Serializable {
@Id
@Column(name = "PERSON_ID")

View File

@ -7,6 +7,10 @@
package org.hibernate.orm.test.envers.integration.onetoone.bidirectional.primarykeyjoincolumn;
import java.io.Serializable;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
@ -15,16 +19,11 @@ import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PrimaryKeyJoinColumn;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Proxy;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@Proxy(lazy = true)
public class NotAuditedProxyPerson implements Serializable {
public class NotAuditedPerson implements Serializable {
@Id
@Column(name = "PERSON_ID")
@GeneratedValue(generator = "NotAuditedProxyKeyGenerator")
@ -38,14 +37,14 @@ public class NotAuditedProxyPerson implements Serializable {
@PrimaryKeyJoinColumn(name = "PERSON_ID", referencedColumnName = "ACCOUNT_ID")
private AccountNotAuditedOwners account;
public NotAuditedProxyPerson() {
public NotAuditedPerson() {
}
public NotAuditedProxyPerson(String name) {
public NotAuditedPerson(String name) {
this.name = name;
}
public NotAuditedProxyPerson(Long personId, String name) {
public NotAuditedPerson(Long personId, String name) {
this.personId = personId;
this.name = name;
}
@ -55,11 +54,11 @@ public class NotAuditedProxyPerson implements Serializable {
if ( this == o ) {
return true;
}
if ( !(o instanceof NotAuditedProxyPerson) ) {
if ( !(o instanceof NotAuditedPerson ) ) {
return false;
}
NotAuditedProxyPerson person = (NotAuditedProxyPerson) o;
NotAuditedPerson person = (NotAuditedPerson) o;
if ( personId != null ? !personId.equals( person.personId ) : person.personId != null ) {
return false;

View File

@ -7,7 +7,6 @@
package org.hibernate.orm.test.envers.integration.onetoone.bidirectional.primarykeyjoincolumn;
import java.util.Arrays;
import jakarta.persistence.EntityManager;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.query.AuditEntity;
@ -19,6 +18,8 @@ import org.hibernate.testing.TestForIssue;
import org.junit.Assert;
import org.junit.Test;
import jakarta.persistence.EntityManager;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@ -27,7 +28,6 @@ public class OneToOneWithPrimaryKeyJoinTest extends BaseEnversJPAFunctionalTestC
private Long personId = null;
private Long accountId = null;
private Long proxyPersonId = null;
private Long noProxyPersonId = null;
private Long accountNotAuditedOwnersId = null;
@Override
@ -36,8 +36,7 @@ public class OneToOneWithPrimaryKeyJoinTest extends BaseEnversJPAFunctionalTestC
Person.class,
Account.class,
AccountNotAuditedOwners.class,
NotAuditedNoProxyPerson.class,
NotAuditedProxyPerson.class
NotAuditedPerson.class
};
}
@ -58,15 +57,11 @@ public class OneToOneWithPrimaryKeyJoinTest extends BaseEnversJPAFunctionalTestC
// Revision 2
em.getTransaction().begin();
NotAuditedNoProxyPerson noProxyPerson = new NotAuditedNoProxyPerson( "Kinga" );
NotAuditedProxyPerson proxyPerson = new NotAuditedProxyPerson( "Lukasz" );
NotAuditedPerson proxyPerson = new NotAuditedPerson( "Lukasz" );
AccountNotAuditedOwners accountNotAuditedOwners = new AccountNotAuditedOwners( "Standard" );
noProxyPerson.setAccount( accountNotAuditedOwners );
proxyPerson.setAccount( accountNotAuditedOwners );
accountNotAuditedOwners.setOwner( noProxyPerson );
accountNotAuditedOwners.setCoOwner( proxyPerson );
accountNotAuditedOwners.setOwner( proxyPerson );
em.persist( accountNotAuditedOwners );
em.persist( noProxyPerson );
em.persist( proxyPerson );
em.getTransaction().commit();
@ -74,7 +69,6 @@ public class OneToOneWithPrimaryKeyJoinTest extends BaseEnversJPAFunctionalTestC
accountId = account.getAccountId();
accountNotAuditedOwnersId = accountNotAuditedOwners.getAccountId();
proxyPersonId = proxyPerson.getPersonId();
noProxyPersonId = noProxyPerson.getPersonId();
}
@Test
@ -127,16 +121,13 @@ public class OneToOneWithPrimaryKeyJoinTest extends BaseEnversJPAFunctionalTestC
@Test
public void testHistoryOfAccountNotAuditedOwners() {
NotAuditedNoProxyPerson noProxyPersonVer1 = new NotAuditedNoProxyPerson( noProxyPersonId, "Kinga" );
NotAuditedProxyPerson proxyPersonVer1 = new NotAuditedProxyPerson( proxyPersonId, "Lukasz" );
NotAuditedPerson proxyPersonVer1 = new NotAuditedPerson( proxyPersonId, "Lukasz" );
AccountNotAuditedOwners accountNotAuditedOwnersVer1 = new AccountNotAuditedOwners(
accountNotAuditedOwnersId,
"Standard"
);
noProxyPersonVer1.setAccount( accountNotAuditedOwnersVer1 );
proxyPersonVer1.setAccount( accountNotAuditedOwnersVer1 );
accountNotAuditedOwnersVer1.setOwner( noProxyPersonVer1 );
accountNotAuditedOwnersVer1.setCoOwner( proxyPersonVer1 );
accountNotAuditedOwnersVer1.setOwner( proxyPersonVer1 );
Object[] result = ((Object[]) getAuditReader().createQuery()
.forRevisionsOfEntity( AccountNotAuditedOwners.class, false, true )
@ -146,13 +137,10 @@ public class OneToOneWithPrimaryKeyJoinTest extends BaseEnversJPAFunctionalTestC
Assert.assertEquals( accountNotAuditedOwnersVer1, result[0] );
Assert.assertEquals( RevisionType.ADD, result[2] );
// Checking non-proxy reference
Assert.assertEquals( accountNotAuditedOwnersVer1.getOwner(), ((AccountNotAuditedOwners) result[0]).getOwner() );
// Checking proxy reference
Assert.assertTrue( ((AccountNotAuditedOwners) result[0]).getCoOwner() instanceof HibernateProxy );
Assert.assertTrue( ((AccountNotAuditedOwners) result[0]).getOwner() instanceof HibernateProxy );
Assert.assertEquals(
proxyPersonVer1.getPersonId(),
((AccountNotAuditedOwners) result[0]).getCoOwner().getPersonId()
((AccountNotAuditedOwners) result[0]).getOwner().getPersonId()
);
Assert.assertEquals(

View File

@ -115,6 +115,26 @@ String isDefault();
----
[[proxy-annotation]]
== Replace @Proxy
Applications will need to replace usages of the removed `@Proxy` annotation.
`@Proxy#proxyClass` has no direct replacement, but was also never needed/useful.
Here we focus on `@Proxy#laxy` attribute which, again, was hardly ever useful.
By default (true), Hibernate would proxy an entity when possible and when asked for.
"Asked for" includes calls to `Session#getReference` and lazy associations.
All such cases though are already controllable by the application.
* Instead of `Session#getReference`, use `Session#find`
* Use eager associations, using
** `FetchType.EAGER` (the default for to-one associations anyway), possibly combined with `@Fetch`
** `EntityGraph`
** `@FetchProfiles`
The effect can also often be mitigated using Hibernate's bytecode-based laziness (possibly combined with `@ConcreteProxy`).
[[flush-persist]]
== Session flush and persist