HHH-15739 deprecate @LazyToOne and @LazyCollection
and add some docs and cleanups
This commit is contained in:
parent
c8ffee43ef
commit
5b5721f64b
|
@ -990,19 +990,7 @@ See the <<chapters/domain/associations.adoc#associations-JoinFormula,`@JoinFormu
|
|||
[[annotations-hibernate-lazycollection]]
|
||||
==== `@LazyCollection`
|
||||
|
||||
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/LazyCollection.html[`@LazyCollection`] annotation is used to specify the lazy fetching behavior of a given collection.
|
||||
The possible values are given by the `https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/LazyCollectionOption.html[LazyCollectionOption]` enumeration:
|
||||
|
||||
`TRUE`:: Load it when the state is requested.
|
||||
`FALSE`:: Eagerly load it.
|
||||
`EXTRA`:: Prefer extra queries over full collection loading.
|
||||
|
||||
The `TRUE` and `FALSE` values are deprecated since you should be using the Jakarta Persistence {jpaJavadocUrlPrefix}FetchType.html[`FetchType`] attribute of the <<annotations-jpa-elementcollection>>, <<annotations-jpa-onetomany>>, or <<annotations-jpa-manytomany>> collection.
|
||||
|
||||
The `EXTRA` value has no equivalent in the Jakarta Persistence specification, and it's used to avoid loading the entire collection even when the collection is accessed for the first time.
|
||||
Each element is fetched individually using a secondary query.
|
||||
|
||||
See the <<chapters/fetching/Fetching.adoc#fetching-LazyCollection, `@LazyCollection` mapping>> section for more info.
|
||||
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Index.html[[line-through]#`@LazyCollection`#] annotation is deprecated.
|
||||
|
||||
[[annotations-hibernate-lazygroup]]
|
||||
==== `@LazyGroup`
|
||||
|
@ -1019,15 +1007,7 @@ See the <<chapters/pc/BytecodeEnhancement.adoc#BytecodeEnhancement-lazy-loading-
|
|||
[[annotations-hibernate-lazytoone]]
|
||||
==== `@LazyToOne`
|
||||
|
||||
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/LazyToOne.html[`@LazyToOne`] annotation is used to specify the laziness options, represented by https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/LazyToOneOption.html[`LazyToOneOption`], available for a `@OneToOne` or `@ManyToOne` association.
|
||||
|
||||
`LazyToOneOption` defines the following alternatives:
|
||||
|
||||
FALSE:: Eagerly load the association. This one is not needed since the Jakarta Persistence `FetchType.EAGER` offers the same behavior.
|
||||
NO_PROXY:: This option will fetch the association lazily while returning real entity object.
|
||||
PROXY:: This option will fetch the association lazily while returning a proxy instead.
|
||||
|
||||
See the <<chapters/domain/associations.adoc#associations-one-to-one-bidirectional-lazy,`@LazyToOne` mapping example>> section for more info.
|
||||
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Index.html[[line-through]#`@LazyToOne`#] annotation is deprecated.
|
||||
|
||||
[[annotations-hibernate-listindexbase]]
|
||||
==== `@ListIndexBase`
|
||||
|
|
|
@ -236,8 +236,7 @@ The only way to figure out whether there is an associated record on the child si
|
|||
Because this can lead to N+1 query issues, it's much more efficient to use unidirectional `@OneToOne` associations with the `@MapsId` annotation in place.
|
||||
|
||||
However, if you really need to use a bidirectional association and want to make sure that this is always going to be fetched lazily,
|
||||
then you need to enable lazy state initialization bytecode enhancement and use the
|
||||
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/LazyToOne.html[`@LazyToOne`] annotation as well.
|
||||
then you need to enable lazy state initialization bytecode enhancement.
|
||||
|
||||
[[associations-one-to-one-bidirectional-lazy-example]]
|
||||
.Bidirectional `@OneToOne` lazy parent-side association
|
||||
|
|
|
@ -15,14 +15,10 @@ import jakarta.persistence.Id;
|
|||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToOne;
|
||||
|
||||
import org.hibernate.annotations.LazyToOne;
|
||||
import org.hibernate.annotations.LazyToOneOption;
|
||||
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
|
||||
/**
|
||||
* @author Vlad Mihalcea
|
||||
*/
|
||||
|
@ -58,7 +54,6 @@ public class OneToOneBidirectionalLazyTest extends BaseEntityManagerFunctionalTe
|
|||
orphanRemoval = true,
|
||||
fetch = FetchType.LAZY
|
||||
)
|
||||
@LazyToOne(LazyToOneOption.NO_PROXY)
|
||||
private PhoneDetails details;
|
||||
|
||||
//Getters and setters are omitted for brevity
|
||||
|
|
|
@ -47,6 +47,47 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttrib
|
|||
* are intended for use by generic code that must materialize an "amputated" graph of
|
||||
* Hibernate entities. (For example, a library which deserializes entities from JSON.)
|
||||
* <p>
|
||||
* Lazy fetching of a {@linkplain jakarta.persistence.OneToOne one to one} or
|
||||
* {@linkplain jakarta.persistence.ManyToOne many to one} association requires special
|
||||
* bytecode tricks. The tricks used depend on whether build-time bytecode enhancement
|
||||
* is enabled.
|
||||
* <p>
|
||||
* When bytecode enhancement is <em>not</em> used, an unfetched lazy association* is
|
||||
* represented by a <em>proxy object</em> which holds the identifier (foreign key) of
|
||||
* the associated entity instance.
|
||||
* <ul>
|
||||
* <li>The identifier property of the proxy object is set when the proxy is instantiated.
|
||||
* The program may obtain the entity identifier value of an unfetched proxy, without
|
||||
* triggering lazy fetching, by calling the corresponding getter method.
|
||||
* (It's even possible to set an association to reference an unfetched proxy.)
|
||||
* <li>A delegate entity instance is lazily fetched when any other method of the proxy
|
||||
* is called. Once fetched, the proxy delegates all method invocations to the
|
||||
* delegate.
|
||||
* <li>The proxy does not have the same concrete type as the proxied delegate, and so
|
||||
* {@link #getClass(Object)} must be used in place of {@link Object#getClass()},
|
||||
* and this method fetches the entity by side-effect.
|
||||
* <li>For a polymorphic association, the concrete type of the associated entity is
|
||||
* not known until the delegate is fetched from the database, and so
|
||||
* {@link #unproxy(Object, Class)}} must be used to perform typecasts, and
|
||||
* {@link #getClass(Object)} must be used instead of the Java {@code instanceof}
|
||||
* operator.
|
||||
* </ul>
|
||||
* When bytecode enhancement <em>is</em> used, there is no such indirection, but the
|
||||
* associated entity instance is initially in an unloaded state, with only its
|
||||
* identifier field set.
|
||||
* <ul>
|
||||
* <li>The identifier field of an unloaded entity instance is set when the unloaded
|
||||
* instance is instantiated. The program may obtain the identifier of an unloaded
|
||||
* entity, without triggering lazy loading, by accessing the field containing the
|
||||
* identifier.
|
||||
* <li>The remaining non-lazy state of the entity instance is loaded lazily when any
|
||||
* other field is accessed.
|
||||
* <li>Typecasts, the Java {@code instanceof} operator, and {@link Object#getClass()}
|
||||
* may be used as normal.
|
||||
* </ul>
|
||||
* As an exception to the above rules, <em>polymorphic</em> associations always work
|
||||
* as if bytecode enhancement was not enabled.
|
||||
*<p>
|
||||
* Graphs of Hibernate entities obtained from a {@link Session} are usually in an
|
||||
* amputated form, with associations and collections replaced by proxies and lazy
|
||||
* collections. (That is, by instances of the internal types {@link HibernateProxy}
|
||||
|
|
|
@ -10,18 +10,33 @@ import java.lang.annotation.ElementType;
|
|||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Specify the laziness of a collection, either a
|
||||
* {@link jakarta.persistence.OneToMany} or
|
||||
* {@link jakarta.persistence.ManyToMany} association,
|
||||
* or an {@link jakarta.persistence.ElementCollection}.
|
||||
* <p>
|
||||
* This is an alternative to specifying the JPA
|
||||
* {@link jakarta.persistence.FetchType}.
|
||||
* {@link jakarta.persistence.FetchType}. This annotation
|
||||
* is used to enable {@linkplain LazyCollectionOption#EXTRA
|
||||
* extra-lazy collection fetching}.
|
||||
*
|
||||
* @author Emmanuel Bernard
|
||||
*
|
||||
* @deprecated
|
||||
* <ul>
|
||||
* <li>Use the JPA-defined {@link jakarta.persistence.FetchType#EAGER}
|
||||
* instead of {@code LazyCollection(FALSE)}.
|
||||
* <li>Use static methods of {@link org.hibernate.Hibernate},
|
||||
* for example {@link org.hibernate.Hibernate#size(Collection)},
|
||||
* {@link org.hibernate.Hibernate#contains(Collection, Object)},
|
||||
* or {@link org.hibernate.Hibernate#get(Map, Object)} instead
|
||||
* of {@code LazyCollection(EXTRA)}.
|
||||
* </ul>
|
||||
*/
|
||||
@Deprecated(since="6.2")
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface LazyCollection {
|
||||
|
|
|
@ -6,22 +6,54 @@
|
|||
*/
|
||||
package org.hibernate.annotations;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Lazy options available for a collection.
|
||||
* Enumerates the options for lazy loading of a
|
||||
* {@linkplain jakarta.persistence.ElementCollection collection},
|
||||
* {@linkplain jakarta.persistence.ManyToOne many to one association},
|
||||
* or {@linkplain jakarta.persistence.ManyToMany many to many association}.
|
||||
*
|
||||
* @author Emmanuel Bernard
|
||||
*
|
||||
* @see LazyCollection
|
||||
*
|
||||
* @deprecated
|
||||
* <ul>
|
||||
* <li>Use the JPA-defined {@link jakarta.persistence.FetchType#EAGER}
|
||||
* instead of {@code LazyCollection(FALSE)}.
|
||||
* <li>Use static methods of {@link org.hibernate.Hibernate},
|
||||
* for example {@link org.hibernate.Hibernate#size(Collection)},
|
||||
* {@link org.hibernate.Hibernate#contains(Collection, Object)},
|
||||
* or {@link org.hibernate.Hibernate#get(Map, Object)} instead
|
||||
* of {@code LazyCollection(EXTRA)}.
|
||||
* </ul>
|
||||
*/
|
||||
@Deprecated
|
||||
public enum LazyCollectionOption {
|
||||
/**
|
||||
* Eagerly load it.
|
||||
* The collection is always loaded eagerly, and all its
|
||||
* elements are available immediately. However, access to
|
||||
* the collection is still mediated by an instance of
|
||||
* {@link org.hibernate.collection.spi.PersistentCollection},
|
||||
* which tracks modifications to the collection.
|
||||
*/
|
||||
FALSE,
|
||||
/**
|
||||
* Load it when the state is requested.
|
||||
* The underlying Java collection is proxied by an instance of
|
||||
* {@link org.hibernate.collection.spi.PersistentCollection}
|
||||
* and lazily fetched when a method of the proxy is called.
|
||||
* All elements of the collection are retrieved at once.
|
||||
*/
|
||||
TRUE,
|
||||
/**
|
||||
* Prefer extra queries over full collection loading.
|
||||
* The underlying Java collection is proxied by an instance of
|
||||
* {@link org.hibernate.collection.spi.PersistentCollection}
|
||||
* and its state is fetched lazily from the database as needed,
|
||||
* when methods of the proxy are called. When reasonable, the
|
||||
* proxy will avoid fetching all elements of the collection
|
||||
* at once.
|
||||
*/
|
||||
EXTRA
|
||||
}
|
||||
|
|
|
@ -14,52 +14,16 @@ import java.lang.annotation.Target;
|
|||
/**
|
||||
* Specifies the machinery used to handle lazy fetching of
|
||||
* the annotated {@link jakarta.persistence.OneToOne} or
|
||||
* {@link jakarta.persistence.ManyToOne} association. This
|
||||
* is an alternative to specifying only the JPA
|
||||
* {@link jakarta.persistence.FetchType}. This annotation
|
||||
* is occasionally useful, since there are observable
|
||||
* differences in semantics between:
|
||||
* <ul>
|
||||
* <li>{@linkplain LazyToOneOption#FALSE eager fetching},
|
||||
* <li>lazy fetching via interception of calls on a
|
||||
* {@linkplain LazyToOneOption#PROXY proxy object},
|
||||
* and
|
||||
* <li>lazy fetching via interception of field access on
|
||||
* the {@linkplain LazyToOneOption#NO_PROXY owning side}
|
||||
* of the association.
|
||||
* </ul>
|
||||
* By default, an unfetched lazy association is represented
|
||||
* by a <em>proxy object</em> which holds the identifier
|
||||
* (foreign key) of the associated entity instance.
|
||||
* It's possible to obtain the identifier from an unfetched
|
||||
* proxy, without fetching the entity from the database, by
|
||||
* calling the corresponding getter method. (It's even
|
||||
* possible to set an association to reference an unfetched
|
||||
* proxy.) Lazy fetching occurs when any other method of the
|
||||
* proxy is called. Once fetched, the proxy delegates all
|
||||
* method invocations to the fetched entity instance.
|
||||
* For a polymorphic association, the concrete type of the
|
||||
* entity instance represented by a proxy is unknown, and
|
||||
* so {@link org.hibernate.Hibernate#getClass(Object)} must
|
||||
* be used to obtain the concrete type, fetching the entity
|
||||
* by side effect. Similarly, typecasts must be performed
|
||||
* using {@link org.hibernate.Hibernate#unproxy(Object, Class)}.
|
||||
* <p>
|
||||
* With {@code LazyToOne(NO_PROXY)}, an associated entity
|
||||
* instance begins in an unloaded state, with only its
|
||||
* identifier field set. Thus, it's possible to obtain the
|
||||
* identifier if an unloaded entity, without triggering
|
||||
* lazy loading. Typecasts, {@code instanceof}, and
|
||||
* {@link Object#getClass()} work as normal. But this
|
||||
* option is only available when bytecode enhancement is
|
||||
* used.
|
||||
* <p>
|
||||
* <strong>Currently, Hibernate does not support
|
||||
* {@code LazyToOne(NO_PROXY)} for polymorphic associations,
|
||||
* and instead falls back to using a proxy!</strong>
|
||||
* {@link jakarta.persistence.ManyToOne} association.
|
||||
* This is an alternative to specifying only the JPA
|
||||
* {@link jakarta.persistence.FetchType}.
|
||||
*
|
||||
* @author Emmanuel Bernard
|
||||
*
|
||||
* @deprecated use JPA annotations to specify the
|
||||
* {@link jakarta.persistence.FetchType}
|
||||
*/
|
||||
@Deprecated(since="6.2")
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface LazyToOne {
|
||||
|
|
|
@ -15,14 +15,21 @@ package org.hibernate.annotations;
|
|||
* @author Emmanuel Bernard
|
||||
*
|
||||
* @see LazyToOne
|
||||
*
|
||||
* @deprecated since {@link LazyToOne} is deprecated, use
|
||||
* {@link jakarta.persistence.FetchType} instead
|
||||
*/
|
||||
@Deprecated(since="6.2")
|
||||
public enum LazyToOneOption {
|
||||
/**
|
||||
* The association is always loaded eagerly. The identifier
|
||||
* and concrete type of the associated entity instance,
|
||||
* along with all the rest of its non-lazy fields, are always
|
||||
* available immediately.
|
||||
*
|
||||
* @deprecated use {@link jakarta.persistence.FetchType#EAGER}
|
||||
*/
|
||||
@Deprecated
|
||||
FALSE,
|
||||
/**
|
||||
* The association is proxied and a delegate entity instance
|
||||
|
@ -38,7 +45,8 @@ public enum LazyToOneOption {
|
|||
* <li>The proxy does not have the same concrete type as the
|
||||
* proxied delegate, and so
|
||||
* {@link org.hibernate.Hibernate#getClass(Object)}
|
||||
* must be used in place of {@link Object#getClass()}.
|
||||
* must be used in place of {@link Object#getClass()},
|
||||
* and this method fetches the entity by side-effect.
|
||||
* <li>For a polymorphic association, the concrete type of
|
||||
* the proxied entity instance is not known until the
|
||||
* delegate is fetched from the database, and so
|
||||
|
@ -48,7 +56,10 @@ public enum LazyToOneOption {
|
|||
* must be used instead of the Java {@code instanceof}
|
||||
* operator.
|
||||
* </ul>
|
||||
*
|
||||
* @deprecated use {@link jakarta.persistence.FetchType#LAZY}
|
||||
*/
|
||||
@Deprecated
|
||||
PROXY,
|
||||
/**
|
||||
* The associated entity instance is initially in an unloaded
|
||||
|
@ -65,9 +76,11 @@ public enum LazyToOneOption {
|
|||
* <li>Bytecode enhancement is required. If the class is not
|
||||
* enhanced, this option is equivalent to {@link #PROXY}.
|
||||
* </ul>
|
||||
* <strong>Currently, Hibernate does not support this setting
|
||||
* for polymorphic associations, and instead falls back to
|
||||
* {@link #PROXY}!</strong>
|
||||
* Hibernate does not support this setting for polymorphic
|
||||
* associations, and instead falls back to {@link #PROXY}.
|
||||
*
|
||||
* @deprecated this setting no longer has any useful effect
|
||||
*/
|
||||
@Deprecated
|
||||
NO_PROXY
|
||||
}
|
||||
|
|
|
@ -1014,11 +1014,7 @@ public class BinderHelper {
|
|||
}
|
||||
|
||||
private static GenerationType interpretGenerationType(GeneratedValue generatedValueAnn) {
|
||||
if ( generatedValueAnn.strategy() == null ) {
|
||||
return GenerationType.AUTO;
|
||||
}
|
||||
|
||||
return generatedValueAnn.strategy();
|
||||
return generatedValueAnn.strategy() == null ? GenerationType.AUTO : generatedValueAnn.strategy();
|
||||
}
|
||||
|
||||
public static boolean isEmptyAnnotationValue(String annotationString) {
|
||||
|
@ -1265,7 +1261,14 @@ public class BinderHelper {
|
|||
}
|
||||
|
||||
public static FetchMode getFetchMode(FetchType fetch) {
|
||||
return fetch == FetchType.EAGER ? FetchMode.JOIN : FetchMode.SELECT;
|
||||
switch ( fetch ) {
|
||||
case EAGER:
|
||||
return FetchMode.JOIN;
|
||||
case LAZY:
|
||||
return FetchMode.SELECT;
|
||||
default:
|
||||
throw new AssertionFailure("unknown fetch type: " + fetch);
|
||||
}
|
||||
}
|
||||
|
||||
private static CascadeType convertCascadeType(jakarta.persistence.CascadeType cascade) {
|
||||
|
|
|
@ -131,31 +131,31 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
return !isEmptyAnnotationValue( mappedBy ) ? ForeignKeyDirection.TO_PARENT : ForeignKeyDirection.FROM_PARENT;
|
||||
}
|
||||
|
||||
private void bindOwned(Map<String, PersistentClass> persistentClasses, OneToOne value, Property property) {
|
||||
value.setMappedByProperty( mappedBy );
|
||||
final PersistentClass targetEntity = persistentClasses.get( value.getReferencedEntityName() );
|
||||
private void bindOwned(Map<String, PersistentClass> persistentClasses, OneToOne oneToOne, Property property) {
|
||||
oneToOne.setMappedByProperty( mappedBy );
|
||||
final PersistentClass targetEntity = persistentClasses.get( oneToOne.getReferencedEntityName() );
|
||||
if ( targetEntity == null ) {
|
||||
throw new MappingException( "Association '" + getPath( propertyHolder, inferredData )
|
||||
+ "' targets unknown entity type '" + value.getReferencedEntityName() + "'" );
|
||||
+ "' targets unknown entity type '" + oneToOne.getReferencedEntityName() + "'" );
|
||||
}
|
||||
final Property targetProperty = targetProperty( value, targetEntity );
|
||||
final Property targetProperty = targetProperty( oneToOne, targetEntity );
|
||||
if ( targetProperty.getValue() instanceof OneToOne ) {
|
||||
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
|
||||
}
|
||||
else if ( targetProperty.getValue() instanceof ManyToOne ) {
|
||||
bindTargetManyToOne( persistentClasses, value, property, targetEntity, targetProperty );
|
||||
bindTargetManyToOne( persistentClasses, oneToOne, property, targetEntity, targetProperty );
|
||||
}
|
||||
else {
|
||||
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
|
||||
+ "' is 'mappedBy' a property named '" + mappedBy
|
||||
+ "' of the target entity type '" + value.getReferencedEntityName()
|
||||
+ "' of the target entity type '" + oneToOne.getReferencedEntityName()
|
||||
+ "' which is not a '@OneToOne' or '@ManyToOne' association" );
|
||||
}
|
||||
}
|
||||
|
||||
private void bindTargetManyToOne(
|
||||
Map<String, PersistentClass> persistentClasses,
|
||||
OneToOne value,
|
||||
OneToOne oneToOne,
|
||||
Property property,
|
||||
PersistentClass targetEntity,
|
||||
Property targetProperty) {
|
||||
|
@ -174,11 +174,11 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
final ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() );
|
||||
//FIXME use ignore not found here
|
||||
manyToOne.setNotFoundAction( notFoundAction );
|
||||
manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() );
|
||||
manyToOne.setFetchMode( value.getFetchMode() );
|
||||
manyToOne.setLazy( value.isLazy() );
|
||||
manyToOne.setReferencedEntityName( value.getReferencedEntityName() );
|
||||
manyToOne.setUnwrapProxy( value.isUnwrapProxy() );
|
||||
manyToOne.setCascadeDeleteEnabled( oneToOne.isCascadeDeleteEnabled() );
|
||||
manyToOne.setFetchMode( oneToOne.getFetchMode() );
|
||||
manyToOne.setLazy( oneToOne.isLazy() );
|
||||
manyToOne.setReferencedEntityName( oneToOne.getReferencedEntityName() );
|
||||
manyToOne.setUnwrapProxy( oneToOne.isUnwrapProxy() );
|
||||
manyToOne.markAsLogicalOneToOne();
|
||||
property.setValue( manyToOne );
|
||||
for ( Column column: otherSideJoin.getKey().getColumns() ) {
|
||||
|
@ -203,7 +203,7 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
|
||||
}
|
||||
|
||||
value.setReferencedPropertyName( mappedBy );
|
||||
oneToOne.setReferencedPropertyName( mappedBy );
|
||||
|
||||
// HHH-6813
|
||||
// Foo: @Id long id, @OneToOne(mappedBy="foo") Bar bar
|
||||
|
@ -212,16 +212,16 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
boolean referenceToPrimaryKey = mappedBy == null
|
||||
|| targetEntityIdentifier instanceof Component
|
||||
&& !( (Component) targetEntityIdentifier ).hasProperty( mappedBy );
|
||||
value.setReferenceToPrimaryKey( referenceToPrimaryKey );
|
||||
oneToOne.setReferenceToPrimaryKey( referenceToPrimaryKey );
|
||||
|
||||
final String propertyRef = value.getReferencedPropertyName();
|
||||
final String propertyRef = oneToOne.getReferencedPropertyName();
|
||||
if ( propertyRef != null ) {
|
||||
buildingContext.getMetadataCollector()
|
||||
.addUniquePropertyReference( value.getReferencedEntityName(), propertyRef );
|
||||
.addUniquePropertyReference( oneToOne.getReferencedEntityName(), propertyRef );
|
||||
}
|
||||
}
|
||||
|
||||
private Property targetProperty(OneToOne value, PersistentClass targetEntity) {
|
||||
private Property targetProperty(OneToOne oneToOne, PersistentClass targetEntity) {
|
||||
try {
|
||||
Property targetProperty = findPropertyByName( targetEntity, mappedBy );
|
||||
if ( targetProperty != null ) {
|
||||
|
@ -233,10 +233,10 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
}
|
||||
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
|
||||
+ "' is 'mappedBy' a property named '" + mappedBy
|
||||
+ "' which does not exist in the target entity type '" + value.getReferencedEntityName() + "'" );
|
||||
+ "' which does not exist in the target entity type '" + oneToOne.getReferencedEntityName() + "'" );
|
||||
}
|
||||
|
||||
private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOne value, String propertyName, Property property) {
|
||||
private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOne oneToOne, String propertyName, Property property) {
|
||||
// we need to check if the columns are in the right order
|
||||
// if not, then we need to create a many to one and formula
|
||||
// but actually, since entities linked by a one to one need
|
||||
|
@ -245,7 +245,7 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
|
||||
if ( rightOrder ) {
|
||||
final ToOneFkSecondPass secondPass = new ToOneFkSecondPass(
|
||||
value,
|
||||
oneToOne,
|
||||
joinColumns,
|
||||
!optional, //cannot have nullable and unique on certain DBs
|
||||
propertyHolder.getPersistentClass(),
|
||||
|
@ -253,7 +253,7 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
buildingContext
|
||||
);
|
||||
secondPass.doSecondPass(persistentClasses);
|
||||
//no column associated since its a one to one
|
||||
//no column associated since it's a one to one
|
||||
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
|
||||
}
|
||||
// else {
|
||||
|
|
|
@ -300,50 +300,66 @@ public class ToOneBinder {
|
|||
XProperty property,
|
||||
PropertyData inferredData,
|
||||
PropertyHolder propertyHolder) {
|
||||
final FetchType fetchType = getJpaFetchType( property );
|
||||
handleLazy( toOne, property, inferredData, propertyHolder );
|
||||
handleFetch( toOne, property );
|
||||
}
|
||||
|
||||
final LazyToOne lazy = property.getAnnotation( LazyToOne.class );
|
||||
final NotFound notFound = property.getAnnotation( NotFound.class );
|
||||
if ( notFound != null ) {
|
||||
private static void handleFetch(ToOne toOne, XProperty property) {
|
||||
if ( property.isAnnotationPresent( Fetch.class ) ) {
|
||||
// Hibernate @Fetch annotation takes precedence
|
||||
handleHibernateFetchMode( toOne, property );
|
||||
}
|
||||
else {
|
||||
toOne.setFetchMode( getFetchMode( getJpaFetchType( property ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleLazy(ToOne toOne, XProperty property, PropertyData inferredData, PropertyHolder propertyHolder) {
|
||||
if ( property.isAnnotationPresent( NotFound.class ) ) {
|
||||
toOne.setLazy( false );
|
||||
toOne.setUnwrapProxy( true );
|
||||
}
|
||||
else if ( lazy != null ) {
|
||||
boolean lazyFalse = lazy.value() == LazyToOneOption.FALSE;
|
||||
if ( fetchType == FetchType.LAZY && lazyFalse ) {
|
||||
throw new AnnotationException("Association '" + getPath( propertyHolder, inferredData )
|
||||
+ "' is marked 'fetch=LAZY' and '@LazyToOne(FALSE)'");
|
||||
}
|
||||
toOne.setLazy( !lazyFalse );
|
||||
toOne.setUnwrapProxy( lazy.value() == LazyToOneOption.NO_PROXY );
|
||||
}
|
||||
else {
|
||||
toOne.setLazy( fetchType == FetchType.LAZY );
|
||||
toOne.setUnwrapProxy( fetchType != FetchType.LAZY );
|
||||
boolean eager = isEager( property, inferredData, propertyHolder );
|
||||
toOne.setLazy( !eager );
|
||||
toOne.setUnwrapProxy( eager );
|
||||
toOne.setUnwrapProxyImplicit( true );
|
||||
}
|
||||
}
|
||||
|
||||
final Fetch fetch = property.getAnnotation( Fetch.class );
|
||||
if ( fetch != null ) {
|
||||
// Hibernate @Fetch annotation takes precedence
|
||||
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
|
||||
private static void handleHibernateFetchMode(ToOne toOne, XProperty property) {
|
||||
switch ( property.getAnnotation( Fetch.class ).value() ) {
|
||||
case JOIN:
|
||||
toOne.setFetchMode( FetchMode.JOIN );
|
||||
toOne.setLazy( false );
|
||||
toOne.setUnwrapProxy( false );
|
||||
}
|
||||
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
|
||||
break;
|
||||
case SELECT:
|
||||
toOne.setFetchMode( FetchMode.SELECT );
|
||||
}
|
||||
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
|
||||
break;
|
||||
case SUBSELECT:
|
||||
throw new AnnotationException( "Association '" + property.getName()
|
||||
+ "' is annotated '@Fetch(SUBSELECT)' but is not many-valued");
|
||||
default:
|
||||
throw new AssertionFailure("unknown fetch type");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEager(XProperty property, PropertyData inferredData, PropertyHolder propertyHolder) {
|
||||
final FetchType fetchType = getJpaFetchType( property );
|
||||
if ( property.isAnnotationPresent( LazyToOne.class ) ) {
|
||||
// LazyToOne takes precedent
|
||||
final LazyToOne lazy = property.getAnnotation( LazyToOne.class );
|
||||
boolean eager = lazy.value() == LazyToOneOption.FALSE;
|
||||
if ( eager && fetchType == FetchType.LAZY ) {
|
||||
// conflicts with non-default setting
|
||||
throw new AnnotationException("Association '" + getPath(propertyHolder, inferredData)
|
||||
+ "' is marked 'fetch=LAZY' and '@LazyToOne(FALSE)'");
|
||||
}
|
||||
return eager;
|
||||
}
|
||||
else {
|
||||
throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
|
||||
}
|
||||
}
|
||||
else {
|
||||
toOne.setFetchMode( getFetchMode( fetchType ) );
|
||||
return fetchType == FetchType.EAGER;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,7 +457,8 @@ public class ToOneBinder {
|
|||
//column.getTable() => persistentClass.getTable()
|
||||
final String propertyName = inferredData.getPropertyName();
|
||||
LOG.tracev( "Fetching {0} with {1}", propertyName, fetchMode );
|
||||
if ( isMapToPK( joinColumns, propertyHolder, trueOneToOne ) || !isEmptyAnnotationValue( mappedBy ) ) {
|
||||
if ( isMapToPK( joinColumns, propertyHolder, trueOneToOne )
|
||||
|| !isEmptyAnnotationValue( mappedBy ) ) {
|
||||
//is a true one-to-one
|
||||
//FIXME referencedColumnName ignored => ordering may fail.
|
||||
final OneToOneSecondPass secondPass = new OneToOneSecondPass(
|
||||
|
@ -491,7 +508,7 @@ public class ToOneBinder {
|
|||
}
|
||||
else {
|
||||
//try to find a hidden true one to one (FK == PK columns)
|
||||
KeyValue identifier = propertyHolder.getIdentifier();
|
||||
final KeyValue identifier = propertyHolder.getIdentifier();
|
||||
if ( identifier == null ) {
|
||||
//this is a @OneToOne in an @EmbeddedId (the persistentClass.identifier is not set yet, it's being built)
|
||||
//by definition the PK cannot refer to itself so it cannot map to itself
|
||||
|
@ -562,23 +579,24 @@ public class ToOneBinder {
|
|||
}
|
||||
|
||||
private static boolean noConstraint(ForeignKey joinColumns, MetadataBuildingContext context) {
|
||||
return joinColumns != null
|
||||
&& ( joinColumns.value() == ConstraintMode.NO_CONSTRAINT
|
||||
|| joinColumns.value() == ConstraintMode.PROVIDER_DEFAULT
|
||||
&& context.getBuildingOptions().isNoConstraintByDefault() );
|
||||
if ( joinColumns == null ) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
ConstraintMode mode = joinColumns.value();
|
||||
return mode == ConstraintMode.NO_CONSTRAINT
|
||||
|| mode == ConstraintMode.PROVIDER_DEFAULT && context.getBuildingOptions().isNoConstraintByDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getReferenceEntityName(PropertyData propertyData, XClass targetEntity, MetadataBuildingContext context) {
|
||||
if ( AnnotationBinder.isDefault( targetEntity, context ) ) {
|
||||
return propertyData.getClassOrElementName();
|
||||
}
|
||||
else {
|
||||
return targetEntity.getName();
|
||||
}
|
||||
return AnnotationBinder.isDefault( targetEntity, context )
|
||||
? propertyData.getClassOrElementName()
|
||||
: targetEntity.getName();
|
||||
}
|
||||
|
||||
public static String getReferenceEntityName(PropertyData propertyData, MetadataBuildingContext context) {
|
||||
XClass targetEntity = getTargetEntity( propertyData, context );
|
||||
final XClass targetEntity = getTargetEntity( propertyData, context );
|
||||
return AnnotationBinder.isDefault( targetEntity, context )
|
||||
? propertyData.getClassOrElementName()
|
||||
: targetEntity.getName();
|
||||
|
@ -590,13 +608,13 @@ public class ToOneBinder {
|
|||
}
|
||||
|
||||
private static Class<?> getTargetEntityClass(XProperty property) {
|
||||
final ManyToOne mTo = property.getAnnotation( ManyToOne.class );
|
||||
if (mTo != null) {
|
||||
return mTo.targetEntity();
|
||||
final ManyToOne manyToOne = property.getAnnotation( ManyToOne.class );
|
||||
if ( manyToOne != null ) {
|
||||
return manyToOne.targetEntity();
|
||||
}
|
||||
final OneToOne oTo = property.getAnnotation( OneToOne.class );
|
||||
if (oTo != null) {
|
||||
return oTo.targetEntity();
|
||||
final OneToOne oneToOne = property.getAnnotation( OneToOne.class );
|
||||
if ( oneToOne != null ) {
|
||||
return oneToOne.targetEntity();
|
||||
}
|
||||
throw new AssertionFailure("Unexpected discovery of a targetEntity: " + property.getName() );
|
||||
}
|
||||
|
|
|
@ -95,6 +95,8 @@ public class ToOneFkSecondPass extends FkSecondPass {
|
|||
|
||||
public void doSecondPass(java.util.Map<String, PersistentClass> persistentClasses) throws MappingException {
|
||||
if ( value instanceof ManyToOne ) {
|
||||
//TODO: move this validation logic to a separate ManyToOnSecondPass
|
||||
// for consistency with how this is handled for OneToOnes
|
||||
final ManyToOne manyToOne = (ManyToOne) value;
|
||||
final PersistentClass targetEntity = persistentClasses.get( manyToOne.getReferencedEntityName() );
|
||||
if ( targetEntity == null ) {
|
||||
|
|
|
@ -150,6 +150,8 @@ import org.hibernate.usertype.UserCollectionType;
|
|||
import org.jboss.logging.Logger;
|
||||
|
||||
import static jakarta.persistence.AccessType.PROPERTY;
|
||||
import static jakarta.persistence.FetchType.EAGER;
|
||||
import static jakarta.persistence.FetchType.LAZY;
|
||||
import static org.hibernate.cfg.AnnotatedColumn.buildColumnFromAnnotation;
|
||||
import static org.hibernate.cfg.AnnotatedColumn.buildColumnFromNoAnnotation;
|
||||
import static org.hibernate.cfg.AnnotatedColumn.buildColumnsFromAnnotations;
|
||||
|
@ -1432,44 +1434,53 @@ public abstract class CollectionBinder {
|
|||
}
|
||||
|
||||
private void defineFetchingStrategy() {
|
||||
final FetchType jpaFetchType = getJpaFetchType();
|
||||
handleLazy( jpaFetchType );
|
||||
handleFetch( jpaFetchType );
|
||||
handleLazy();
|
||||
handleFetch();
|
||||
}
|
||||
|
||||
private void handleFetch(FetchType jpaFetchType) {
|
||||
final Fetch fetch = property.getAnnotation( Fetch.class );
|
||||
if ( fetch != null ) {
|
||||
private void handleFetch() {
|
||||
if ( property.isAnnotationPresent( Fetch.class ) ) {
|
||||
// Hibernate @Fetch annotation takes precedence
|
||||
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
|
||||
handleHibernateFetchMode();
|
||||
}
|
||||
else {
|
||||
collection.setFetchMode( getFetchMode( getJpaFetchType() ) );
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHibernateFetchMode() {
|
||||
switch ( property.getAnnotation( Fetch.class ).value() ) {
|
||||
case JOIN:
|
||||
collection.setFetchMode( FetchMode.JOIN );
|
||||
collection.setLazy( false );
|
||||
}
|
||||
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
|
||||
break;
|
||||
case SELECT:
|
||||
collection.setFetchMode( FetchMode.SELECT );
|
||||
}
|
||||
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
|
||||
break;
|
||||
case SUBSELECT:
|
||||
collection.setFetchMode( FetchMode.SELECT );
|
||||
collection.setSubselectLoadable( true );
|
||||
collection.getOwner().setSubselectLoadableCollections( true );
|
||||
}
|
||||
else {
|
||||
throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
|
||||
}
|
||||
}
|
||||
else {
|
||||
collection.setFetchMode( getFetchMode(jpaFetchType) );
|
||||
break;
|
||||
default:
|
||||
throw new AssertionFailure( "unknown fetch type");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLazy(FetchType jpaFetchType) {
|
||||
private void handleLazy() {
|
||||
final FetchType jpaFetchType = getJpaFetchType();
|
||||
if ( property.isAnnotationPresent( LazyCollection.class ) ) {
|
||||
final LazyCollection lazy = property.getAnnotation( LazyCollection.class );
|
||||
if ( lazy != null ) {
|
||||
collection.setLazy( lazy.value() != LazyCollectionOption.FALSE );
|
||||
boolean eager = lazy.value() == LazyCollectionOption.FALSE;
|
||||
if ( !eager && jpaFetchType == EAGER ) {
|
||||
throw new AnnotationException("Collection '" + safeCollectionRole()
|
||||
+ "' is marked 'fetch=EAGER' and '@LazyCollection(" + lazy.value() + ")'");
|
||||
}
|
||||
collection.setLazy( !eager );
|
||||
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
|
||||
}
|
||||
else {
|
||||
collection.setLazy( jpaFetchType == FetchType.LAZY );
|
||||
collection.setLazy( jpaFetchType == LAZY );
|
||||
collection.setExtraLazy( false );
|
||||
}
|
||||
}
|
||||
|
@ -1489,7 +1500,7 @@ public abstract class CollectionBinder {
|
|||
return elementCollection.fetch();
|
||||
}
|
||||
else if ( manyToAny != null ) {
|
||||
return FetchType.LAZY;
|
||||
return LAZY;
|
||||
}
|
||||
else {
|
||||
throw new AssertionFailure(
|
||||
|
@ -1526,27 +1537,8 @@ public abstract class CollectionBinder {
|
|||
* return true if it's a Fk, false if it's an association table
|
||||
*/
|
||||
protected boolean bindStarToManySecondPass(Map<String, PersistentClass> persistentClasses) {
|
||||
final XClass elementType = getElementType();
|
||||
final PersistentClass persistentClass = persistentClasses.get( elementType.getName() );
|
||||
boolean reversePropertyInJoin = false;
|
||||
if ( persistentClass != null && hasMappedBy() ) {
|
||||
try {
|
||||
reversePropertyInJoin =
|
||||
0 != persistentClass.getJoinNumber( persistentClass.getRecursiveProperty( mappedBy ) );
|
||||
}
|
||||
catch (MappingException e) {
|
||||
throw new AnnotationException( "Collection '" + safeCollectionRole()
|
||||
+ "' is 'mappedBy' a property named '" + mappedBy
|
||||
+ "' which does not exist in the target entity '" + elementType.getName() + "'" );
|
||||
}
|
||||
}
|
||||
if ( persistentClass != null
|
||||
&& !reversePropertyInJoin
|
||||
&& oneToMany
|
||||
&& !isExplicitAssociationTable
|
||||
&& ( joinColumns.getJoinColumns().get(0).isImplicit() && hasMappedBy() //implicit @JoinColumn
|
||||
|| !foreignJoinColumns.getJoinColumns().get(0).isImplicit() ) //this is an explicit @JoinColumn
|
||||
) {
|
||||
final PersistentClass persistentClass = persistentClasses.get( getElementType().getName() );
|
||||
if ( noAssociationTable( persistentClass ) ) {
|
||||
//this is a foreign key
|
||||
bindOneToManySecondPass( persistentClasses );
|
||||
return true;
|
||||
|
@ -1558,6 +1550,39 @@ public abstract class CollectionBinder {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isReversePropertyInJoin(XClass elementType, PersistentClass persistentClass) {
|
||||
if ( persistentClass != null && hasMappedBy() ) {
|
||||
try {
|
||||
return persistentClass.getJoinNumber( persistentClass.getRecursiveProperty( mappedBy ) ) != 0;
|
||||
}
|
||||
catch (MappingException e) {
|
||||
throw new AnnotationException( "Collection '" + safeCollectionRole()
|
||||
+ "' is 'mappedBy' a property named '" + mappedBy
|
||||
+ "' which does not exist in the target entity '" + elementType.getName() + "'" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean noAssociationTable(PersistentClass persistentClass) {
|
||||
return persistentClass != null
|
||||
&& !isReversePropertyInJoin( getElementType(), persistentClass )
|
||||
&& oneToMany
|
||||
&& !isExplicitAssociationTable
|
||||
&& ( implicitJoinColumn() || explicitForeignJoinColumn() );
|
||||
}
|
||||
|
||||
private boolean implicitJoinColumn() {
|
||||
return joinColumns.getJoinColumns().get(0).isImplicit()
|
||||
&& hasMappedBy(); //implicit @JoinColumn
|
||||
}
|
||||
|
||||
private boolean explicitForeignJoinColumn() {
|
||||
return !foreignJoinColumns.getJoinColumns().get(0).isImplicit(); //this is an explicit @JoinColumn
|
||||
}
|
||||
|
||||
private boolean hasMappedBy() {
|
||||
return isNotEmpty( mappedBy );
|
||||
}
|
||||
|
@ -2357,7 +2382,7 @@ public abstract class CollectionBinder {
|
|||
inverseJoinColumns,
|
||||
inferredData,
|
||||
cascadeDeleteEnabled,
|
||||
anyAnn.fetch() == FetchType.LAZY,
|
||||
anyAnn.fetch() == LAZY,
|
||||
Nullability.NO_CONSTRAINT,
|
||||
propertyHolder,
|
||||
new EntityBinder(),
|
||||
|
|
|
@ -27,9 +27,9 @@ public abstract class ToOne extends SimpleValue implements Fetchable, SortableVa
|
|||
private String propertyName;
|
||||
private boolean lazy = true;
|
||||
private boolean sorted;
|
||||
protected boolean unwrapProxy;
|
||||
protected boolean isUnwrapProxyImplicit;
|
||||
protected boolean referenceToPrimaryKey = true;
|
||||
private boolean unwrapProxy;
|
||||
private boolean unwrapProxyImplicit;
|
||||
private boolean referenceToPrimaryKey = true;
|
||||
|
||||
protected ToOne(MetadataBuildingContext buildingContext, Table table) {
|
||||
super( buildingContext, table );
|
||||
|
@ -44,7 +44,7 @@ public abstract class ToOne extends SimpleValue implements Fetchable, SortableVa
|
|||
this.lazy = original.lazy;
|
||||
this.sorted = original.sorted;
|
||||
this.unwrapProxy = original.unwrapProxy;
|
||||
this.isUnwrapProxyImplicit = original.isUnwrapProxyImplicit;
|
||||
this.unwrapProxyImplicit = original.unwrapProxyImplicit;
|
||||
this.referenceToPrimaryKey = original.referenceToPrimaryKey;
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,7 @@ public abstract class ToOne extends SimpleValue implements Fetchable, SortableVa
|
|||
}
|
||||
|
||||
public boolean isUnwrapProxyImplicit() {
|
||||
return isUnwrapProxyImplicit;
|
||||
return unwrapProxyImplicit;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,7 +143,7 @@ public abstract class ToOne extends SimpleValue implements Fetchable, SortableVa
|
|||
* for reference later
|
||||
*/
|
||||
public void setUnwrapProxyImplicit(boolean unwrapProxyImplicit) {
|
||||
isUnwrapProxyImplicit = unwrapProxyImplicit;
|
||||
this.unwrapProxyImplicit = unwrapProxyImplicit;
|
||||
}
|
||||
|
||||
public boolean isReferenceToPrimaryKey() {
|
||||
|
|
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
* 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.mapping.lazytoone;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
|
||||
import org.hibernate.testing.bytecode.enhancement.EnhancementOptions;
|
||||
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
|
||||
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static jakarta.persistence.FetchType.LAZY;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.CoreMatchers.sameInstance;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Baseline test for uni-directional to-one, using an explicit @LazyToOne(NO_PROXY)
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@RunWith( BytecodeEnhancerRunner.class)
|
||||
@EnhancementOptions( lazyLoading = true )
|
||||
public class LanyProxylessManyToOneTests extends BaseNonConfigCoreFunctionalTestCase {
|
||||
private SQLStatementInterceptor sqlStatementInterceptor;
|
||||
|
||||
@Override
|
||||
protected void applyMetadataSources(MetadataSources sources) {
|
||||
super.applyMetadataSources( sources );
|
||||
sources.addAnnotatedClass( Customer.class );
|
||||
sources.addAnnotatedClass( Order.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
|
||||
super.configureStandardServiceRegistryBuilder( ssrb );
|
||||
sqlStatementInterceptor = new SQLStatementInterceptor( ssrb );
|
||||
}
|
||||
|
||||
@Test public void testLazyManyToOne() {
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final Order order = session.byId(Order.class).getReference(1);
|
||||
assertThat( Hibernate.isPropertyInitialized( order, "customer"), is(false) );
|
||||
assertThat( order.customer, nullValue() );
|
||||
Customer customer = order.getCustomer();
|
||||
assertThat( Hibernate.isPropertyInitialized( order, "customer"), is(true) );
|
||||
assertThat( order.customer, notNullValue() );
|
||||
assertThat( customer, notNullValue() );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
assertThat( customer.getId(), is(1) );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
assertThat( customer.getName(), is("Acme Brick") );
|
||||
assertThat( Hibernate.isInitialized(customer), is(true) );
|
||||
}
|
||||
);
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final Order order = session.byId(Order.class).getReference(1);
|
||||
assertThat( Hibernate.isPropertyInitialized( order, "customer"), is(false) );
|
||||
assertThat( order.customer, nullValue() );
|
||||
Customer customer = order.getCustomer();
|
||||
assertThat( Hibernate.isPropertyInitialized( order, "customer"), is(true) );
|
||||
assertThat( order.customer, notNullValue() );
|
||||
assertThat( customer, notNullValue() );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
Hibernate.initialize( customer );
|
||||
assertThat( Hibernate.isInitialized(customer), is(true) );
|
||||
assertThat( customer.id, is(1) );
|
||||
assertThat( customer.name, is("Acme Brick") );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOwnerIsProxy() {
|
||||
final EntityPersister orderDescriptor = sessionFactory().getMappingMetamodel().getEntityDescriptor( Order.class );
|
||||
final BytecodeEnhancementMetadata orderEnhancementMetadata = orderDescriptor.getBytecodeEnhancementMetadata();
|
||||
assertThat( orderEnhancementMetadata.isEnhancedForLazyLoading(), is( true ) );
|
||||
|
||||
final EntityPersister customerDescriptor = sessionFactory().getMappingMetamodel().getEntityDescriptor( Customer.class );
|
||||
final BytecodeEnhancementMetadata customerEnhancementMetadata = customerDescriptor.getBytecodeEnhancementMetadata();
|
||||
assertThat( customerEnhancementMetadata.isEnhancedForLazyLoading(), is( true ) );
|
||||
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final Order order = session.byId( Order.class ).getReference( 1 );
|
||||
|
||||
// we should have just the uninitialized proxy of the owner - and
|
||||
// therefore no SQL statements should have been executed
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 0 ) );
|
||||
|
||||
final BytecodeLazyAttributeInterceptor initialInterceptor = orderEnhancementMetadata.extractLazyInterceptor( order );
|
||||
assertThat( initialInterceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) );
|
||||
|
||||
// access the id - should do nothing with db
|
||||
order.getId();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 0 ) );
|
||||
assertThat( initialInterceptor, sameInstance( orderEnhancementMetadata.extractLazyInterceptor( order ) ) );
|
||||
|
||||
// this should trigger loading the entity's base state
|
||||
order.getAmount();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 1 ) );
|
||||
final BytecodeLazyAttributeInterceptor interceptor = orderEnhancementMetadata.extractLazyInterceptor( order );
|
||||
assertThat( initialInterceptor, not( sameInstance( interceptor ) ) );
|
||||
assertThat( interceptor, instanceOf( LazyAttributeLoadingInterceptor.class ) );
|
||||
final LazyAttributeLoadingInterceptor attrInterceptor = (LazyAttributeLoadingInterceptor) interceptor;
|
||||
assertThat( attrInterceptor.hasAnyUninitializedAttributes(), is( false ) );
|
||||
|
||||
// should not trigger a load and the `customer` reference should be an uninitialized enhanced proxy
|
||||
final Customer customer = order.getCustomer();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 1 ) );
|
||||
|
||||
final BytecodeLazyAttributeInterceptor initialCustomerInterceptor = customerEnhancementMetadata.extractLazyInterceptor( customer );
|
||||
assertThat( initialCustomerInterceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) );
|
||||
|
||||
// just as above, accessing id should trigger no loads
|
||||
customer.getId();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 1 ) );
|
||||
assertThat( initialCustomerInterceptor, sameInstance( customerEnhancementMetadata.extractLazyInterceptor( customer ) ) );
|
||||
|
||||
customer.getName();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 2 ) );
|
||||
assertThat( customerEnhancementMetadata.extractLazyInterceptor( customer ), instanceOf( LazyAttributeLoadingInterceptor.class ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-14659")
|
||||
public void testQueryJoinFetch() {
|
||||
Order order = fromTransaction( (session) -> {
|
||||
final Order result = session.createQuery(
|
||||
"select o from Order o join fetch o.customer",
|
||||
Order.class )
|
||||
.uniqueResult();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 1 ) );
|
||||
return result;
|
||||
} );
|
||||
|
||||
// The "join fetch" should have already initialized the property,
|
||||
// so that the getter can safely be called outside of a session.
|
||||
assertTrue( Hibernate.isPropertyInitialized( order, "customer" ) );
|
||||
// The "join fetch" should have already initialized the associated entity.
|
||||
Customer customer = order.getCustomer();
|
||||
assertTrue( Hibernate.isInitialized( customer ) );
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 1 ) );
|
||||
}
|
||||
|
||||
@Before
|
||||
public void createTestData() {
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final Customer customer = new Customer( 1, "Acme Brick" );
|
||||
session.persist( customer );
|
||||
final Order order = new Order( 1, customer, BigDecimal.ONE );
|
||||
session.persist( order );
|
||||
}
|
||||
);
|
||||
sqlStatementInterceptor.clear();
|
||||
}
|
||||
|
||||
@After
|
||||
public void dropTestData() {
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
session.createQuery( "delete Order" ).executeUpdate();
|
||||
session.createQuery( "delete Customer" ).executeUpdate();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity( name = "Customer" )
|
||||
@Table( name = "customer" )
|
||||
public static class Customer {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
public Customer() {
|
||||
}
|
||||
|
||||
public Customer(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "Order")
|
||||
@Table( name = "`order`")
|
||||
public static class Order {
|
||||
@Id
|
||||
private Integer id;
|
||||
@ManyToOne( fetch = LAZY )
|
||||
private Customer customer;
|
||||
private BigDecimal amount;
|
||||
|
||||
public Order() {
|
||||
}
|
||||
|
||||
public Order(Integer id, Customer customer, BigDecimal amount) {
|
||||
this.id = id;
|
||||
this.customer = customer;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Customer getCustomer() {
|
||||
return customer;
|
||||
}
|
||||
|
||||
public void setCustomer(Customer customer) {
|
||||
this.customer = customer;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@ import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInter
|
|||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
|
@ -37,6 +36,8 @@ import static jakarta.persistence.FetchType.LAZY;
|
|||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.CoreMatchers.sameInstance;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hibernate.annotations.LazyToOneOption.NO_PROXY;
|
||||
|
@ -65,6 +66,41 @@ public class ManyToOneExplicitOptionTests extends BaseNonConfigCoreFunctionalTes
|
|||
sqlStatementInterceptor = new SQLStatementInterceptor( ssrb );
|
||||
}
|
||||
|
||||
@Test public void testLazyManyToOne() {
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final Order order = session.byId(Order.class).getReference(1);
|
||||
assertThat( Hibernate.isPropertyInitialized( order, "customer"), is(false) );
|
||||
assertThat( order.customer, nullValue() );
|
||||
Customer customer = order.getCustomer();
|
||||
assertThat( Hibernate.isPropertyInitialized( order, "customer"), is(true) );
|
||||
assertThat( order.customer, notNullValue() );
|
||||
assertThat( customer, notNullValue() );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
assertThat( customer.getId(), is(1) );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
assertThat( customer.getName(), is("Acme Brick") );
|
||||
assertThat( Hibernate.isInitialized(customer), is(true) );
|
||||
}
|
||||
);
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final Order order = session.byId(Order.class).getReference(1);
|
||||
assertThat( Hibernate.isPropertyInitialized( order, "customer"), is(false) );
|
||||
assertThat( order.customer, nullValue() );
|
||||
Customer customer = order.getCustomer();
|
||||
assertThat( Hibernate.isPropertyInitialized( order, "customer"), is(true) );
|
||||
assertThat( order.customer, notNullValue() );
|
||||
assertThat( customer, notNullValue() );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
Hibernate.initialize( customer );
|
||||
assertThat( Hibernate.isInitialized(customer), is(true) );
|
||||
assertThat( customer.id, is(1) );
|
||||
assertThat( customer.name, is("Acme Brick") );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOwnerIsProxy() {
|
||||
final EntityPersister orderDescriptor = sessionFactory().getMappingMetamodel().getEntityDescriptor( Order.class );
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* 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.mapping.lazytoone.onetoone;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
|
||||
import org.hibernate.testing.bytecode.enhancement.EnhancementOptions;
|
||||
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
|
||||
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static jakarta.persistence.FetchType.LAZY;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.CoreMatchers.sameInstance;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Same as OneToOneExplicitOptionTests but using @Proxyless
|
||||
*/
|
||||
@RunWith( BytecodeEnhancerRunner.class)
|
||||
@EnhancementOptions( lazyLoading = true )
|
||||
public class LazyProxylessOneToOneTests extends BaseNonConfigCoreFunctionalTestCase {
|
||||
private SQLStatementInterceptor sqlStatementInterceptor;
|
||||
|
||||
@Override
|
||||
protected void applyMetadataSources(MetadataSources sources) {
|
||||
super.applyMetadataSources( sources );
|
||||
sources.addAnnotatedClass( Customer.class );
|
||||
sources.addAnnotatedClass( SupplementalInfo.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
|
||||
super.configureStandardServiceRegistryBuilder( ssrb );
|
||||
sqlStatementInterceptor = new SQLStatementInterceptor( ssrb );
|
||||
}
|
||||
|
||||
@Test public void testLazyOneToOne() {
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final SupplementalInfo supplementalInfo = session.byId(SupplementalInfo.class).getReference(1);
|
||||
assertThat( Hibernate.isPropertyInitialized( supplementalInfo, "customer"), is(false) );
|
||||
assertThat( supplementalInfo.customer, nullValue() );
|
||||
Customer customer = supplementalInfo.getCustomer();
|
||||
assertThat( Hibernate.isPropertyInitialized( supplementalInfo, "customer"), is(true) );
|
||||
assertThat( supplementalInfo.customer, notNullValue() );
|
||||
assertThat( customer, notNullValue() );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
assertThat( customer.getId(), is(1) );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
assertThat( customer.getName(), is("Acme Brick") );
|
||||
assertThat( Hibernate.isInitialized(customer), is(true) );
|
||||
}
|
||||
);
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final SupplementalInfo supplementalInfo = session.byId(SupplementalInfo.class).getReference(1);
|
||||
assertThat( Hibernate.isPropertyInitialized( supplementalInfo, "customer"), is(false) );
|
||||
assertThat( supplementalInfo.customer, nullValue() );
|
||||
Customer customer = supplementalInfo.getCustomer();
|
||||
assertThat( Hibernate.isPropertyInitialized( supplementalInfo, "customer"), is(true) );
|
||||
assertThat( supplementalInfo.customer, notNullValue() );
|
||||
assertThat( customer, notNullValue() );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
Hibernate.initialize( customer );
|
||||
assertThat( Hibernate.isInitialized(customer), is(true) );
|
||||
assertThat( customer.id, is(1) );
|
||||
assertThat( customer.name, is("Acme Brick") );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOwnerIsProxy() {
|
||||
final EntityPersister supplementalInfoDescriptor = sessionFactory().getMappingMetamodel().getEntityDescriptor( SupplementalInfo.class );
|
||||
final BytecodeEnhancementMetadata supplementalInfoEnhancementMetadata = supplementalInfoDescriptor.getBytecodeEnhancementMetadata();
|
||||
assertThat( supplementalInfoEnhancementMetadata.isEnhancedForLazyLoading(), is( true ) );
|
||||
|
||||
final EntityPersister customerDescriptor = sessionFactory().getMappingMetamodel().getEntityDescriptor( Customer.class );
|
||||
final BytecodeEnhancementMetadata customerEnhancementMetadata = customerDescriptor.getBytecodeEnhancementMetadata();
|
||||
assertThat( customerEnhancementMetadata.isEnhancedForLazyLoading(), is( true ) );
|
||||
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final SupplementalInfo supplementalInfo = session.byId( SupplementalInfo.class ).getReference( 1 );
|
||||
|
||||
// we should have just the uninitialized SupplementalInfo proxy
|
||||
// - therefore no SQL statements should have been executed
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 0 ) );
|
||||
|
||||
final BytecodeLazyAttributeInterceptor initialInterceptor = supplementalInfoEnhancementMetadata.extractLazyInterceptor( supplementalInfo );
|
||||
assertThat( initialInterceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) );
|
||||
|
||||
// access the id - should do nothing with db
|
||||
supplementalInfo.getId();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 0 ) );
|
||||
assertThat( supplementalInfoEnhancementMetadata.extractLazyInterceptor( supplementalInfo ), sameInstance( initialInterceptor ) );
|
||||
|
||||
// this should trigger loading the entity's base state
|
||||
supplementalInfo.getSomething();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 1 ) );
|
||||
final BytecodeLazyAttributeInterceptor interceptor = supplementalInfoEnhancementMetadata.extractLazyInterceptor( supplementalInfo );
|
||||
assertThat( initialInterceptor, not( sameInstance( interceptor ) ) );
|
||||
assertThat( interceptor, instanceOf( LazyAttributeLoadingInterceptor.class ) );
|
||||
final LazyAttributeLoadingInterceptor attrInterceptor = (LazyAttributeLoadingInterceptor) interceptor;
|
||||
assertThat( attrInterceptor.hasAnyUninitializedAttributes(), is( false ) );
|
||||
|
||||
// should not trigger a load and the `customer` reference should be an uninitialized enhanced proxy
|
||||
final Customer customer = supplementalInfo.getCustomer();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 1 ) );
|
||||
|
||||
final BytecodeLazyAttributeInterceptor initialCustomerInterceptor = customerEnhancementMetadata.extractLazyInterceptor( customer );
|
||||
assertThat( initialCustomerInterceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) );
|
||||
|
||||
// just as above, accessing id should trigger no loads
|
||||
customer.getId();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 1 ) );
|
||||
assertThat( initialCustomerInterceptor, sameInstance( customerEnhancementMetadata.extractLazyInterceptor( customer ) ) );
|
||||
|
||||
customer.getName();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 2 ) );
|
||||
assertThat( customerEnhancementMetadata.extractLazyInterceptor( customer ), instanceOf( LazyAttributeLoadingInterceptor.class ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-14659")
|
||||
public void testQueryJoinFetch() {
|
||||
SupplementalInfo info = fromTransaction( (session) -> {
|
||||
final SupplementalInfo result = session.createQuery(
|
||||
"select s from SupplementalInfo s join fetch s.customer",
|
||||
SupplementalInfo.class )
|
||||
.uniqueResult();
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 1 ) );
|
||||
return result;
|
||||
} );
|
||||
|
||||
// The "join fetch" should have already initialized the property,
|
||||
// so that the getter can safely be called outside a session.
|
||||
assertTrue( Hibernate.isPropertyInitialized( info, "customer" ) );
|
||||
// The "join fetch" should have already initialized the associated entity.
|
||||
Customer customer = info.getCustomer();
|
||||
assertTrue( Hibernate.isInitialized( customer ) );
|
||||
assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 1 ) );
|
||||
}
|
||||
|
||||
@Before
|
||||
public void createTestData() {
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final Customer customer = new Customer( 1, "Acme Brick" );
|
||||
session.persist( customer );
|
||||
final SupplementalInfo supplementalInfo = new SupplementalInfo( 1, customer, "extra details" );
|
||||
session.persist( supplementalInfo );
|
||||
}
|
||||
);
|
||||
sqlStatementInterceptor.clear();
|
||||
}
|
||||
|
||||
@After
|
||||
public void dropTestData() {
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
session.createQuery( "delete SupplementalInfo" ).executeUpdate();
|
||||
session.createQuery( "delete Customer" ).executeUpdate();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity( name = "Customer" )
|
||||
@Table( name = "customer" )
|
||||
public static class Customer {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
public Customer() {
|
||||
}
|
||||
|
||||
public Customer(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "SupplementalInfo" )
|
||||
@Table( name = "supplemental" )
|
||||
public static class SupplementalInfo {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
@OneToOne( fetch = LAZY, optional = false )
|
||||
private Customer customer;
|
||||
|
||||
private String something;
|
||||
|
||||
public SupplementalInfo() {
|
||||
}
|
||||
|
||||
public SupplementalInfo(Integer id, Customer customer, String something) {
|
||||
this.id = id;
|
||||
this.customer = customer;
|
||||
this.something = something;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Customer getCustomer() {
|
||||
return customer;
|
||||
}
|
||||
|
||||
public void setCustomer(Customer customer) {
|
||||
this.customer = customer;
|
||||
}
|
||||
|
||||
public String getSomething() {
|
||||
return something;
|
||||
}
|
||||
|
||||
public void setSomething(String something) {
|
||||
this.something = something;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInter
|
|||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
|
@ -36,6 +35,8 @@ import static jakarta.persistence.FetchType.LAZY;
|
|||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.CoreMatchers.sameInstance;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hibernate.annotations.LazyToOneOption.NO_PROXY;
|
||||
|
@ -62,6 +63,41 @@ public class OneToOneExplicitOptionTests extends BaseNonConfigCoreFunctionalTest
|
|||
sqlStatementInterceptor = new SQLStatementInterceptor( ssrb );
|
||||
}
|
||||
|
||||
@Test public void testLazyOneToOne() {
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final SupplementalInfo supplementalInfo = session.byId(SupplementalInfo.class).getReference(1);
|
||||
assertThat( Hibernate.isPropertyInitialized( supplementalInfo, "customer"), is(false) );
|
||||
assertThat( supplementalInfo.customer, nullValue() );
|
||||
Customer customer = supplementalInfo.getCustomer();
|
||||
assertThat( Hibernate.isPropertyInitialized( supplementalInfo, "customer"), is(true) );
|
||||
assertThat( supplementalInfo.customer, notNullValue() );
|
||||
assertThat( customer, notNullValue() );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
assertThat( customer.getId(), is(1) );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
assertThat( customer.getName(), is("Acme Brick") );
|
||||
assertThat( Hibernate.isInitialized(customer), is(true) );
|
||||
}
|
||||
);
|
||||
inTransaction(
|
||||
(session) -> {
|
||||
final SupplementalInfo supplementalInfo = session.byId(SupplementalInfo.class).getReference(1);
|
||||
assertThat( Hibernate.isPropertyInitialized( supplementalInfo, "customer"), is(false) );
|
||||
assertThat( supplementalInfo.customer, nullValue() );
|
||||
Customer customer = supplementalInfo.getCustomer();
|
||||
assertThat( Hibernate.isPropertyInitialized( supplementalInfo, "customer"), is(true) );
|
||||
assertThat( supplementalInfo.customer, notNullValue() );
|
||||
assertThat( customer, notNullValue() );
|
||||
assertThat( Hibernate.isInitialized(customer), is(false) );
|
||||
Hibernate.initialize( customer );
|
||||
assertThat( Hibernate.isInitialized(customer), is(true) );
|
||||
assertThat( customer.getId(), is(1) );
|
||||
assertThat( customer.getName(), is("Acme Brick") );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOwnerIsProxy() {
|
||||
final EntityPersister supplementalInfoDescriptor = sessionFactory().getMappingMetamodel().getEntityDescriptor( SupplementalInfo.class );
|
||||
|
@ -129,7 +165,7 @@ public class OneToOneExplicitOptionTests extends BaseNonConfigCoreFunctionalTest
|
|||
} );
|
||||
|
||||
// The "join fetch" should have already initialized the property,
|
||||
// so that the getter can safely be called outside of a session.
|
||||
// so that the getter can safely be called outside a session.
|
||||
assertTrue( Hibernate.isPropertyInitialized( info, "customer" ) );
|
||||
// The "join fetch" should have already initialized the associated entity.
|
||||
Customer customer = info.getCustomer();
|
||||
|
|
Loading…
Reference in New Issue