HHH-17818 Add a UG paragraph and examples for `@ConcreteProxy`

This commit is contained in:
Marco Belladelli 2024-04-19 10:47:04 +02:00
parent 84cb94b990
commit 7adab31924
4 changed files with 97 additions and 2 deletions

View File

@ -462,6 +462,10 @@ 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. 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. 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]] [[entity-proxy-interface-mapping]]
.Final entity class implementing the `Identifiable` interface .Final entity class implementing the `Identifiable` interface
==== ====
@ -493,6 +497,62 @@ 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 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. constructed without needing to fetch the actual entity POJO.
[[concrete-proxy]]
==== Create proxies that resolve their inheritance subtype
When working with lazy associations or entity references for types that define and inheritance hierarchy Hibernate often creates proxies starting from the root class, with no information about the actual subtype that's referenced by the lazy instance. This can be a problem when using `instanceof` to check the type of said lazy entity references or when trying to cast to the concrete subtype.
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/ConcreteProxy.html[`@ConcreteProxy`] annotation can be used on an entity hierarchy root mapping to specify that Hibernate should always try to resolve the actual subtype corresponding to the proxy instance created. This effectively means that proxies for that entity hierarchy will always be created from the correct subclass, allowing to preserve laziness and enable using type checks and casts.
[[entity-concrete-proxy-mapping]]
.Root entity class annotated with `@ConcreteProxy`
====
[source,java]
----
include::{example-dir-proxy}/concrete/AbstractConcreteProxyTest.java[tag=entity-concrete-proxy-mapping,indent=0]
----
====
In the following example we load the parent's lazy association and resolve to the concrete `SingleSubChild1` type:
[[entity-concrete-proxy-find]]
.Loading parent entity with lazy association
====
[source,java]
----
include::{example-dir-proxy}/concrete/AbstractConcreteProxyTest.java[tag=entity-concrete-proxy-find,indent=0]
----
[source,sql]
----
include::{extrasdir}/entity/entity-concrete-proxy-find.sql[]
----
====
IMPORTANT: This added functionality does not come free: in order to determine the concrete type to use when creating the Proxy instance, Hibernate might need to access the association target's table(s) to discover the actual subtype corresponding to a specific identifier value.
The concrete type will be determined:
* With *single table* inheritance, the discriminator column value will be left joined when fetching associations or simply read from the entity table when getting references.
* When using *joined* inheritance, all subtype tables will need to be left joined to determine the concrete type. Note however that when using an explicit discriminator column, the behavior is the same as for single-table inheritance.
* Finally, for *table-per-class* inheritance, all subtype tables will need to be (union) queried to determine the concrete type.
In the following example, you can see how Hibernate issues a query to resolve the concrete proxy type for an entity reference:
[[entity-concrete-proxy-reference]]
.Resolving the concrete proxy type for `getReference()`:
====
[source,java]
----
include::{example-dir-proxy}/concrete/AbstractConcreteProxyTest.java[tag=entity-concrete-proxy-reference,indent=0]
----
[source,sql]
----
include::{extrasdir}/entity/entity-concrete-proxy-reference.sql[]
----
====
[[entity-persister]] [[entity-persister]]
==== Define a custom entity persister ==== Define a custom entity persister

View File

@ -0,0 +1,15 @@
select
sp1_0.id,
sp1_0.single_id,
s1_0.disc_col
from
SingleParent sp1_0
left join
SingleBase s1_0
on s1_0.id=sp1_0.single_id
where
sp1_0.id=?
-- binding parameter (1:BIGINT) <- [1]
-- extracted value (2:BIGINT) -> [1]
-- extracted value (3:VARCHAR) -> [SingleSubChild1]

View File

@ -0,0 +1,10 @@
select
sc1_0.disc_col
from
SingleBase sc1_0
where
sc1_0.disc_col in ('SingleChild1', 'SingleSubChild1')
and sc1_0.id=?
-- binding parameter (1:BIGINT) <- [1]
-- extracted value (1:VARCHAR) -> [SingleSubChild1]

View File

@ -41,11 +41,13 @@ public abstract class AbstractConcreteProxyTest extends BaseNonConfigCoreFunctio
inspector.clear(); inspector.clear();
// test find and association // test find and association
inSession( session -> { inSession( session -> {
//tag::entity-concrete-proxy-find[]
final SingleParent parent1 = session.find( SingleParent.class, 1L ); final SingleParent parent1 = session.find( SingleParent.class, 1L );
assertThat( parent1.getSingle(), instanceOf( SingleSubChild1.class ) ); assertThat( parent1.getSingle(), instanceOf( SingleSubChild1.class ) );
assertThat( Hibernate.isInitialized( parent1.getSingle() ), is( false ) ); assertThat( Hibernate.isInitialized( parent1.getSingle() ), is( false ) );
final SingleSubChild1 proxy = (SingleSubChild1) parent1.getSingle(); final SingleSubChild1 proxy = (SingleSubChild1) parent1.getSingle();
assertThat( Hibernate.isInitialized( proxy ), is( false ) ); assertThat( Hibernate.isInitialized( proxy ), is( false ) );
//end::entity-concrete-proxy-find[]
inspector.assertExecutedCount( 1 ); inspector.assertExecutedCount( 1 );
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 1 ); inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 1 );
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 1 ); inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 1 );
@ -68,9 +70,13 @@ public abstract class AbstractConcreteProxyTest extends BaseNonConfigCoreFunctio
inspector.clear(); inspector.clear();
// test get reference // test get reference
inSession( session -> { inSession( session -> {
//tag::entity-concrete-proxy-reference[]
final SingleChild1 proxy1 = session.getReference( SingleChild1.class, 1L ); final SingleChild1 proxy1 = session.getReference( SingleChild1.class, 1L );
assertThat( proxy1, instanceOf( SingleSubChild1.class ) ); assertThat( proxy1, instanceOf( SingleSubChild1.class ) );
assertThat( Hibernate.isInitialized( proxy1 ), is( false ) ); assertThat( Hibernate.isInitialized( proxy1 ), is( false ) );
final SingleSubChild1 subChild1 = (SingleSubChild1) proxy1;
assertThat( Hibernate.isInitialized( subChild1 ), is( false ) );
//end::entity-concrete-proxy-reference[]
inspector.assertExecutedCount( 1 ); inspector.assertExecutedCount( 1 );
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 2 ); inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 2 );
inspector.clear(); inspector.clear();
@ -286,6 +292,9 @@ public abstract class AbstractConcreteProxyTest extends BaseNonConfigCoreFunctio
return (SQLStatementInspector) sessionFactory().getSessionFactoryOptions().getStatementInspector(); return (SQLStatementInspector) sessionFactory().getSessionFactoryOptions().getStatementInspector();
} }
// InheritanceType.SINGLE_TABLE
//tag::entity-concrete-proxy-mapping[]
@Entity( name = "SingleParent" ) @Entity( name = "SingleParent" )
public static class SingleParent { public static class SingleParent {
@Id @Id
@ -307,8 +316,6 @@ public abstract class AbstractConcreteProxyTest extends BaseNonConfigCoreFunctio
} }
} }
// InheritanceType.SINGLE_TABLE
@Entity( name = "SingleBase" ) @Entity( name = "SingleBase" )
@Inheritance( strategy = InheritanceType.SINGLE_TABLE ) @Inheritance( strategy = InheritanceType.SINGLE_TABLE )
@DiscriminatorColumn( name = "disc_col" ) @DiscriminatorColumn( name = "disc_col" )
@ -351,6 +358,9 @@ public abstract class AbstractConcreteProxyTest extends BaseNonConfigCoreFunctio
} }
} }
// Other subtypes omitted for brevity
//end::entity-concrete-proxy-mapping[]
@Entity( name = "SingleChild2" ) @Entity( name = "SingleChild2" )
public static class SingleChild2 extends SingleBase { public static class SingleChild2 extends SingleBase {
private Integer child2Prop; private Integer child2Prop;