diff --git a/hibernate-core/src/main/java/org/hibernate/Hibernate.java b/hibernate-core/src/main/java/org/hibernate/Hibernate.java index 6644d420f4..c46f9d219f 100644 --- a/hibernate-core/src/main/java/org/hibernate/Hibernate.java +++ b/hibernate-core/src/main/java/org/hibernate/Hibernate.java @@ -8,6 +8,7 @@ package org.hibernate; import java.util.Iterator; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.HibernateIterator; @@ -63,6 +64,13 @@ public final class Hibernate { else if ( proxy instanceof PersistentCollection ) { ( (PersistentCollection) proxy ).forceInitialization(); } + else if ( proxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) proxy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( proxy, null ); + } + } } /** @@ -76,6 +84,13 @@ public final class Hibernate { if ( proxy instanceof HibernateProxy ) { return !( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUninitialized(); } + else if ( proxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) proxy ).$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return false; + } + return true; + } else if ( proxy instanceof PersistentCollection ) { return ( (PersistentCollection) proxy ).wasInitialized(); } @@ -187,7 +202,10 @@ public final class Hibernate { if ( entity instanceof PersistentAttributeInterceptable ) { PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); - if ( interceptor != null && interceptor instanceof LazyAttributeLoadingInterceptor ) { + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return false; + } + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { return ( (LazyAttributeLoadingInterceptor) interceptor ).isAttributeLoaded( propertyName ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index a8b393a626..de2b94b645 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -63,9 +63,8 @@ import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.tuple.entity.EntityTuplizerFactory; -import org.jboss.logging.Logger; - import static org.hibernate.cfg.AvailableSettings.ACQUIRE_CONNECTIONS; +import static org.hibernate.cfg.AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY; import static org.hibernate.cfg.AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS; import static org.hibernate.cfg.AvailableSettings.ALLOW_REFRESH_DETACHED_ENTITY; import static org.hibernate.cfg.AvailableSettings.ALLOW_UPDATE_OUTSIDE_TRANSACTION; @@ -193,6 +192,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private NullPrecedence defaultNullPrecedence; private boolean orderUpdatesEnabled; private boolean orderInsertsEnabled; + private boolean enhancementAsProxyEnabled; // multi-tenancy @@ -244,6 +244,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private boolean nativeExceptionHandling51Compliance; + @SuppressWarnings({"WeakerAccess", "deprecation"}) public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, BootstrapContext context) { this.serviceRegistry = serviceRegistry; @@ -340,6 +341,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { this.defaultNullPrecedence = NullPrecedence.parse( defaultNullPrecedence ); this.orderUpdatesEnabled = ConfigurationHelper.getBoolean( ORDER_UPDATES, configurationSettings ); this.orderInsertsEnabled = ConfigurationHelper.getBoolean( ORDER_INSERTS, configurationSettings ); + this.enhancementAsProxyEnabled = ConfigurationHelper.getBoolean( ALLOW_ENHANCEMENT_AS_PROXY, configurationSettings ); this.jtaTrackByThread = cfgService.getSetting( JTA_TRACK_BY_THREAD, BOOLEAN, true ); @@ -1031,6 +1033,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { return nativeExceptionHandling51Compliance; } + @Override + public boolean isEnhancementAsProxyEnabled() { + return enhancementAsProxyEnabled; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 44b8c5f24c..f1f9acd235 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -432,4 +432,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp public boolean nativeExceptionHandling51Compliance() { return delegate.nativeExceptionHandling51Compliance(); } + + @Override + public boolean isEnhancementAsProxyEnabled() { + return delegate.isEnhancementAsProxyEnabled(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 08c0d66bed..8e92af0def 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -290,4 +290,11 @@ public interface SessionFactoryOptions { default boolean nativeExceptionHandling51Compliance() { return false; } + + /** + * Can bytecode-enhanced entity classes be used as a "proxy"? + */ + default boolean isEnhancementAsProxyEnabled() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java b/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java new file mode 100644 index 0000000000..203d7d2929 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java @@ -0,0 +1,22 @@ +/* + * 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.bytecode; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public interface BytecodeLogger extends BasicLogger { + String NAME = "org.hibernate.orm.bytecode"; + + Logger LOGGER = Logger.getLogger( NAME ); + + boolean TRACE_ENABLED = LOGGER.isTraceEnabled(); + boolean DEBUG_ENABLED = LOGGER.isDebugEnabled(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index cec2f0f0d1..2c7827f02d 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -459,6 +459,11 @@ public class EnhancerImpl implements Enhancer { return getAnnotations().isAnnotationPresent( annotationType ); } + @Override + public String toString() { + return fieldDescription.toString(); + } + AnnotationDescription.Loadable getAnnotation(Class annotationType) { return getAnnotations().ofType( annotationType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index 18050ac72f..3b9eb4114b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -101,7 +101,10 @@ final class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDecla } TypeDefinition managedCtSuperclass = managedCtClass.getSuperClass(); - if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { + if ( enhancementContext.isEntityClass( managedCtSuperclass.asErasure() ) ) { + return Collections.emptyList(); + } + else if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { return collectInheritPersistentFields( managedCtSuperclass, enhancementContext ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java index 2f8ddce0b8..4e5da5c87d 100755 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java @@ -7,8 +7,10 @@ package org.hibernate.bytecode.enhance.spi; import java.io.Serializable; +import java.util.Collections; import java.util.Set; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -32,9 +34,18 @@ public interface LazyPropertyInitializer { } }; + /** + * @deprecated Prefer the form of these methods defined on + * {@link BytecodeLazyAttributeInterceptor} instead + */ + @Deprecated interface InterceptorImplementor { - Set getInitializedLazyAttributeNames(); - void attributeInitialized(String name); + default Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + + default void attributeInitialized(String name) { + } } /** diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java new file mode 100644 index 0000000000..a201fdc316 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java @@ -0,0 +1,160 @@ +/* + * 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.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractInterceptor implements SessionAssociableInterceptor { + private final String entityName; + + private transient SharedSessionContractImplementor session; + private boolean allowLoadOutsideTransaction; + private String sessionFactoryUuid; + + @SuppressWarnings("WeakerAccess") + public AbstractInterceptor(String entityName) { + this.entityName = entityName; + } + + public String getEntityName() { + return entityName; + } + + @Override + public SharedSessionContractImplementor getLinkedSession() { + return session; + } + + @Override + public void setSession(SharedSessionContractImplementor session) { + this.session = session; + if ( session != null && !allowLoadOutsideTransaction ) { + this.allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); + if ( this.allowLoadOutsideTransaction ) { + this.sessionFactoryUuid = session.getFactory().getUuid(); + } + } + } + + @Override + public void unsetSession() { + this.session = null; + } + + @Override + public boolean allowLoadOutsideTransaction() { + return allowLoadOutsideTransaction; + } + + @Override + public String getSessionFactoryUuid() { + return sessionFactoryUuid; + } + + /** + * Handle the case of reading an attribute. The result is what is returned to the caller + */ + protected abstract Object handleRead(Object target, String attributeName, Object value); + + /** + * Handle the case of writing an attribute. The result is what is set as the entity state + */ + protected abstract Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue); + + @Override + public boolean readBoolean(Object obj, String name, boolean oldValue) { + return (Boolean) handleRead( obj, name, oldValue ); + } + + @Override + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { + return (Boolean) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public byte readByte(Object obj, String name, byte oldValue) { + return (Byte) handleRead( obj, name, oldValue ); + } + + @Override + public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { + return (Byte) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public char readChar(Object obj, String name, char oldValue) { + return (Character) handleRead( obj, name, oldValue ); + } + + @Override + public char writeChar(Object obj, String name, char oldValue, char newValue) { + return (char) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public short readShort(Object obj, String name, short oldValue) { + return (Short) handleRead( obj, name, oldValue ); + } + + @Override + public short writeShort(Object obj, String name, short oldValue, short newValue) { + return (Short) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public int readInt(Object obj, String name, int oldValue) { + return (Integer) handleRead( obj, name, oldValue ); + } + + @Override + public int writeInt(Object obj, String name, int oldValue, int newValue) { + return (Integer) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public float readFloat(Object obj, String name, float oldValue) { + return (Float) handleRead( obj, name, oldValue ); + } + + @Override + public float writeFloat(Object obj, String name, float oldValue, float newValue) { + return (Float) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public double readDouble(Object obj, String name, double oldValue) { + return (Double) handleRead( obj, name, oldValue ); + } + + @Override + public double writeDouble(Object obj, String name, double oldValue, double newValue) { + return (Double) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public long readLong(Object obj, String name, long oldValue) { + return (Long) handleRead( obj, name, oldValue ); + } + + @Override + public long writeLong(Object obj, String name, long oldValue, long newValue) { + return (Long) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public Object readObject(Object obj, String name, Object oldValue) { + return handleRead( obj, name, oldValue ); + } + + @Override + public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { + return handleWrite( obj, name, oldValue, newValue ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java new file mode 100644 index 0000000000..3eb53f0261 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java @@ -0,0 +1,26 @@ +/* + * 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.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractLazyLoadInterceptor extends AbstractInterceptor implements BytecodeLazyAttributeInterceptor { + + @SuppressWarnings("unused") + public AbstractLazyLoadInterceptor(String entityName) { + super( entityName ); + } + + @SuppressWarnings("WeakerAccess") + public AbstractLazyLoadInterceptor(String entityName, SharedSessionContractImplementor session) { + super( entityName ); + setSession( session ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java new file mode 100644 index 0000000000..ae92635ea7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java @@ -0,0 +1,40 @@ +/* + * 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.bytecode.enhance.spi.interceptor; + +import java.util.Set; + +import org.hibernate.Incubating; + +/** + * @author Steve Ebersole + */ +@Incubating +public interface BytecodeLazyAttributeInterceptor extends SessionAssociableInterceptor { + /** + * The name of the entity this interceptor is meant to intercept + */ + String getEntityName(); + + /** + * The id of the entity instance this interceptor is associated with + */ + Object getIdentifier(); + + /** + * The names of all lazy attributes which have been initialized + */ + @Override + Set getInitializedLazyAttributeNames(); + + /** + * Callback from the enhanced class that an attribute has been read or written + */ + void attributeInitialized(String name); + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java new file mode 100644 index 0000000000..d9d79ad734 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java @@ -0,0 +1,267 @@ +/* + * 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.bytecode.enhance.spi.interceptor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.EntityMode; +import org.hibernate.LockMode; +import org.hibernate.bytecode.BytecodeLogger; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SelfDirtinessTracker; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.tuple.entity.EntityTuplizer; +import org.hibernate.type.CompositeType; + +/** + * @author Steve Ebersole + */ +public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInterceptor { + private final Set identifierAttributeNames; + private final CompositeType nonAggregatedCidMapper; + + private final EntityKey entityKey; + + private final boolean inLineDirtyChecking; + private Set writtenFieldNames; + + private boolean initialized; + + public EnhancementAsProxyLazinessInterceptor( + String entityName, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, + EntityKey entityKey, + SharedSessionContractImplementor session) { + super( entityName, session ); + + this.identifierAttributeNames = identifierAttributeNames; + assert identifierAttributeNames != null; + + this.nonAggregatedCidMapper = nonAggregatedCidMapper; + assert nonAggregatedCidMapper != null || identifierAttributeNames.size() == 1; + + this.entityKey = entityKey; + + final EntityPersister entityPersister = session.getFactory().getMetamodel().entityPersister( entityName ); + this.inLineDirtyChecking = entityPersister.getEntityMode() == EntityMode.POJO + && SelfDirtinessTracker.class.isAssignableFrom( entityPersister.getMappedClass() ); + } + + public EntityKey getEntityKey() { + return entityKey; + } + + @Override + protected Object handleRead(Object target, String attributeName, Object value) { + // it is illegal for this interceptor to still be attached to the entity after initialization + if ( initialized ) { + throw new IllegalStateException( "EnhancementAsProxyLazinessInterceptor interception on an initialized instance" ); + } + + // the attribute being read is an entity-id attribute + // - we already know the id, return that + if ( identifierAttributeNames.contains( attributeName ) ) { + return extractIdValue( target, attributeName ); + } + + // Use `performWork` to group together multiple Session accesses + return EnhancementHelper.performWork( + this, + (session, isTempSession) -> { + final Object[] writtenValues; + + final EntityPersister entityPersister = session.getFactory() + .getMetamodel() + .entityPersister( getEntityName() ); + final EntityTuplizer entityTuplizer = entityPersister.getEntityTuplizer(); + + if ( inLineDirtyChecking && writtenFieldNames != null && !writtenFieldNames.isEmpty() ) { + + // enhancement has dirty-tracking available and at least one attribute was explicitly set + + if ( writtenFieldNames.contains( attributeName ) ) { + // the requested attribute was one of the attributes explicitly set, we can just return the explicitly set value + return entityTuplizer.getPropertyValue( target, attributeName ); + } + + // otherwise we want to save all of the explicitly set values in anticipation of + // the force initialization below so that we can "replay" them after the + // initialization + + writtenValues = new Object[writtenFieldNames.size()]; + + int index = 0; + for ( String writtenFieldName : writtenFieldNames ) { + writtenValues[index] = entityTuplizer.getPropertyValue( target, writtenFieldName ); + index++; + } + } + else { + writtenValues = null; + } + + final Object initializedValue = forceInitialize( + target, + attributeName, + session, + isTempSession + ); + + initialized = true; + + if ( writtenValues != null ) { + // here is the replaying of the explicitly set values we prepared above + int index = 0; + for ( String writtenFieldName : writtenFieldNames ) { + entityTuplizer.setPropertyValue( target, writtenFieldName, writtenValues[index++] ); + } + writtenFieldNames.clear(); + } + + return initializedValue; + }, + getEntityName(), + attributeName + ); + } + + private Object extractIdValue(Object target, String attributeName) { + // access to the id or part of it for non-aggregated cid + if ( nonAggregatedCidMapper == null ) { + return getIdentifier(); + } + else { + return nonAggregatedCidMapper.getPropertyValue( + target, + nonAggregatedCidMapper.getPropertyIndex( attributeName ), + getLinkedSession() + ); + } + } + + public Object forceInitialize(Object target, String attributeName) { + BytecodeLogger.LOGGER.tracef( + "EnhancementAsProxyLazinessInterceptor#forceInitialize : %s#%s -> %s )", + entityKey.getEntityName(), + entityKey.getIdentifier(), + attributeName + ); + + return EnhancementHelper.performWork( + this, + (session, isTemporarySession) -> forceInitialize( target, attributeName, session, isTemporarySession ), + getEntityName(), + attributeName + ); + } + + public Object forceInitialize(Object target, String attributeName, SharedSessionContractImplementor session, boolean isTemporarySession) { + BytecodeLogger.LOGGER.tracef( + "EnhancementAsProxyLazinessInterceptor#forceInitialize : %s#%s -> %s )", + entityKey.getEntityName(), + entityKey.getIdentifier(), + attributeName + ); + + final EntityPersister persister = session.getFactory() + .getMetamodel() + .entityPersister( getEntityName() ); + + if ( isTemporarySession ) { + // Add an entry for this entity in the PC of the temp Session + session.getPersistenceContext().addEntity( + target, + Status.READ_ONLY, + // loaded state + ArrayHelper.filledArray( + LazyPropertyInitializer.UNFETCHED_PROPERTY, + Object.class, + persister.getPropertyTypes().length + ), + entityKey, + persister.getVersion( target ), + LockMode.NONE, + // we assume an entry exists in the db + true, + persister, + true + ); + } + + return persister.initializeEnhancedEntityUsedAsProxy( + target, + attributeName, + session + ); + } + + @Override + protected Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue) { + if ( initialized ) { + throw new IllegalStateException( "EnhancementAsProxyLazinessInterceptor interception on an initialized instance" ); + } + + if ( identifierAttributeNames.contains( attributeName ) ) { + EnhancementHelper.performWork( + this, + (session, isTempSession) -> session.getFactory() + .getMetamodel() + .entityPersister( getEntityName() ) + .getEntityTuplizer() + .getPropertyValue( target, attributeName ), + getEntityName(), + attributeName + ); + } + + if ( ! inLineDirtyChecking ) { + // we need to force-initialize the proxy - the fetch group to which the `attributeName` belongs + try { + forceInitialize( target, attributeName ); + } + finally { + initialized = true; + } + } + else { + // because of the entity being enhanced with `org.hibernate.engine.spi.SelfDirtinessTracker` + // we can skip forcing the initialization. However, in the case of a subsequent read we + // need to know which attributes had been explicitly set so that we can re-play the setters + // after the force-initialization there + if ( writtenFieldNames == null ) { + writtenFieldNames = new HashSet<>(); + } + writtenFieldNames.add( attributeName ); + } + + return newValue; + } + + @Override + public Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + + @Override + public void attributeInitialized(String name) { + if ( initialized ) { + throw new UnsupportedOperationException( "Expected call to EnhancementAsProxyLazinessInterceptor#attributeInitialized" ); + } + } + + @Override + public Object getIdentifier() { + return entityKey.getIdentifier(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java new file mode 100644 index 0000000000..a958e5e8f9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java @@ -0,0 +1,217 @@ +/* + * 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 . + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import java.util.Locale; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.hibernate.FlushMode; +import org.hibernate.LazyInitializationException; +import org.hibernate.bytecode.BytecodeLogger; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.SessionFactoryRegistry; +import org.hibernate.mapping.OneToOne; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.ToOne; +import org.hibernate.mapping.Value; + +/** + * @author Steve Ebersole + */ +public class EnhancementHelper { + /** + * Should the given property be included in the owner's base fetch group? + */ + public static boolean includeInBaseFetchGroup( + Property bootMapping, + boolean isEnhanced, + boolean allowEnhancementAsProxy, + Function hasSubclassChecker) { + final Value value = bootMapping.getValue(); + + if ( ! isEnhanced ) { + if ( value instanceof ToOne ) { + if ( ( (ToOne) value ).isUnwrapProxy() ) { + BytecodeLogger.LOGGER.debugf( + "To-one property `%s#%s` was mapped with LAZY + NO_PROXY but the class was not enhanced", + bootMapping.getPersistentClass().getEntityName(), + bootMapping.getName() + ); + } + } + return true; + } + + if ( value instanceof ToOne ) { + final ToOne toOne = (ToOne) value; + if ( toOne.isLazy() ) { + if ( toOne.isUnwrapProxy() ) { + if ( toOne instanceof OneToOne ) { + return false; + } + // include it in the base fetch group so long as the config allows + // using the FK to create an "enhancement proxy" +// return allowEnhancementAsProxy && hasSubclassChecker.apply( toOne.getReferencedEntityName() ); + return allowEnhancementAsProxy; + } + + } + + return true; + } + + return ! bootMapping.isLazy(); + } + + public static T performWork( + BytecodeLazyAttributeInterceptor interceptor, + BiFunction work, + String entityName, + String attributeName) { + SharedSessionContractImplementor session = interceptor.getLinkedSession(); + + boolean isTempSession = false; + boolean isJta = false; + + // first figure out which Session to use + if ( session == null ) { + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); + isTempSession = true; + } + else { + throwLazyInitializationException( Cause.NO_SESSION, entityName, attributeName ); + } + } + else if ( !session.isOpen() ) { + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); + isTempSession = true; + } + else { + throwLazyInitializationException( Cause.CLOSED_SESSION, entityName, attributeName ); + } + } + else if ( !session.isConnected() ) { + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); + isTempSession = true; + } + else { + throwLazyInitializationException( Cause.DISCONNECTED_SESSION, entityName, attributeName); + } + } + + // If we are using a temporary Session, begin a transaction if necessary + if ( isTempSession ) { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork started temporary Session" ); + + isJta = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); + + if ( !isJta ) { + // Explicitly handle the transactions only if we're not in + // a JTA environment. A lazy loading temporary session can + // be created even if a current session and transaction are + // open (ex: session.clear() was used). We must prevent + // multiple transactions. + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork starting transaction on temporary Session" ); + session.beginTransaction(); + } + } + + try { + // do the actual work + return work.apply( session, isTempSession ); + } + finally { + if ( isTempSession ) { + try { + // Commit the JDBC transaction is we started one. + if ( !isJta ) { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork committing transaction on temporary Session" ); + session.getTransaction().commit(); + } + } + catch (Exception e) { + BytecodeLogger.LOGGER.warn( + "Unable to commit JDBC transaction on temporary session used to load lazy " + + "collection associated to no session" + ); + } + + // Close the just opened temp Session + try { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork closing temporary Session" ); + session.close(); + } + catch (Exception e) { + BytecodeLogger.LOGGER.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); + } + } + } + } + + enum Cause { + NO_SESSION, + CLOSED_SESSION, + DISCONNECTED_SESSION, + NO_SF_UUID + } + + private static void throwLazyInitializationException(Cause cause, String entityName, String attributeName) { + final String reason; + switch ( cause ) { + case NO_SESSION: { + reason = "no session and settings disallow loading outside the Session"; + break; + } + case CLOSED_SESSION: { + reason = "session is closed and settings disallow loading outside the Session"; + break; + } + case DISCONNECTED_SESSION: { + reason = "session is disconnected and settings disallow loading outside the Session"; + break; + } + case NO_SF_UUID: { + reason = "could not determine SessionFactory UUId to create temporary Session for loading"; + break; + } + default: { + reason = ""; + } + } + + final String message = String.format( + Locale.ROOT, + "Unable to perform requested lazy initialization [%s.%s] - %s", + entityName, + attributeName, + reason + ); + + throw new LazyInitializationException( message ); + } + + private static SharedSessionContractImplementor openTemporarySessionForLoading( + BytecodeLazyAttributeInterceptor interceptor, + String entityName, + String attributeName) { + if ( interceptor.getSessionFactoryUuid() == null ) { + throwLazyInitializationException( Cause.NO_SF_UUID, entityName, attributeName ); + } + + final SessionFactoryImplementor sf = (SessionFactoryImplementor) + SessionFactoryRegistry.INSTANCE.getSessionFactory( interceptor.getSessionFactoryUuid() ); + final SharedSessionContractImplementor session = (SharedSessionContractImplementor) sf.openSession(); + session.getPersistenceContext().setDefaultReadOnly( true ); + session.setHibernateFlushMode( FlushMode.MANUAL ); + return session; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java deleted file mode 100644 index 9459288a17..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java +++ /dev/null @@ -1,179 +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 . - */ -package org.hibernate.bytecode.enhance.spi.interceptor; - -import java.util.Locale; - -import org.hibernate.FlushMode; -import org.hibernate.LazyInitializationException; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.SessionFactoryRegistry; - -import org.jboss.logging.Logger; - -/** - * @author Steve Ebersole - */ -public class Helper { - private static final Logger log = Logger.getLogger( Helper.class ); - - interface Consumer { - SharedSessionContractImplementor getLinkedSession(); - boolean allowLoadOutsideTransaction(); - String getSessionFactoryUuid(); - } - - interface LazyInitializationWork { - T doWork(SharedSessionContractImplementor session, boolean isTemporarySession); - - // informational details - String getEntityName(); - String getAttributeName(); - } - - - private final Consumer consumer; - - public Helper(Consumer consumer) { - this.consumer = consumer; - } - - public T performWork(LazyInitializationWork lazyInitializationWork) { - SharedSessionContractImplementor session = consumer.getLinkedSession(); - - boolean isTempSession = false; - boolean isJta = false; - - // first figure out which Session to use - if ( session == null ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); - isTempSession = true; - } - else { - throwLazyInitializationException( Cause.NO_SESSION, lazyInitializationWork ); - } - } - else if ( !session.isOpen() ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); - isTempSession = true; - } - else { - throwLazyInitializationException( Cause.CLOSED_SESSION, lazyInitializationWork ); - } - } - else if ( !session.isConnected() ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); - isTempSession = true; - } - else { - throwLazyInitializationException( Cause.DISCONNECTED_SESSION, lazyInitializationWork ); - } - } - - // If we are using a temporary Session, begin a transaction if necessary - if ( isTempSession ) { - isJta = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); - - if ( !isJta ) { - // Explicitly handle the transactions only if we're not in - // a JTA environment. A lazy loading temporary session can - // be created even if a current session and transaction are - // open (ex: session.clear() was used). We must prevent - // multiple transactions. - session.beginTransaction(); - } - } - - try { - // do the actual work - return lazyInitializationWork.doWork( session, isTempSession ); - } - finally { - if ( isTempSession ) { - try { - // Commit the JDBC transaction is we started one. - if ( !isJta ) { - session.getTransaction().commit(); - } - } - catch (Exception e) { - log.warn( - "Unable to commit JDBC transaction on temporary session used to load lazy " + - "collection associated to no session" - ); - } - - // Close the just opened temp Session - try { - session.close(); - } - catch (Exception e) { - log.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); - } - } - } - } - - enum Cause { - NO_SESSION, - CLOSED_SESSION, - DISCONNECTED_SESSION, - NO_SF_UUID - } - - private void throwLazyInitializationException(Cause cause, LazyInitializationWork work) { - final String reason; - switch ( cause ) { - case NO_SESSION: { - reason = "no session and settings disallow loading outside the Session"; - break; - } - case CLOSED_SESSION: { - reason = "session is closed and settings disallow loading outside the Session"; - break; - } - case DISCONNECTED_SESSION: { - reason = "session is disconnected and settings disallow loading outside the Session"; - break; - } - case NO_SF_UUID: { - reason = "could not determine SessionFactory UUId to create temporary Session for loading"; - break; - } - default: { - reason = ""; - } - } - - final String message = String.format( - Locale.ROOT, - "Unable to perform requested lazy initialization [%s.%s] - %s", - work.getEntityName(), - work.getAttributeName(), - reason - ); - - throw new LazyInitializationException( message ); - } - - private SharedSessionContractImplementor openTemporarySessionForLoading(LazyInitializationWork lazyInitializationWork) { - if ( consumer.getSessionFactoryUuid() == null ) { - throwLazyInitializationException( Cause.NO_SF_UUID, lazyInitializationWork ); - } - - final SessionFactoryImplementor sf = (SessionFactoryImplementor) - SessionFactoryRegistry.INSTANCE.getSessionFactory( consumer.getSessionFactoryUuid() ); - final SharedSessionContractImplementor session = (SharedSessionContractImplementor) sf.openSession(); - session.getPersistenceContext().setDefaultReadOnly( true ); - session.setHibernateFlushMode( FlushMode.MANUAL ); - return session; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java index 58672cc9bf..86f9b4b1f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java @@ -16,47 +16,39 @@ import java.util.Set; import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.CollectionTracker; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.InterceptorImplementor; -import org.hibernate.bytecode.enhance.spi.interceptor.Helper.Consumer; -import org.hibernate.bytecode.enhance.spi.interceptor.Helper.LazyInitializationWork; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.persister.entity.EntityPersister; -import org.jboss.logging.Logger; - /** * Interceptor that loads attributes lazily * * @author Luis Barreiro * @author Steve Ebersole */ -public class LazyAttributeLoadingInterceptor - implements PersistentAttributeInterceptor, Consumer, InterceptorImplementor { - private static final Logger log = Logger.getLogger( LazyAttributeLoadingInterceptor.class ); - - private final String entityName; +public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor { + private final Object identifier; private final Set lazyFields; - private Set initializedLazyFields; - private transient SharedSessionContractImplementor session; - private boolean allowLoadOutsideTransaction; - private String sessionFactoryUuid; - public LazyAttributeLoadingInterceptor( String entityName, + Object identifier, Set lazyFields, SharedSessionContractImplementor session) { - this.entityName = entityName; + super( entityName, session ); + this.identifier = identifier; this.lazyFields = lazyFields; - - setSession( session ); } - protected final Object intercept(Object target, String attributeName, Object value) { + @Override + public Object getIdentifier() { + return identifier; + } + + @Override + protected Object handleRead(Object target, String attributeName, Object value) { if ( !isAttributeLoaded( attributeName ) ) { Object loadedValue = fetchAttribute( target, attributeName ); attributeInitialized( attributeName ); @@ -65,6 +57,14 @@ public class LazyAttributeLoadingInterceptor return value; } + @Override + protected Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue) { + if ( !isAttributeLoaded( attributeName ) ) { + attributeInitialized( attributeName ); + } + return newValue; + } + /** * Fetches the lazy attribute. The attribute does not get associated with the entity. (To be used by hibernate methods) */ @@ -73,72 +73,48 @@ public class LazyAttributeLoadingInterceptor } protected Object loadAttribute(final Object target, final String attributeName) { - return new Helper( this ).performWork( - new LazyInitializationWork() { - @Override - public Object doWork(SharedSessionContractImplementor session, boolean isTemporarySession) { - final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( getEntityName() ); + return EnhancementHelper.performWork( + this, + (session, isTemporarySession) -> { + final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( getEntityName() ); - if ( isTemporarySession ) { - final Serializable id = persister.getIdentifier( target, null ); + if ( isTemporarySession ) { + final Serializable id = persister.getIdentifier( target, null ); - // Add an entry for this entity in the PC of the temp Session - // NOTE : a few arguments that would be nice to pass along here... - // 1) loadedState if we know any - final Object[] loadedState = null; - // 2) does a row exist in the db for this entity? - final boolean existsInDb = true; - session.getPersistenceContext().addEntity( - target, - Status.READ_ONLY, - loadedState, - session.generateEntityKey( id, persister ), - persister.getVersion( target ), - LockMode.NONE, - existsInDb, - persister, - true - ); - } - - final LazyPropertyInitializer initializer = (LazyPropertyInitializer) persister; - final Object loadedValue = initializer.initializeLazyProperty( - attributeName, + // Add an entry for this entity in the PC of the temp Session + // NOTE : a few arguments that would be nice to pass along here... + // 1) loadedState if we know any + final Object[] loadedState = null; + // 2) does a row exist in the db for this entity? + final boolean existsInDb = true; + session.getPersistenceContext().addEntity( target, - session + Status.READ_ONLY, + loadedState, + session.generateEntityKey( id, persister ), + persister.getVersion( target ), + LockMode.NONE, + existsInDb, + persister, + true ); - - takeCollectionSizeSnapshot( target, attributeName, loadedValue ); - return loadedValue; } - @Override - public String getEntityName() { - return entityName; - } + final LazyPropertyInitializer initializer = (LazyPropertyInitializer) persister; + final Object loadedValue = initializer.initializeLazyProperty( + attributeName, + target, + session + ); - @Override - public String getAttributeName() { - return attributeName; - } - } + takeCollectionSizeSnapshot( target, attributeName, loadedValue ); + return loadedValue; + }, + getEntityName(), + attributeName ); } - public final void setSession(SharedSessionContractImplementor session) { - this.session = session; - if ( session != null && !allowLoadOutsideTransaction ) { - this.allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); - if ( this.allowLoadOutsideTransaction ) { - this.sessionFactoryUuid = session.getFactory().getUuid(); - } - } - } - - public final void unsetSession() { - this.session = null; - } - public boolean isAttributeLoaded(String fieldName) { return !isLazyAttribute( fieldName ) || isInitializedLazyField( fieldName ); } @@ -171,13 +147,11 @@ public class LazyAttributeLoadingInterceptor @Override public String toString() { - return "LazyAttributeLoader(entityName=" + entityName + " ,lazyFields=" + lazyFields + ')'; + return "LazyAttributeLoader(entityName=" + getEntityName() + " ,lazyFields=" + lazyFields + ')'; } - // - private void takeCollectionSizeSnapshot(Object target, String fieldName, Object value) { - if ( value != null && value instanceof Collection && target instanceof SelfDirtinessTracker ) { + if ( value instanceof Collection && target instanceof SelfDirtinessTracker ) { CollectionTracker tracker = ( (SelfDirtinessTracker) target ).$$_hibernate_getCollectionTracker(); if ( tracker == null ) { ( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes(); @@ -187,152 +161,20 @@ public class LazyAttributeLoadingInterceptor } } - @Override - public boolean readBoolean(Object obj, String name, boolean oldValue) { - return (Boolean) intercept( obj, name, oldValue ); - } - - @Override - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public byte readByte(Object obj, String name, byte oldValue) { - return (Byte) intercept( obj, name, oldValue ); - } - - @Override - public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public char readChar(Object obj, String name, char oldValue) { - return (Character) intercept( obj, name, oldValue ); - } - - @Override - public char writeChar(Object obj, String name, char oldValue, char newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public short readShort(Object obj, String name, short oldValue) { - return (Short) intercept( obj, name, oldValue ); - } - - @Override - public short writeShort(Object obj, String name, short oldValue, short newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public int readInt(Object obj, String name, int oldValue) { - return (Integer) intercept( obj, name, oldValue ); - } - - @Override - public int writeInt(Object obj, String name, int oldValue, int newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public float readFloat(Object obj, String name, float oldValue) { - return (Float) intercept( obj, name, oldValue ); - } - - @Override - public float writeFloat(Object obj, String name, float oldValue, float newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public double readDouble(Object obj, String name, double oldValue) { - return (Double) intercept( obj, name, oldValue ); - } - - @Override - public double writeDouble(Object obj, String name, double oldValue, double newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public long readLong(Object obj, String name, long oldValue) { - return (Long) intercept( obj, name, oldValue ); - } - - @Override - public long writeLong(Object obj, String name, long oldValue, long newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public Object readObject(Object obj, String name, Object oldValue) { - return intercept( obj, name, oldValue ); - } - - @Override - public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public SharedSessionContractImplementor getLinkedSession() { - return session; - } - - @Override - public boolean allowLoadOutsideTransaction() { - return allowLoadOutsideTransaction; - } - - @Override - public String getSessionFactoryUuid() { - return sessionFactoryUuid; - } - @Override public void attributeInitialized(String name) { if ( !isLazyAttribute( name ) ) { return; } if ( initializedLazyFields == null ) { - initializedLazyFields = new HashSet(); + initializedLazyFields = new HashSet<>(); } initializedLazyFields.add( name ); } @Override public Set getInitializedLazyAttributeNames() { - return initializedLazyFields == null ? Collections.emptySet() : initializedLazyFields; + return initializedLazyFields == null ? Collections.emptySet() : initializedLazyFields; } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java index 312bea07fd..380b17711c 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java @@ -16,6 +16,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; @@ -29,12 +30,12 @@ public class LazyAttributesMetadata implements Serializable { /** * Build a LazyFetchGroupMetadata based on the attributes defined for the * PersistentClass - * - * @param mappedEntity The entity definition - * - * @return The built LazyFetchGroupMetadata */ - public static LazyAttributesMetadata from(PersistentClass mappedEntity) { + public static LazyAttributesMetadata from( + PersistentClass mappedEntity, + boolean isEnhanced, + boolean allowEnhancementAsProxy, + Function hasSubclassChecker) { final Map lazyAttributeDescriptorMap = new LinkedHashMap<>(); final Map> fetchGroupToAttributesMap = new HashMap<>(); @@ -44,7 +45,13 @@ public class LazyAttributesMetadata implements Serializable { while ( itr.hasNext() ) { i++; final Property property = (Property) itr.next(); - if ( property.isLazy() ) { + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + property, + isEnhanced, + allowEnhancementAsProxy, + hasSubclassChecker + ); + if ( lazy ) { final LazyAttributeDescriptor lazyAttributeDescriptor = LazyAttributeDescriptor.from( property, i, x++ ); lazyAttributeDescriptorMap.put( lazyAttributeDescriptor.getName(), lazyAttributeDescriptor ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java new file mode 100644 index 0000000000..7f60ffddbc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java @@ -0,0 +1,25 @@ +/* + * 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.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public interface SessionAssociableInterceptor extends PersistentAttributeInterceptor { + SharedSessionContractImplementor getLinkedSession(); + + void setSession(SharedSessionContractImplementor session); + + void unsetSession(); + + boolean allowLoadOutsideTransaction(); + + String getSessionFactoryUuid(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java index b71ba5aad2..3e932fce02 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java @@ -6,8 +6,11 @@ */ package org.hibernate.bytecode.spi; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -37,6 +40,7 @@ public interface BytecodeEnhancementMetadata { * Build and inject an interceptor instance into the enhanced entity. * * @param entity The entity into which built interceptor should be injected + * @param identifier * @param session The session to which the entity instance belongs. * * @return The built and injected interceptor @@ -45,8 +49,19 @@ public interface BytecodeEnhancementMetadata { */ LazyAttributeLoadingInterceptor injectInterceptor( Object entity, + Object identifier, SharedSessionContractImplementor session) throws NotInstrumentedException; + void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session); + + void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session); + /** * Extract the field interceptor instance from the enhanced entity. * @@ -58,6 +73,8 @@ public interface BytecodeEnhancementMetadata { */ LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException; + BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException; + boolean hasUnFetchedAttributes(Object entity); boolean isAttributeLoaded(Object entity, String attributeName); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index e8634f160f..3ee971e385 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -859,6 +859,27 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { */ String ENFORCE_LEGACY_PROXY_CLASSNAMES = "hibernate.bytecode.enforce_legacy_proxy_classnames"; + /** + * Should Hibernate use enhanced entities "as a proxy"? + * + * E.g., when an application uses {@link org.hibernate.Session#load} against an enhanced + * class, enabling this will allow Hibernate to create an "empty" instance of the enhanced + * class to act as the proxy - it contains just the identifier which is later used to + * trigger the base initialization but no other data is loaded + * + * Not enabling this (the legacy default behavior) would cause the "base" attributes to + * be loaded. Any lazy-group attributes would not be initialized. + * + * Applications using bytecode enhancement and switching to allowing this should be careful + * in use of the various {@link org.hibernate.Hibernate} methods such as + * {@link org.hibernate.Hibernate#isInitialized}, + * {@link org.hibernate.Hibernate#isPropertyInitialized}, etc - enabling this setting changes + * the results of those methods + * + * @implSpec See {@link org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor} + */ + String ALLOW_ENHANCEMENT_AS_PROXY = "hibernate.bytecode.allow_enhancement_as_proxy"; + /** * The classname of the HQL query parser factory */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java index bb87bced99..714f2f932b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java @@ -16,12 +16,15 @@ import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.Session; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CachedNaturalIdValueSource; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntryExtraState; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -345,6 +348,15 @@ public abstract class AbstractEntityEntry implements Serializable, EntityEntry { return ! persister.hasCollections() && ! ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes(); } + if ( entity instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + // we never have to check an uninitialized proxy + return true; + } + } + final CustomEntityDirtinessStrategy customEntityDirtinessStrategy = getPersistenceContext().getSession().getFactory().getCustomEntityDirtinessStrategy(); if ( customEntityDirtinessStrategy.canDirtyCheck( entity, getPersister(), (Session) getPersistenceContext().getSession() ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index 6afa9ce858..7d9d97ceca 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -94,7 +94,7 @@ public final class Cascade { final String propertyName = propertyNames[ i ]; final boolean isUninitializedProperty = hasUninitializedLazyProperties && - !persister.getInstrumentationMetadata().isAttributeLoaded( parent, propertyName ); + !persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName ); if ( style.doCascade( action ) ) { final Object child; @@ -133,7 +133,7 @@ public final class Cascade { else if ( action.performOnLazyProperty() && types[ i ].isEntityType() ) { // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() // returns true. - LazyAttributeLoadingInterceptor interceptor = persister.getInstrumentationMetadata() + LazyAttributeLoadingInterceptor interceptor = persister.getBytecodeEnhancementMetadata() .extractInterceptor( parent ); child = interceptor.fetchAttribute( parent, propertyName ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java index 1153f9d1f3..f4b4a61a96 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java @@ -164,9 +164,10 @@ public final class Collections { //TODO: better to pass the id in as an argument? ce.setCurrentKey( type.getKeyOfOwner( entity, session ) ); - final boolean isBytecodeEnhanced = persister.getOwnerEntityPersister().getInstrumentationMetadata().isEnhancedForLazyLoading(); + final boolean isBytecodeEnhanced = persister.getOwnerEntityPersister().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); if ( isBytecodeEnhanced && !collection.wasInitialized() ) { - // skip it + // the class of the collection owner is enhanced for lazy loading and we found an un-initialized PersistentCollection + // - skip it if ( LOG.isDebugEnabled() ) { LOG.debugf( "Skipping uninitialized bytecode-lazy collection: %s", @@ -175,57 +176,58 @@ public final class Collections { } ce.setReached( true ); ce.setProcessed( true ); + return; } - else { - // The CollectionEntry.isReached() stuff is just to detect any silly users - // who set up circular or shared references between/to collections. - if ( ce.isReached() ) { - // We've been here before - throw new HibernateException( - "Found shared references to a collection: " + type.getRole() + + // The CollectionEntry.isReached() stuff is just to detect any silly users + // who set up circular or shared references between/to collections. + if ( ce.isReached() ) { + // We've been here before + throw new HibernateException( + "Found shared references to a collection: " + type.getRole() + ); + } + + ce.setReached( true ); + + if ( LOG.isDebugEnabled() ) { + if ( collection.wasInitialized() ) { + LOG.debugf( + "Collection found: %s, was: %s (initialized)", + MessageHelper.collectionInfoString( + persister, + collection, + ce.getCurrentKey(), + session + ), + MessageHelper.collectionInfoString( + ce.getLoadedPersister(), + collection, + ce.getLoadedKey(), + session + ) ); } - ce.setReached( true ); - - if ( LOG.isDebugEnabled() ) { - if ( collection.wasInitialized() ) { - LOG.debugf( - "Collection found: %s, was: %s (initialized)", - MessageHelper.collectionInfoString( - persister, - collection, - ce.getCurrentKey(), - session - ), - MessageHelper.collectionInfoString( - ce.getLoadedPersister(), - collection, - ce.getLoadedKey(), - session - ) - ); - } - else { - LOG.debugf( - "Collection found: %s, was: %s (uninitialized)", - MessageHelper.collectionInfoString( - persister, - collection, - ce.getCurrentKey(), - session - ), - MessageHelper.collectionInfoString( - ce.getLoadedPersister(), - collection, - ce.getLoadedKey(), - session - ) - ); - } + else { + LOG.debugf( + "Collection found: %s, was: %s (uninitialized)", + MessageHelper.collectionInfoString( + persister, + collection, + ce.getCurrentKey(), + session + ), + MessageHelper.collectionInfoString( + ce.getLoadedPersister(), + collection, + ce.getLoadedKey(), + session + ) + ); } - - prepareCollectionForUpdate( collection, ce, factory ); } + + prepareCollectionForUpdate( collection, ce, factory ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java index 78f6b97cc3..879533baff 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java @@ -177,10 +177,7 @@ public final class ForeignKeys { // superclass or the same as the entity type of a nullifiable entity). // It is unclear if a more complicated check would impact performance // more than just initializing the associated entity. - return persister - .getInstrumentationMetadata() - .extractInterceptor( self ) - .fetchAttribute( self, propertyName ); + return ( (LazyPropertyInitializer) persister ).initializeLazyProperty( propertyName, self, session ); } else { return value; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index 28bf7af324..629f5e3a9e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -31,6 +31,8 @@ import org.hibernate.NonUniqueObjectException; import org.hibernate.PersistentObjectException; import org.hibernate.TransientObjectException; import org.hibernate.action.spi.AfterTransactionCompletionProcess; +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.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.access.SoftLock; @@ -495,6 +497,8 @@ public class StatefulPersistenceContext implements PersistenceContext { final boolean existsInDatabase, final EntityPersister persister, final boolean disableVersionIncrement) { + assert lockMode != null; + final EntityEntry e; /* @@ -571,15 +575,29 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public boolean reassociateIfUninitializedProxy(Object value) throws MappingException { - if ( !Hibernate.isInitialized( value ) ) { - final HibernateProxy proxy = (HibernateProxy) value; - final LazyInitializer li = proxy.getHibernateLazyInitializer(); - reassociateProxy( li, proxy ); - return true; - } - else { - return false; + if ( ! Hibernate.isInitialized( value ) ) { + + // could be a proxy.... + if ( value instanceof HibernateProxy ) { + final HibernateProxy proxy = (HibernateProxy) value; + final LazyInitializer li = proxy.getHibernateLazyInitializer(); + reassociateProxy( li, proxy ); + return true; + } + + // or an uninitialized enhanced entity ("bytecode proxy")... + if ( value instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable bytecodeProxy = (PersistentAttributeInterceptable) value; + final BytecodeLazyAttributeInterceptor interceptor = (BytecodeLazyAttributeInterceptor) bytecodeProxy.$$_hibernate_getInterceptor(); + if ( interceptor != null ) { + interceptor.setSession( getSession() ); + } + return true; + } + } + + return false; } @Override @@ -636,6 +654,14 @@ public class StatefulPersistenceContext implements PersistenceContext { //initialize + unwrap the object and return it return li.getImplementation(); } + else if ( maybeProxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) maybeProxy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( maybeProxy, null ); + } + return maybeProxy; + } else { return maybeProxy; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java index 3b426effe5..f687a6f2aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java @@ -341,7 +341,7 @@ public final class TwoPhaseLoad { return true; } } - + return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java index 4a448e8bb4..9e3dccaed9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java @@ -256,7 +256,7 @@ public class CollectionLoadContext { // If the owner is bytecode-enhanced and the owner's collection value is uninitialized, // then go ahead and set it to the newly initialized collection. final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = - persister.getOwnerEntityPersister().getInstrumentationMetadata(); + persister.getOwnerEntityPersister().getBytecodeEnhancementMetadata(); if ( bytecodeEnhancementMetadata.isEnhancedForLazyLoading() ) { // Lazy properties in embeddables/composites are not currently supported for embeddables (HHH-10480), // so check to make sure the collection is not in an embeddable before checking to see if diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java index 38bf8090be..61e80eca6d 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java @@ -6,47 +6,76 @@ */ package org.hibernate.engine.spi; +import java.util.Collections; +import java.util.Set; + +import org.hibernate.Incubating; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.InterceptorImplementor; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; /** + * The base contract for interceptors that can be injected into + * enhanced entities for the purpose of intercepting attribute access + * * @author Steve Ebersole + * + * @see PersistentAttributeInterceptable */ +@Incubating +@SuppressWarnings("unused") public interface PersistentAttributeInterceptor extends InterceptorImplementor { + boolean readBoolean(Object obj, String name, boolean oldValue); - public boolean readBoolean(Object obj, String name, boolean oldValue); + boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); + byte readByte(Object obj, String name, byte oldValue); - public byte readByte(Object obj, String name, byte oldValue); + byte writeByte(Object obj, String name, byte oldValue, byte newValue); - public byte writeByte(Object obj, String name, byte oldValue, byte newValue); + char readChar(Object obj, String name, char oldValue); - public char readChar(Object obj, String name, char oldValue); + char writeChar(Object obj, String name, char oldValue, char newValue); - public char writeChar(Object obj, String name, char oldValue, char newValue); + short readShort(Object obj, String name, short oldValue); - public short readShort(Object obj, String name, short oldValue); + short writeShort(Object obj, String name, short oldValue, short newValue); - public short writeShort(Object obj, String name, short oldValue, short newValue); + int readInt(Object obj, String name, int oldValue); - public int readInt(Object obj, String name, int oldValue); + int writeInt(Object obj, String name, int oldValue, int newValue); - public int writeInt(Object obj, String name, int oldValue, int newValue); + float readFloat(Object obj, String name, float oldValue); - public float readFloat(Object obj, String name, float oldValue); + float writeFloat(Object obj, String name, float oldValue, float newValue); - public float writeFloat(Object obj, String name, float oldValue, float newValue); + double readDouble(Object obj, String name, double oldValue); - public double readDouble(Object obj, String name, double oldValue); + double writeDouble(Object obj, String name, double oldValue, double newValue); - public double writeDouble(Object obj, String name, double oldValue, double newValue); + long readLong(Object obj, String name, long oldValue); - public long readLong(Object obj, String name, long oldValue); + long writeLong(Object obj, String name, long oldValue, long newValue); - public long writeLong(Object obj, String name, long oldValue, long newValue); + Object readObject(Object obj, String name, Object oldValue); - public Object readObject(Object obj, String name, Object oldValue); + Object writeObject(Object obj, String name, Object oldValue, Object newValue); - public Object writeObject(Object obj, String name, Object oldValue, Object newValue); + /** + * @deprecated Just as the method it overrides. Interceptors that deal with + * lazy state should implement {@link BytecodeLazyAttributeInterceptor} + */ + @Deprecated + @Override + default Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + /** + * @deprecated Just as the method it overrides. Interceptors that deal with + * lazy state should implement {@link BytecodeLazyAttributeInterceptor} + */ + @Override + @Deprecated + default void attributeInitialized(String name) { + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java index d0db9689f6..4b205bc524 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java @@ -246,12 +246,22 @@ public interface SharedSessionContractImplementor Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) throws HibernateException; + default Object internalLoad( + String entityName, + Serializable id, + boolean eager, + boolean nullable, + Boolean unwrapProxy) throws HibernateException { + return internalLoad( entityName, id, eager, nullable ); + } + /** * Load an instance immediately. This method is only called when lazily initializing a proxy. * Do not return the proxy. */ Object immediateLoad(String entityName, Serializable id) throws HibernateException; + /** * Execute a find() query */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java index 39806b7d69..76ce2d1611 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java @@ -363,7 +363,7 @@ public abstract class AbstractSaveEventListener Object[] values, Type[] types, EventSource source) { - WrapVisitor visitor = new WrapVisitor( source ); + WrapVisitor visitor = new WrapVisitor( entity, id, source ); // substitutes into values by side-effect visitor.processEntityPropertyValues( values, types ); return visitor.isSubstitutionRequired(); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java index e160b24e31..d715abd10f 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java @@ -69,8 +69,6 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i * Handle the given load event. * * @param event The load event to be handled. - * - * @throws HibernateException */ public void onLoad( final LoadEvent event, @@ -102,7 +100,7 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i ); } else { - return event.getSession().getFactory().getEntityPersister( event.getEntityClassName() ); + return event.getSession().getFactory().getMetamodel().entityPersister( event.getEntityClassName() ); } } @@ -157,7 +155,7 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i loadType, persister, dependentIdType, - event.getSession().getFactory().getEntityPersister( dependentParentType.getAssociatedEntityName() ) + event.getSession().getFactory().getMetamodel().entityPersister( dependentParentType.getAssociatedEntityName() ) ); return; } @@ -196,8 +194,6 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i * @param options The defined load options * * @return The loaded entity. - * - * @throws HibernateException */ private Object load( final LoadEvent event, @@ -260,27 +256,108 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i ); } - // this class has no proxies (so do a shortcut) - if ( !persister.hasProxy() ) { - return load( event, persister, keyToLoad, options ); - } - final PersistenceContext persistenceContext = event.getSession().getPersistenceContext(); - // look for a proxy - Object proxy = persistenceContext.getProxy( keyToLoad ); - if ( proxy != null ) { - return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy ); - } + final boolean allowBytecodeProxy = event.getSession() + .getFactory() + .getSessionFactoryOptions() + .isEnhancementAsProxyEnabled(); - if ( options.isAllowProxyCreation() ) { - return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext ); + final boolean entityHasHibernateProxyFactory = persister.getEntityMetamodel() + .getTuplizer() + .getProxyFactory() != null; + + // Check for the case where we can use the entity itself as a proxy + if ( options.isAllowProxyCreation() + && allowBytecodeProxy + && persister.getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + // if there is already a managed entity instance associated with the PC, return it + final Object managed = persistenceContext.getEntity( keyToLoad ); + if ( managed != null ) { + if ( options.isCheckDeleted() ) { + final EntityEntry entry = persistenceContext.getEntry( managed ); + final Status status = entry.getStatus(); + if ( status == Status.DELETED || status == Status.GONE ) { + return null; + } + } + return managed; + } + + // if the entity defines a HibernateProxy factory, see if there is an + // existing proxy associated with the PC - and if so, use it + if ( entityHasHibernateProxyFactory ) { + final Object proxy = persistenceContext.getProxy( keyToLoad ); + + if ( proxy != null ) { + LOG.trace( "Entity proxy found in session cache" ); + + LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer(); + + if ( li.isUnwrap() || event.getShouldUnwrapProxy() ) { + return li.getImplementation(); + } + + + return persistenceContext.narrowProxy( proxy, persister, keyToLoad, null ); + } + + // specialized handling for entities with subclasses with a HibernateProxy factory + if ( persister.getEntityMetamodel().hasSubclasses() ) { + // entities with subclasses that define a ProxyFactory can create + // a HibernateProxy so long as NO_PROXY was not specified. + if ( event.getShouldUnwrapProxy() != null && event.getShouldUnwrapProxy() ) { + LOG.debugf( "Ignoring NO_PROXY for to-one association with subclasses to honor laziness" ); + } + return createProxy( event, persister, keyToLoad, persistenceContext ); + } + } + + // This is the crux of HHH-11147 + // create the (uninitialized) entity instance - has only id set + final Object entity = persister.getEntityTuplizer().instantiate( + keyToLoad.getIdentifier(), + event.getSession() + ); + + // add the entity instance to the persistence context + persistenceContext.addEntity( + entity, + Status.MANAGED, + null, + keyToLoad, + null, + LockMode.NONE, + true, + persister, + true + ); + + persister.getEntityMetamodel() + .getBytecodeEnhancementMetadata() + .injectEnhancedEntityAsProxyInterceptor( entity, keyToLoad, event.getSession() ); + + return entity; + } + else { + if ( persister.hasProxy() ) { + // look for a proxy + Object proxy = persistenceContext.getProxy( keyToLoad ); + if ( proxy != null ) { + return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy ); + } + + if ( options.isAllowProxyCreation() ) { + return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext ); + } + } } // return a newly loaded object return load( event, persister, keyToLoad, options ); } + /** * Given a proxy, initialize it and/or narrow it provided either * is necessary. @@ -304,10 +381,13 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i if ( traceEnabled ) { LOG.trace( "Entity proxy found in session cache" ); } + LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer(); + if ( li.isUnwrap() ) { return li.getImplementation(); } + Object impl = null; if ( !options.isAllowProxyCreation() ) { impl = load( event, persister, keyToLoad, options ); @@ -318,6 +398,7 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i .handleEntityNotFound( persister.getEntityName(), keyToLoad.getIdentifier() ); } } + return persistenceContext.narrowProxy( proxy, persister, keyToLoad, impl ); } @@ -358,6 +439,14 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i if ( traceEnabled ) { LOG.trace( "Creating new proxy for entity" ); } + return createProxy( event, persister, keyToLoad, persistenceContext ); + } + + private Object createProxy( + LoadEvent event, + EntityPersister persister, + EntityKey keyToLoad, + PersistenceContext persistenceContext) { // return new uninitialized proxy Object proxy = persister.createProxy( event.getEntityId(), event.getSession() ); persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad ); @@ -376,8 +465,6 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i * @param source The originating session * * @return The loaded entity - * - * @throws HibernateException */ private Object lockAndLoad( final LoadEvent event, @@ -505,6 +592,7 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i * * @return The object loaded from the datasource, or null if not found. */ + @SuppressWarnings("WeakerAccess") protected Object loadFromDatasource( final LoadEvent event, final EntityPersister persister) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java index 7f587cb585..f7458af096 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java @@ -17,12 +17,15 @@ import org.hibernate.WrongClassException; import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -109,26 +112,41 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme final EventSource source = event.getSession(); final Object original = event.getOriginal(); - if ( original != null ) { + // NOTE : `original` is the value being merged + if ( original != null ) { final Object entity; if ( original instanceof HibernateProxy ) { LazyInitializer li = ( (HibernateProxy) original ).getHibernateLazyInitializer(); if ( li.isUninitialized() ) { LOG.trace( "Ignoring uninitialized proxy" ); event.setResult( source.load( li.getEntityName(), li.getIdentifier() ) ); - return; //EARLY EXIT! + //EARLY EXIT! + return; } else { entity = li.getImplementation(); } } + else if ( original instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) original; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor; + LOG.trace( "Ignoring uninitialized enhanced-proxy" ); + event.setResult( source.load( proxyInterceptor.getEntityName(), (Serializable) proxyInterceptor.getIdentifier() ) ); + //EARLY EXIT! + return; + } + else { + entity = original; + } + } else { entity = original; } - if ( copyCache.containsKey( entity ) && - ( copyCache.isOperatedOn( entity ) ) ) { + if ( copyCache.containsKey( entity ) && ( copyCache.isOperatedOn( entity ) ) ) { LOG.trace( "Already in merge process" ); event.setResult( entity ); } @@ -210,36 +228,50 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme LOG.trace( "Merging transient instance" ); final Object entity = event.getEntity(); - final EventSource source = event.getSession(); + final EventSource session = event.getSession(); final String entityName = event.getEntityName(); - final EntityPersister persister = source.getEntityPersister( entityName, entity ); + final EntityPersister persister = session.getEntityPersister( entityName, entity ); - final Serializable id = persister.hasIdentifierProperty() ? - persister.getIdentifier( entity, source ) : - null; - if ( copyCache.containsKey( entity ) ) { - persister.setIdentifier( copyCache.get( entity ), id, source ); + final Serializable id = persister.hasIdentifierProperty() + ? persister.getIdentifier( entity, session ) + : null; + + final Object copy; + final Object existingCopy = copyCache.get( entity ); + if ( existingCopy != null ) { + persister.setIdentifier( copyCache.get( entity ), id, session ); + copy = existingCopy; } else { - ( (MergeContext) copyCache ).put( entity, source.instantiate( persister, id ), true ); //before cascade! + copy = session.instantiate( persister, id ); + + //before cascade! + ( (MergeContext) copyCache ).put( entity, copy, true ); } - final Object copy = copyCache.get( entity ); // cascade first, so that all unsaved objects get their // copy created before we actually copy //cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE); - super.cascadeBeforeSave( source, persister, entity, copyCache ); - copyValues( persister, entity, copy, source, copyCache, ForeignKeyDirection.FROM_PARENT ); + super.cascadeBeforeSave( session, persister, entity, copyCache ); + copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.FROM_PARENT ); - saveTransientEntity( copy, entityName, event.getRequestedId(), source, copyCache ); + saveTransientEntity( copy, entityName, event.getRequestedId(), session, copyCache ); // cascade first, so that all unsaved objects get their // copy created before we actually copy - super.cascadeAfterSave( source, persister, entity, copyCache ); - copyValues( persister, entity, copy, source, copyCache, ForeignKeyDirection.TO_PARENT ); + super.cascadeAfterSave( session, persister, entity, copyCache ); + copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.TO_PARENT ); event.setResult( copy ); + + if ( copy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) copy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor == null ) { + persister.getBytecodeEnhancementMetadata().injectInterceptor( copy, id, session ); + } + } } private void saveTransientEntity( @@ -283,11 +315,12 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme String previousFetchProfile = source.getLoadQueryInfluencers().getInternalFetchProfile(); source.getLoadQueryInfluencers().setInternalFetchProfile( "merge" ); + //we must clone embedded composite identifiers, or //we will get back the same instance that we pass in - final Serializable clonedIdentifier = (Serializable) persister.getIdentifierType() - .deepCopy( id, source.getFactory() ); + final Serializable clonedIdentifier = (Serializable) persister.getIdentifierType().deepCopy( id, source.getFactory() ); final Object result = source.get( entityName, clonedIdentifier ); + source.getLoadQueryInfluencers().setInternalFetchProfile( previousFetchProfile ); if ( result == null ) { @@ -301,9 +334,11 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme entityIsTransient( event, copyCache ); } else { - ( (MergeContext) copyCache ).put( entity, result, true ); //before cascade! + // before cascade! + ( (MergeContext) copyCache ).put( entity, result, true ); + + final Object target = unproxyManagedForDetachedMerging( entity, result, persister, source ); - final Object target = source.getPersistenceContext().unproxy( result ); if ( target == entity ) { throw new AssertionFailure( "entity was not detached" ); } @@ -334,6 +369,43 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme } + private Object unproxyManagedForDetachedMerging( + Object incoming, + Object managed, + EntityPersister persister, + EventSource source) { + if ( incoming instanceof HibernateProxy ) { + return source.getPersistenceContext().unproxy( managed ); + } + + if ( incoming instanceof PersistentAttributeInterceptable + && persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() + && source.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled() ) { + + final PersistentAttributeInterceptor incomingInterceptor = ( (PersistentAttributeInterceptable) incoming ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor managedInterceptor = ( (PersistentAttributeInterceptable) managed ).$$_hibernate_getInterceptor(); + + // todo - do we need to specially handle the case where both `incoming` and `managed` are initialized, but + // with different attributes initialized? + // - for now, assume we do not... + + // if the managed entity is not a proxy, we can just return it + if ( ! ( managedInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) ) { + return managed; + } + + // if the incoming entity is still a proxy there is no need to force initialization of the managed one + if ( incomingInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return managed; + } + + // otherwise, force initialization + persister.initializeEnhancedEntityUsedAsProxy( managed, null, source ); + } + + return managed; + } + private void markInterceptorDirty(final Object entity, final Object target, EntityPersister persister) { // for enhanced entities, copy over the dirty attributes if ( entity instanceof SelfDirtinessTracker && target instanceof SelfDirtinessTracker ) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java index a506004105..c5cb4caada 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java @@ -21,17 +21,20 @@ import org.hibernate.type.CollectionType; * @author Gavin King */ public class FlushVisitor extends AbstractVisitor { - private Object owner; - Object processCollection(Object collection, CollectionType type) - throws HibernateException { + FlushVisitor(EventSource session, Object owner) { + super(session); + this.owner = owner; + } + + Object processCollection(Object collection, CollectionType type) throws HibernateException { - if (collection==CollectionType.UNFETCHED_COLLECTION) { + if ( collection == CollectionType.UNFETCHED_COLLECTION ) { return null; } - if (collection!=null) { + if ( collection != null ) { final PersistentCollection coll; if ( type.hasHolder() ) { coll = getSession().getPersistenceContext().getCollectionHolder(collection); @@ -55,9 +58,4 @@ public class FlushVisitor extends AbstractVisitor { return true; } - FlushVisitor(EventSource session, Object owner) { - super(session); - this.owner = owner; - } - } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java index 923d9389d1..0ddbc19b8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java @@ -6,8 +6,11 @@ */ package org.hibernate.event.internal; +import java.io.Serializable; + import org.hibernate.EntityMode; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionImplementor; @@ -25,10 +28,19 @@ import org.hibernate.type.Type; * * @author Gavin King */ +@SuppressWarnings("WeakerAccess") public class WrapVisitor extends ProxyVisitor { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( WrapVisitor.class ); + private Object entity; + private Serializable id; - boolean substitute; + private boolean substitute; + + public WrapVisitor(Object entity, Serializable id, EventSource session) { + super( session ); + this.entity = entity; + this.id = id; + } boolean isSubstitutionRequired() { return substitute; @@ -42,20 +54,26 @@ public class WrapVisitor extends ProxyVisitor { Object processCollection(Object collection, CollectionType collectionType) throws HibernateException { - if ( collection != null && ( collection instanceof PersistentCollection ) ) { + if ( collection == null ) { + return null; + } + if ( collection == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + return null; + } + + if ( collection instanceof PersistentCollection ) { + final PersistentCollection coll = (PersistentCollection) collection; final SessionImplementor session = getSession(); - PersistentCollection coll = (PersistentCollection) collection; + if ( coll.setCurrentSession( session ) ) { reattachCollection( coll, collectionType ); } + return null; - - } - else { - return processArrayOrNewCollection( collection, collectionType ); } + return processArrayOrNewCollection( collection, collectionType ); } final Object processArrayOrNewCollection(Object collection, CollectionType collectionType) diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java index ed186ab395..eccbe18a34 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java @@ -47,6 +47,8 @@ public class LoadEvent extends AbstractEvent { private Object result; private PostLoadEvent postLoadEvent; + private Boolean shouldUnwrapProxy; + public LoadEvent(Serializable entityId, Object instanceToLoad, EventSource source) { this( entityId, null, instanceToLoad, DEFAULT_LOCK_OPTIONS, false, source ); } @@ -190,4 +192,19 @@ public class LoadEvent extends AbstractEvent { public void setPostLoadEvent(PostLoadEvent postLoadEvent) { this.postLoadEvent = postLoadEvent; } + + public Boolean getShouldUnwrapProxy() { + if ( shouldUnwrapProxy == null ) { + final boolean enabled = getSession().getFactory() + .getSessionFactoryOptions() + .isEnhancementAsProxyEnabled(); + return enabled; + } + + return shouldUnwrapProxy; + } + + public void setShouldUnwrapProxy(Boolean shouldUnwrapProxy) { + this.shouldUnwrapProxy = shouldUnwrapProxy; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java index f035011744..63a0d93375 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java @@ -13,6 +13,7 @@ import java.sql.SQLException; import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.ScrollableResults; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.hql.internal.HolderInstantiator; @@ -189,21 +190,28 @@ public class ScrollableResultsImpl extends AbstractScrollableResults implements return; } - final Object result = getLoader().loadSingleRow( - getResultSet(), - getSession(), - getQueryParameters(), - true - ); - if ( result != null && result.getClass().isArray() ) { - currentRow = (Object[]) result; - } - else { - currentRow = new Object[] {result}; - } + final PersistenceContext persistenceContext = getSession().getPersistenceContext(); + persistenceContext.beforeLoad(); + try { + final Object result = getLoader().loadSingleRow( + getResultSet(), + getSession(), + getQueryParameters(), + true + ); + if ( result != null && result.getClass().isArray() ) { + currentRow = (Object[]) result; + } + else { + currentRow = new Object[] {result}; + } - if ( getHolderInstantiator() != null ) { - currentRow = new Object[] {getHolderInstantiator().instantiate( currentRow )}; + if ( getHolderInstantiator() != null ) { + currentRow = new Object[] { getHolderInstantiator().instantiate( currentRow ) }; + } + } + finally { + persistenceContext.afterLoad(); } afterScrollOperation(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 4f5a01f8db..a7f997a0c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1123,25 +1123,40 @@ public final class SessionImpl } return result; } + @Override + public final Object internalLoad( + String entityName, + Serializable id, + boolean eager, + boolean nullable) throws HibernateException { + return internalLoad( entityName, id, eager, nullable, null ); + } @Override - public final Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) - throws HibernateException { - // todo : remove - LoadEventListener.LoadType type = nullable - ? LoadEventListener.INTERNAL_LOAD_NULLABLE - : eager - ? LoadEventListener.INTERNAL_LOAD_EAGER - : LoadEventListener.INTERNAL_LOAD_LAZY; + public final Object internalLoad( + String entityName, + Serializable id, + boolean eager, + boolean nullable, + Boolean unwrapProxy) { + final LoadEventListener.LoadType type; + if ( nullable ) { + type = LoadEventListener.INTERNAL_LOAD_NULLABLE; + } + else { + type = eager + ? LoadEventListener.INTERNAL_LOAD_EAGER + : LoadEventListener.INTERNAL_LOAD_LAZY; + } LoadEvent event = loadEvent; loadEvent = null; event = recycleEventInstance( event, id, entityName ); - fireLoad( event, type ); + event.setShouldUnwrapProxy( unwrapProxy );fireLoad( event, type ); Object result = event.getResult(); if ( !nullable ) { - UnresolvableObjectException.throwIfNull( result, id, entityName ); - } + UnresolvableObjectException.throwIfNull( result, id, entityName );} + if ( loadEvent == null ) { event.setEntityClassName( null ); event.setEntityId( null ); @@ -1166,6 +1181,7 @@ public final class SessionImpl event.setLockMode( LoadEvent.DEFAULT_LOCK_MODE ); event.setLockScope( LoadEvent.DEFAULT_LOCK_OPTIONS.getScope() ); event.setLockTimeout( LoadEvent.DEFAULT_LOCK_OPTIONS.getTimeOut() ); + event.setShouldUnwrapProxy( null ); return event; } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java index 375ed7a2c9..a9d98b6fe4 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -65,13 +65,15 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen } }; - private PersistenceContext temporaryPersistenceContext = new StatefulPersistenceContext( this ); + private final PersistenceContext temporaryPersistenceContext = new StatefulPersistenceContext( this ); - private boolean connectionProvided; + private final boolean connectionProvided; + private final boolean allowBytecodeProxy; StatelessSessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { super( factory, options ); connectionProvided = options.getConnection() != null; + allowBytecodeProxy = getFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled(); } @Override @@ -248,9 +250,12 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen } @Override - public Object immediateLoad(String entityName, Serializable id) - throws HibernateException { - throw new SessionException( "proxies cannot be fetched by a stateless session" ); + public Object immediateLoad(String entityName, Serializable id) throws HibernateException { + if ( getPersistenceContext().isLoadFinished() ) { + throw new SessionException( "proxies cannot be fetched by a stateless session" ); + } + // unless we are still in the process of handling a top-level load + return get( entityName, id ); } @Override @@ -275,19 +280,54 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen boolean eager, boolean nullable) throws HibernateException { checkOpen(); + EntityPersister persister = getFactory().getMetamodel().entityPersister( entityName ); + final EntityKey entityKey = generateEntityKey( id, persister ); + // first, try to load it from the temp PC associated to this SS - Object loaded = temporaryPersistenceContext.getEntity( generateEntityKey( id, persister ) ); + Object loaded = temporaryPersistenceContext.getEntity( entityKey ); if ( loaded != null ) { // we found it in the temp PC. Should indicate we are in the midst of processing a result set // containing eager fetches via join fetch return loaded; } - if ( !eager && persister.hasProxy() ) { - // if the metadata allowed proxy creation and caller did not request forceful eager loading, - // generate a proxy - return persister.createProxy( id, this ); + + if ( !eager ) { + // caller did not request forceful eager loading, see if we can create + // some form of proxy + + // first, check to see if we can use "bytecode proxies" + + if ( allowBytecodeProxy + && persister.getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + + // we cannot use bytecode proxy for entities with subclasses + if ( !persister.getEntityMetamodel().hasSubclasses() ) { + final Object entity = persister.getEntityTuplizer().instantiate( id, this ); + + persister.getEntityMetamodel() + .getBytecodeEnhancementMetadata() + .injectEnhancedEntityAsProxyInterceptor( entity, entityKey, this ); + + getPersistenceContext().addEntity( entityKey, entity ); + return entity; + } + } + + // we could not use bytecode proxy, check to see if we can use HibernateProxy + if ( persister.hasProxy() ) { + final Object existingProxy = getPersistenceContext().getProxy( entityKey ); + if ( existingProxy != null ) { + return getPersistenceContext().narrowProxy( existingProxy, persister, entityKey, null ); + } + else { + final Object proxy = persister.createProxy( id, this ); + getPersistenceContext().addProxy( entityKey, proxy ); + return proxy; + } + } } + // otherwise immediately materialize it return get( entityName, id ); } @@ -424,6 +464,18 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen @Override public Object getEntityUsingInterceptor(EntityKey key) throws HibernateException { checkOpen(); + + final Object result = getPersistenceContext().getEntity( key ); + if ( result != null ) { + return result; + } + + final Object newObject = getInterceptor().getEntity( key.getEntityName(), key.getIdentifier() ); + if ( newObject != null ) { + getPersistenceContext().addEntity( key, newObject ); + return newObject; + } + return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java index 4576328131..a7f48dd26e 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java @@ -35,6 +35,12 @@ public final class ArrayHelper { return -1; } + public static T[] filledArray(T value, Class valueJavaType, int size) { + final T[] array = (T[]) Array.newInstance( valueJavaType, size ); + Arrays.fill( array, value ); + return array; + } + public static String[] toStringArray(Object[] objects) { int length = objects.length; String[] result = new String[length]; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index a3ebcd2069..5cf761cf10 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -31,6 +31,7 @@ import org.hibernate.ScrollMode; import org.hibernate.Session; import org.hibernate.StaleObjectStateException; import org.hibernate.WrongClassException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.cache.spi.FilterKey; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; @@ -50,6 +51,8 @@ import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.RowSelection; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -66,6 +69,7 @@ import org.hibernate.internal.FetchingScrollableResultsImpl; import org.hibernate.internal.ScrollableResultsImpl; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.loader.entity.CascadeEntityLoader; import org.hibernate.loader.spi.AfterLoadAction; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; @@ -107,12 +111,14 @@ public abstract class Loader { private volatile ColumnNameCache columnNameCache; private final boolean referenceCachingEnabled; + private final boolean enhancementAsProxyEnabled; private boolean isJdbc4 = true; public Loader(SessionFactoryImplementor factory) { this.factory = factory; this.referenceCachingEnabled = factory.getSessionFactoryOptions().isDirectReferenceCacheEntriesEnabled(); + this.enhancementAsProxyEnabled = factory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(); } /** @@ -829,6 +835,7 @@ public abstract class Loader { keys[targetIndex], object, lockModes[targetIndex], + hydratedObjects, session ); } @@ -1491,7 +1498,8 @@ public abstract class Loader { Object version = session.getPersistenceContext().getEntry( entity ).getVersion(); - if ( version != null ) { //null version means the object is in the process of being loaded somewhere else in the ResultSet + if ( version != null ) { + // null version means the object is in the process of being loaded somewhere else in the ResultSet final VersionType versionType = persister.getVersionType(); final Object currentVersion = versionType.nullSafeGet( rs, @@ -1526,7 +1534,7 @@ public abstract class Loader { final List hydratedObjects, final SharedSessionContractImplementor session) throws HibernateException, SQLException { final int cols = persisters.length; - final EntityAliases[] descriptors = getEntityAliases(); + final EntityAliases[] entityAliases = getEntityAliases(); if ( LOG.isDebugEnabled() ) { LOG.debugf( "Result row: %s", StringHelper.toString( keys ) ); @@ -1546,7 +1554,6 @@ public abstract class Loader { //If the object is already loaded, return the loaded one object = session.getEntityUsingInterceptor( key ); if ( object != null ) { - //its already loaded so don't need to hydrate it instanceAlreadyLoaded( rs, i, @@ -1554,6 +1561,7 @@ public abstract class Loader { key, object, lockModes[i], + hydratedObjects, session ); } @@ -1562,7 +1570,7 @@ public abstract class Loader { rs, i, persisters[i], - descriptors[i].getRowIdAlias(), + entityAliases[i].getRowIdAlias(), key, lockModes[i], optionalObjectKey, @@ -1590,6 +1598,7 @@ public abstract class Loader { final EntityKey key, final Object object, final LockMode requestedLockMode, + List hydratedObjects, final SharedSessionContractImplementor session) throws HibernateException, SQLException { if ( !persister.isInstance( object ) ) { @@ -1600,7 +1609,42 @@ public abstract class Loader { ); } - if ( LockMode.NONE != requestedLockMode && upgradeLocks() ) { //no point doing this if NONE was requested + if ( persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() && enhancementAsProxyEnabled ) { + if ( "merge".equals( session.getLoadQueryInfluencers().getInternalFetchProfile() ) ) { + assert this instanceof CascadeEntityLoader; + // we are processing a merge and have found an existing "managed copy" in the + // session - we need to check if this copy is an enhanced-proxy and, if so, + // perform the hydration just as if it were "not yet loaded" + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) object; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + hydrateEntityState( + rs, + i, + persister, + getEntityAliases()[i].getRowIdAlias(), + key, + hydratedObjects, + session, + getInstanceClass( + rs, + i, + persister, + key.getIdentifier(), + session + ), + object, + requestedLockMode + ); + + // EARLY EXIT!!! + // - to skip the version check + return; + } + } + } + + if ( LockMode.NONE != requestedLockMode && upgradeLocks() ) { final EntityEntry entry = session.getPersistenceContext().getEntry( object ); if ( entry.getLockMode().lessThan( requestedLockMode ) ) { //we only check the version when _upgrading_ lock modes @@ -1669,6 +1713,33 @@ public abstract class Loader { // (but don't yet initialize the object itself) // note that we acquire LockMode.READ even if it was not requested LockMode acquiredLockMode = lockMode == LockMode.NONE ? LockMode.READ : lockMode; + hydrateEntityState( + rs, + i, + persister, + rowIdAlias, + key, + hydratedObjects, + session, + instanceClass, + object, + acquiredLockMode + ); + + return object; + } + + private void hydrateEntityState( + ResultSet rs, + int i, + Loadable persister, + String rowIdAlias, + EntityKey key, + List hydratedObjects, + SharedSessionContractImplementor session, + String instanceClass, + Object object, + LockMode acquiredLockMode) throws SQLException { loadFromResultSet( rs, i, @@ -1683,8 +1754,6 @@ public abstract class Loader { //materialize associations (and initialize the object) later hydratedObjects.add( object ); - - return object; } private boolean isEagerPropertyFetchEnabled(int i) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java index 2976734597..f327ed4102 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java @@ -14,6 +14,9 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.StaleObjectStateException; import org.hibernate.WrongClassException; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.engine.internal.TwoPhaseLoad; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.EntityKey; @@ -195,6 +198,31 @@ public class EntityReferenceInitializerImpl implements EntityReferenceInitialize // use the existing association as the hydrated state processingState.registerEntityInstance( existing ); //context.registerHydratedEntity( entityReference, entityKey, existing ); + + // see if the entity is enhanced and is being used as a "proxy" (is fully uninitialized) + final BytecodeEnhancementMetadata enhancementMetadata = entityReference.getEntityPersister() + .getEntityMetamodel() + .getBytecodeEnhancementMetadata(); + + if ( enhancementMetadata.isEnhancedForLazyLoading() ) { + final BytecodeLazyAttributeInterceptor interceptor = enhancementMetadata.extractLazyInterceptor( existing ); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final LockMode requestedLockMode = context.resolveLockMode( entityReference ); + final LockMode lockModeToAcquire = requestedLockMode == LockMode.NONE + ? LockMode.READ + : requestedLockMode; + + loadFromResultSet( + resultSet, + context, + existing, + getConcreteEntityTypeName( resultSet, context, entityKey ), + entityKey, + lockModeToAcquire + ); + } + } + return; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index ebf86b44e9..f18d27046b 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -14,6 +14,7 @@ import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.PropertyNotFoundException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyles; import org.hibernate.engine.spi.Mapping; @@ -233,29 +234,31 @@ public class Property implements Serializable, MetaAttributable { public void setLazy(boolean lazy) { this.lazy=lazy; } - + + /** + * Is this property lazy in the "bytecode" sense? + * + * Lazy here means whether we should push *something* to the entity + * instance for this field in its "base fetch group". Mainly it affects + * whether we should list this property's columns in the SQL select + * for the owning entity when we load its "base fetch group". + * + * The "something" we push varies based on the nature (basic, etc) of + * the property. + * + * @apiNote This form reports whether the property is considered part of the + * base fetch group based solely on the mapping information. However, + * {@link EnhancementHelper#includeInBaseFetchGroup} is used internally to make that + * decision to account for {@link org.hibernate.cfg.AvailableSettings#ALLOW_ENHANCEMENT_AS_PROXY} + */ public boolean isLazy() { if ( value instanceof ToOne ) { - // both many-to-one and one-to-one are represented as a - // Property. EntityPersister is relying on this value to - // determine "lazy fetch groups" in terms of field-level - // interception. So we need to make sure that we return - // true here for the case of many-to-one and one-to-one - // with lazy="no-proxy" - // - // * impl note - lazy="no-proxy" currently forces both - // lazy and unwrap to be set to true. The other case we - // are extremely interested in here is that of lazy="proxy" - // where lazy is set to true, but unwrap is set to false. - // thus we use both here under the assumption that this - // return is really only ever used during persister - // construction to determine the lazy property/field fetch - // groupings. If that assertion changes then this check - // needs to change as well. Partially, this is an issue with - // the overloading of the term "lazy" here... - ToOne toOneValue = ( ToOne ) value; - return toOneValue.isLazy() && toOneValue.isUnwrapProxy(); + // For a many-to-one, this is always false. Whether the + // association is EAGER, PROXY or NO-PROXY we want the fk + // selected + return false; } + return lazy; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index d0b794d918..2f2ccbd76d 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -38,6 +38,9 @@ import org.hibernate.Session; import org.hibernate.StaleObjectStateException; import org.hibernate.StaleStateException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; @@ -74,6 +77,7 @@ import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.PersistenceContext.NaturalIdHelper; import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.ValueInclusion; @@ -551,7 +555,7 @@ public abstract class AbstractEntityPersister this.naturalIdRegionAccessStrategy = null; } - this.entityMetamodel = new EntityMetamodel( persistentClass, this, factory ); + this.entityMetamodel = new EntityMetamodel( persistentClass, this, creationContext ); this.entityTuplizer = this.entityMetamodel.getTuplizer(); if ( entityMetamodel.isMutable() ) { @@ -684,7 +688,20 @@ public abstract class AbstractEntityPersister propertyColumnWriters[i] = colWriters; propertyColumnAliases[i] = colAliases; - if ( lazyAvailable && prop.isLazy() ) { + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + entityMetamodel.isInstrumented(), + creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled(), + associatedEntityName -> { + final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); + if ( bootEntityDescriptor == null ) { + return false; + } + return bootEntityDescriptor.hasSubclasses(); + } + ); + + if ( lazy ) { lazyNames.add( prop.getName() ); lazyNumbers.add( i ); lazyTypes.add( prop.getValue().getType() ); @@ -754,7 +771,18 @@ public abstract class AbstractEntityPersister int[] colnos = new int[prop.getColumnSpan()]; int[] formnos = new int[prop.getColumnSpan()]; int l = 0; - Boolean lazy = Boolean.valueOf( prop.isLazy() && lazyAvailable ); + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + entityMetamodel.isInstrumented(), + creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled(), + associatedEntityName -> { + final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); + if ( bootEntityDescriptor == null ) { + return false; + } + return bootEntityDescriptor.hasSubclasses(); + } + ); while ( colIter.hasNext() ) { Selectable thing = (Selectable) colIter.next(); if ( thing.isFormula() ) { @@ -1025,7 +1053,7 @@ public abstract class AbstractEntityPersister public Object initializeLazyProperty(String fieldName, Object entity, SharedSessionContractImplementor session) { final EntityEntry entry = session.getPersistenceContext().getEntry( entity ); - final InterceptorImplementor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); assert interceptor != null : "Expecting bytecode interceptor to be non-null"; if ( hasCollections() ) { @@ -1052,10 +1080,10 @@ public abstract class AbstractEntityPersister session.getPersistenceContext().addUninitializedCollection( persister, collection, key ); } - // HHH-11161 Initialize, if the collection is not extra lazy - if ( !persister.isExtraLazy() ) { - session.initializeCollection( collection, false ); - } +// // HHH-11161 Initialize, if the collection is not extra lazy +// if ( !persister.isExtraLazy() ) { +// session.initializeCollection( collection, false ); +// } interceptor.attributeInitialized( fieldName ); if ( collectionType.isArrayType() ) { @@ -1146,10 +1174,10 @@ public abstract class AbstractEntityPersister throw new AssertionFailure( "no lazy properties" ); } - final InterceptorImplementor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); assert interceptor != null : "Expecting bytecode interceptor to be non-null"; - LOG.trace( "Initializing lazy properties from datastore" ); + LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName ); final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata() .getLazyAttributesMetadata() @@ -4273,6 +4301,50 @@ public abstract class AbstractEntityPersister return loader.load( id, optionalObject, session, lockOptions ); } + @Override + public Object initializeEnhancedEntityUsedAsProxy( + Object entity, + String nameOfAttributeBeingAccessed, + SharedSessionContractImplementor session) { + final BytecodeEnhancementMetadata enhancementMetadata = getEntityMetamodel().getBytecodeEnhancementMetadata(); + final BytecodeLazyAttributeInterceptor currentInterceptor = enhancementMetadata.extractLazyInterceptor( entity ); + if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) currentInterceptor; + + readLockLoader.load( + proxyInterceptor.getEntityKey().getIdentifier(), + entity, + session, + LockOptions.READ + ); + + final LazyAttributeLoadingInterceptor interceptor = enhancementMetadata.injectInterceptor( + entity, + proxyInterceptor.getEntityKey().getIdentifier(), + session + ); + + final Object value; + if ( nameOfAttributeBeingAccessed == null ) { + return null; + } + else if ( interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) ) { + value = getEntityTuplizer().getPropertyValue( entity, nameOfAttributeBeingAccessed ); + } + else { + value = ( (LazyPropertyInitializer) this ).initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ); + } + + return interceptor.readObject( + entity, + nameOfAttributeBeingAccessed, + value + ); + } + + throw new IllegalStateException( ); + } + @Override public List multiLoad(Serializable[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions) { return DynamicBatchingEntityLoaderBuilder.INSTANCE.multiLoad( @@ -4582,9 +4654,14 @@ public abstract class AbstractEntityPersister public void afterReassociate(Object entity, SharedSessionContractImplementor session) { if ( getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { - LazyAttributeLoadingInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractInterceptor( entity ); + final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata() + .extractLazyInterceptor( entity ); if ( interceptor == null ) { - getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( entity, session ); + getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( + entity, + getIdentifier( entity, session ), + session + ); } else { interceptor.setSession( session ); @@ -5453,6 +5530,11 @@ public abstract class AbstractEntityPersister @Override public BytecodeEnhancementMetadata getInstrumentationMetadata() { + return getBytecodeEnhancementMetadata(); + } + + @Override + public BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() { return entityMetamodel.getBytecodeEnhancementMetadata(); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index eb47a19382..dac30cd60e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -15,7 +15,9 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; +import org.hibernate.bytecode.spi.NotInstrumentedException; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.entry.CacheEntry; @@ -132,6 +134,20 @@ public interface EntityPersister extends EntityDefinition { */ EntityMetamodel getEntityMetamodel(); + /** + * Called from {@link EnhancementAsProxyLazinessInterceptor} to trigger load of + * the entity's non-lazy state as well as the named attribute we are accessing + * if it is still uninitialized after fetching non-lazy state + */ + default Object initializeEnhancedEntityUsedAsProxy( + Object entity, + String nameOfAttributeBeingAccessed, + SharedSessionContractImplementor session) { + throw new UnsupportedOperationException( + "Initialization of entity enhancement used to act like a proxy is not supported by this EntityPersister : " + getClass().getName() + ); + } + /** * Determine whether the given name represents a subclass entity * (or this entity itself) of the entity mapped by this persister. @@ -798,7 +814,11 @@ public interface EntityPersister extends EntityDefinition { EntityTuplizer getEntityTuplizer(); BytecodeEnhancementMetadata getInstrumentationMetadata(); - + + default BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() { + return getInstrumentationMetadata(); + } + FilterAliasGenerator getFilterAliasGenerator(final String rootAlias); /** diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java index 4b770edf44..9265687f55 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java @@ -7,9 +7,11 @@ package org.hibernate.tuple; import java.lang.reflect.Constructor; +import java.util.function.Function; import org.hibernate.EntityMode; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.engine.internal.UnsavedValueFactory; import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -153,7 +155,8 @@ public final class PropertyFactory { SessionFactoryImplementor sessionFactory, int attributeNumber, Property property, - boolean lazyAvailable) { + boolean lazyAvailable, + Function hasSubclassChecker) { final Type type = property.getValue().getType(); final NonIdentifierAttributeNature nature = decode( type ); @@ -168,6 +171,13 @@ public final class PropertyFactory { boolean alwaysDirtyCheck = type.isAssociationType() && ( (AssociationType) type ).isAlwaysDirtyChecked(); + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + property, + lazyAvailable, + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(), + hasSubclassChecker + ); + switch ( nature ) { case BASIC: { return new EntityBasedBasicAttribute( @@ -177,7 +187,7 @@ public final class PropertyFactory { property.getName(), type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -197,7 +207,7 @@ public final class PropertyFactory { property.getName(), (CompositeType) type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -219,7 +229,7 @@ public final class PropertyFactory { property.getName(), (AssociationType) type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -279,7 +289,9 @@ public final class PropertyFactory { return new StandardProperty( property.getName(), type, - lazyAvailable && property.isLazy(), + // only called for embeddable sub-attributes which are never (yet) lazy + //lazyAvailable && property.isLazy(), + false, property.isInsertable(), property.isUpdateable(), property.getValueGenerationStrategy(), diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java index 0702145823..076e4ddb15 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java @@ -16,6 +16,7 @@ import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; @@ -152,7 +153,8 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer { instantiator = buildInstantiator( entityMetamodel, mappingInfo ); - if ( entityMetamodel.isLazy() && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { +// if ( entityMetamodel.isLazy() && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + if ( entityMetamodel.isLazy() ) { proxyFactory = buildProxyFactory( mappingInfo, idGetter, idSetter ); if ( proxyFactory == null ) { entityMetamodel.setLazy( false ); @@ -535,18 +537,24 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer { @Override public Object[] getPropertyValues(Object entity) { final BytecodeEnhancementMetadata enhancementMetadata = entityMetamodel.getBytecodeEnhancementMetadata(); + final LazyAttributesMetadata lazyAttributesMetadata = enhancementMetadata.getLazyAttributesMetadata(); + final int span = entityMetamodel.getPropertySpan(); final Object[] result = new Object[span]; for ( int j = 0; j < span; j++ ) { - NonIdentifierAttribute property = entityMetamodel.getProperties()[j]; - if ( !property.isLazy() || enhancementMetadata.isAttributeLoaded( entity, property.getName() ) ) { + // if the attribute is not lazy (bytecode sense), we can just use the value from the instance + // if the attribute is lazy but has been initialized we can just use the value from the instance + // todo : there should be a third case here when we merge transient instances + if ( ! lazyAttributesMetadata.isLazyAttribute( entityMetamodel.getPropertyNames()[j] ) + || enhancementMetadata.isAttributeLoaded( entity, entityMetamodel.getPropertyNames()[j] ) ) { result[j] = getters[j].get( entity ); } else { result[j] = LazyPropertyInitializer.UNFETCHED_PROPERTY; } } + return result; } @@ -718,7 +726,8 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer { return instantiator; } - protected final ProxyFactory getProxyFactory() { + @Override + public final ProxyFactory getProxyFactory() { return proxyFactory; } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java index 10286c23a6..6f0709c9ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java @@ -6,10 +6,13 @@ */ package org.hibernate.tuple.entity; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.bytecode.spi.NotInstrumentedException; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -44,15 +47,37 @@ public class BytecodeEnhancementMetadataNonPojoImpl implements BytecodeEnhanceme @Override public LazyAttributeLoadingInterceptor injectInterceptor( Object entity, + Object identifier, SharedSessionContractImplementor session) throws NotInstrumentedException { throw new NotInstrumentedException( errorMsg ); } + @Override + public void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session) { + throw new NotInstrumentedException( errorMsg ); + } + + @Override + public void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session) { + throw new NotInstrumentedException( errorMsg ); + } + @Override public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException { throw new NotInstrumentedException( errorMsg ); } + @Override + public BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException { + throw new NotInstrumentedException( errorMsg ); + } + @Override public boolean hasUnFetchedAttributes(Object entity) { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java index 48134c0cee..3e4ba070d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java @@ -6,29 +6,46 @@ */ package org.hibernate.tuple.entity; +import java.util.Set; +import java.util.function.Function; + +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.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.bytecode.spi.NotInstrumentedException; +import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.mapping.PersistentClass; +import org.hibernate.type.CompositeType; /** * @author Steve Ebersole */ public class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhancementMetadata { - public static BytecodeEnhancementMetadata from(PersistentClass persistentClass) { + /** + * Static constructor + */ + public static BytecodeEnhancementMetadata from( + PersistentClass persistentClass, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, + boolean allowEnhancementAsProxy, + Function hasSubclassChecker) { final Class mappedClass = persistentClass.getMappedClass(); final boolean enhancedForLazyLoading = PersistentAttributeInterceptable.class.isAssignableFrom( mappedClass ); final LazyAttributesMetadata lazyAttributesMetadata = enhancedForLazyLoading - ? LazyAttributesMetadata.from( persistentClass ) + ? LazyAttributesMetadata.from( persistentClass, true, allowEnhancementAsProxy, hasSubclassChecker ) : LazyAttributesMetadata.nonEnhanced( persistentClass.getEntityName() ); return new BytecodeEnhancementMetadataPojoImpl( persistentClass.getEntityName(), mappedClass, + identifierAttributeNames, + nonAggregatedCidMapper, enhancedForLazyLoading, lazyAttributesMetadata ); @@ -36,16 +53,26 @@ public class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhancementM private final String entityName; private final Class entityClass; + private final Set identifierAttributeNames; + private final CompositeType nonAggregatedCidMapper; private final boolean enhancedForLazyLoading; private final LazyAttributesMetadata lazyAttributesMetadata; - public BytecodeEnhancementMetadataPojoImpl( + @SuppressWarnings("WeakerAccess") + protected BytecodeEnhancementMetadataPojoImpl( String entityName, Class entityClass, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, boolean enhancedForLazyLoading, LazyAttributesMetadata lazyAttributesMetadata) { + this.nonAggregatedCidMapper = nonAggregatedCidMapper; + assert identifierAttributeNames != null; + assert !identifierAttributeNames.isEmpty(); + this.entityName = entityName; this.entityClass = entityClass; + this.identifierAttributeNames = identifierAttributeNames; this.enhancedForLazyLoading = enhancedForLazyLoading; this.lazyAttributesMetadata = lazyAttributesMetadata; } @@ -67,18 +94,114 @@ public class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhancementM @Override public boolean hasUnFetchedAttributes(Object entity) { - LazyAttributeLoadingInterceptor interceptor = enhancedForLazyLoading ? extractInterceptor( entity ) : null; - return interceptor != null && interceptor.hasAnyUninitializedAttributes(); + if ( ! enhancedForLazyLoading ) { + return false; + } + + final BytecodeLazyAttributeInterceptor interceptor = extractLazyInterceptor( entity ); + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { + return ( (LazyAttributeLoadingInterceptor) interceptor ).hasAnyUninitializedAttributes(); + } + + //noinspection RedundantIfStatement + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return true; + } + + return false; } @Override public boolean isAttributeLoaded(Object entity, String attributeName) { - LazyAttributeLoadingInterceptor interceptor = enhancedForLazyLoading ? extractInterceptor( entity ) : null; - return interceptor == null || interceptor.isAttributeLoaded( attributeName ); + if ( ! enhancedForLazyLoading ) { + return true; + } + + final BytecodeLazyAttributeInterceptor interceptor = extractLazyInterceptor( entity ); + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { + return ( (LazyAttributeLoadingInterceptor) interceptor ).isAttributeLoaded( attributeName ); + } + + return true; } @Override public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException { + return (LazyAttributeLoadingInterceptor) extractLazyInterceptor( entity ); + } + + @Override + public LazyAttributeLoadingInterceptor injectInterceptor( + Object entity, + Object identifier, + SharedSessionContractImplementor session) { + if ( !enhancedForLazyLoading ) { + throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); + } + + if ( !entityClass.isInstance( entity ) ) { + throw new IllegalArgumentException( + String.format( + "Passed entity instance [%s] is not of expected type [%s]", + entity, + getEntityName() + ) + ); + } + final LazyAttributeLoadingInterceptor interceptor = new LazyAttributeLoadingInterceptor( + getEntityName(), + identifier, + lazyAttributesMetadata.getLazyAttributeNames(), + session + ); + + injectInterceptor( entity, interceptor, session ); + + return interceptor; + } + + @Override + public void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session) { + injectInterceptor( + entity, + new EnhancementAsProxyLazinessInterceptor( + entityName, + identifierAttributeNames, + nonAggregatedCidMapper, + entityKey, + session + ), + session + ); + } + + @Override + public void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session) { + if ( !enhancedForLazyLoading ) { + throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); + } + + if ( !entityClass.isInstance( entity ) ) { + throw new IllegalArgumentException( + String.format( + "Passed entity instance [%s] is not of expected type [%s]", + entity, + getEntityName() + ) + ); + } + + ( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( interceptor ); + } + + @Override + public BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException { if ( !enhancedForLazyLoading ) { throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); } @@ -98,31 +221,7 @@ public class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhancementM return null; } - return (LazyAttributeLoadingInterceptor) interceptor; + return (BytecodeLazyAttributeInterceptor) interceptor; } - @Override - public LazyAttributeLoadingInterceptor injectInterceptor(Object entity, SharedSessionContractImplementor session) { - if ( !enhancedForLazyLoading ) { - throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); - } - - if ( !entityClass.isInstance( entity ) ) { - throw new IllegalArgumentException( - String.format( - "Passed entity instance [%s] is not of expected type [%s]", - entity, - getEntityName() - ) - ); - } - - final LazyAttributeLoadingInterceptor interceptor = new LazyAttributeLoadingInterceptor( - getEntityName(), - lazyAttributesMetadata.getLazyAttributeNames(), - session - ); - ( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( interceptor ); - return interceptor; - } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 98d34aba73..9bb4d7f1b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -8,6 +8,7 @@ package org.hibernate.tuple.entity; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -18,6 +19,7 @@ import java.util.Set; import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.OptimisticLockStyle; @@ -31,6 +33,7 @@ import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.spi.PersisterCreationContext; import org.hibernate.tuple.GenerationTiming; import org.hibernate.tuple.IdentifierProperty; import org.hibernate.tuple.InDatabaseValueGenerationStrategy; @@ -125,8 +128,8 @@ public class EntityMetamodel implements Serializable { public EntityMetamodel( PersistentClass persistentClass, EntityPersister persister, - SessionFactoryImplementor sessionFactory) { - this.sessionFactory = sessionFactory; + final PersisterCreationContext creationContext) { + this.sessionFactory = creationContext.getSessionFactory(); name = persistentClass.getEntityName(); rootName = persistentClass.getRootClass().getEntityName(); @@ -139,7 +142,37 @@ public class EntityMetamodel implements Serializable { versioned = persistentClass.isVersioned(); if ( persistentClass.hasPojoRepresentation() ) { - bytecodeEnhancementMetadata = BytecodeEnhancementMetadataPojoImpl.from( persistentClass ); + final Component identifierMapperComponent = persistentClass.getIdentifierMapper(); + final CompositeType nonAggregatedCidMapper; + final Set idAttributeNames; + + if ( identifierMapperComponent != null ) { + nonAggregatedCidMapper = (CompositeType) identifierMapperComponent.getType(); + idAttributeNames = new HashSet<>( ); + //noinspection unchecked + final Iterator propertyItr = identifierMapperComponent.getPropertyIterator(); + while ( propertyItr.hasNext() ) { + idAttributeNames.add( propertyItr.next() ); + } + } + else { + nonAggregatedCidMapper = null; + idAttributeNames = Collections.singleton( identifierAttribute.getName() ); + } + + bytecodeEnhancementMetadata = BytecodeEnhancementMetadataPojoImpl.from( + persistentClass, + idAttributeNames, + nonAggregatedCidMapper, + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(), + associatedEntityName -> { + final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); + if ( bootEntityDescriptor == null ) { + return false; + } + return bootEntityDescriptor.hasSubclasses(); + } + ); } else { bytecodeEnhancementMetadata = new BytecodeEnhancementMetadataNonPojoImpl( persistentClass.getEntityName() ); @@ -201,7 +234,14 @@ public class EntityMetamodel implements Serializable { sessionFactory, i, prop, - bytecodeEnhancementMetadata.isEnhancedForLazyLoading() + bytecodeEnhancementMetadata.isEnhancedForLazyLoading(), + associatedEntityName -> { + final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); + if ( bootEntityDescriptor == null ) { + return false; + } + return bootEntityDescriptor.hasSubclasses(); + } ); } @@ -217,10 +257,23 @@ public class EntityMetamodel implements Serializable { } // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - boolean lazy = prop.isLazy() && bytecodeEnhancementMetadata.isEnhancedForLazyLoading(); + boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + bytecodeEnhancementMetadata.isEnhancedForLazyLoading(), + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(), + associatedEntityName -> { + final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); + if ( bootEntityDescriptor == null ) { + return false; + } + return bootEntityDescriptor.hasSubclasses(); + } + ); + if ( lazy ) { hasLazy = true; } + propertyLaziness[i] = lazy; propertyNames[i] = properties[i].getName(); diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java index ecb6970cbc..f9a79e2e8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java @@ -15,6 +15,7 @@ import org.hibernate.HibernateException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.property.access.spi.Getter; +import org.hibernate.proxy.ProxyFactory; import org.hibernate.tuple.Tuplizer; /** @@ -273,4 +274,8 @@ public interface EntityTuplizer extends Tuplizer { * @return The getter for the version property. */ Getter getVersionGetter(); + + default ProxyFactory getProxyFactory() { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java index 47bd979d66..0910b6d9ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java @@ -46,6 +46,7 @@ public class PojoEntityInstantiator extends PojoInstantiator { PersistentAttributeInterceptor interceptor = new LazyAttributeLoadingInterceptor( entityMetamodel.getName(), + null, entityMetamodel.getBytecodeEnhancementMetadata() .getLazyAttributesMetadata() .getLazyAttributeNames(), diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java index f186e8a384..57c61115c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java @@ -16,7 +16,8 @@ import org.hibernate.EntityMode; import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.cfg.Environment; import org.hibernate.classic.Lifecycle; @@ -268,22 +269,14 @@ public class PojoEntityTuplizer extends AbstractEntityTuplizer { @Override public void afterInitialize(Object entity, SharedSessionContractImplementor session) { - - // moving to multiple fetch groups, the idea of `lazyPropertiesAreUnfetched` really - // needs to become either: - // 1) the names of all un-fetched fetch groups - // 2) the names of all fetched fetch groups - // probably (2) is best - // - // ultimately this comes from EntityEntry, although usage-search seems to show it is never updated there. - // - // also org.hibernate.persister.entity.AbstractEntityPersister.initializeLazyPropertiesFromDatastore() - // needs to be re-worked - if ( entity instanceof PersistentAttributeInterceptable ) { - final LazyAttributeLoadingInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractInterceptor( entity ); - if ( interceptor == null ) { - getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( entity, session ); + final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractLazyInterceptor( entity ); + if ( interceptor == null || interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( + entity, + getIdentifier( entity, session ), + session + ); } else { if ( interceptor.getLinkedSession() == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java index bb078f511f..59e1debf33 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -683,7 +683,8 @@ public abstract class EntityType extends AbstractType implements AssociationType getAssociatedEntityName(), id, eager, - isNullable() + isNullable(), + unwrapProxy ); if ( proxyOrEntity instanceof HibernateProxy ) { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java index c1cb4e0f09..9719a0f673 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java @@ -51,7 +51,7 @@ public class TestLazyPropertyOnPreUpdate extends BaseEntityManagerFunctionalTest @Before public void prepare() throws Exception { EntityPersister ep = entityManagerFactory().getMetamodel().entityPersister( EntityWithLazyProperty.class.getName() ); - assertTrue( ep.getInstrumentationMetadata().isEnhancedForLazyLoading() ); + assertTrue( ep.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ); byte[] testArray = new byte[]{0x2A}; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java index edae6baf78..fbe4ef03af 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java @@ -209,15 +209,6 @@ public class BasicEnhancementTest { public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { return WRITE_MARKER; } - - @Override - public Set getInitializedLazyAttributeNames() { - return null; - } - - @Override - public void attributeInitialized(String name) { - } } // --- // diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java index ed6e755a17..5a636b08ae 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java @@ -79,8 +79,10 @@ public class BidirectionalLazyTest extends BaseCoreFunctionalTestCase { doInHibernate( this::sessionFactory, session -> { Employer employer = session.get( Employer.class, "RedHat" ); + // Delete the associated entity first session.remove( employer ); + for ( Employee employee : employer.getEmployees() ) { assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); session.remove( employee ); @@ -188,6 +190,7 @@ public class BidirectionalLazyTest extends BaseCoreFunctionalTestCase { doInHibernate( this::sessionFactory, session -> { Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); // Get and delete an Employer that is not associated with employee Employer employer = session.get( Employer.class, "RedHat" ); @@ -196,15 +199,48 @@ public class BidirectionalLazyTest extends BaseCoreFunctionalTestCase { // employee.employer is uninitialized. Since the column for employee.employer // is a foreign key, and there is an Employer that has already been removed, // employee.employer will need to be iniitialized to determine if - // employee.employee is nullifiable. + // employee.employer is nullifiable. assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); session.remove( employee ); assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); } ); + } + /** + * @implSpec Same as {@link #testRemoveEntityWithNullLazyManyToOne} but + * deleting the Employer linked to the loaded Employee + */ + @Test + public void testRemoveEntityWithLinkedLazyManyToOne() { + inTransaction( + session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + employee.setEmployer( employer ); + session.persist( employee ); + } + ); + inTransaction( + session -> { + Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + // Get and delete an Employer that is not associated with employee + Employer employer = session.get( Employer.class, "RedHat" ); + session.remove( employer ); + + // employee.employer is uninitialized. Since the column for employee.employer + // is a foreign key, and there is an Employer that has already been removed, + // employee.employer will need to be iniitialized to determine if + // employee.employer is nullifiable. + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + } + ); } private void checkEntityEntryState( diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java new file mode 100644 index 0000000000..1e27611c5a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java @@ -0,0 +1,476 @@ +/* + * 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.test.bytecode.enhancement.lazy; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.StatelessSession; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.query.Query; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +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 org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class StatelessQueryScrollingTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testDynamicFetchScroll() { + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + + final Query query = statelessSession.createQuery( "from Task t join fetch t.resource join fetch t.user"); + final ScrollableResults scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY); + while ( scrollableResults.next() ) { + Task taskRef = (Task) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( taskRef ) ); + assertTrue( Hibernate.isInitialized( taskRef.getUser() ) ); + assertTrue( Hibernate.isInitialized( taskRef.getResource() ) ); + assertFalse( Hibernate.isInitialized( taskRef.getResource().getOwner() ) ); + } + } + + @Test + public void testDynamicFetchCollectionScroll() { + StatelessSession statelessSession = sessionFactory().openStatelessSession(); + statelessSession.beginTransaction(); + + final Query query = statelessSession.createQuery( "select p from Producer p join fetch p.products" ); + final ScrollableResults scrollableResults = query.scroll(ScrollMode.FORWARD_ONLY); + while ( scrollableResults.next() ) { + Producer producer = (Producer) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( producer ) ); + assertTrue( Hibernate.isPropertyInitialized( producer, "products" ) ); + assertTrue( Hibernate.isInitialized( producer.getProducts() ) ); + + for (Product product : producer.getProducts()) { + assertTrue( Hibernate.isInitialized( product ) ); + assertFalse( Hibernate.isInitialized( product.getVendor() ) ); + } + } + + statelessSession.getTransaction().commit(); + statelessSession.close(); + + } + + + @Before + public void createTestData() { + inTransaction( + session -> { + Date now = new Date(); + User me = new User( "me" ); + User you = new User( "you" ); + Resource yourClock = new Resource( "clock", you ); + Task task = new Task( me, "clean", yourClock, now ); // :) + + session.save( me ); + session.save( you ); + session.save( yourClock ); + session.save( task ); + + User u3 = new User( "U3" ); + User u4 = new User( "U4" ); + Resource it = new Resource( "it", u4 ); + Task task2 = new Task( u3, "beat", it, now ); // :)) + + session.save( u3 ); + session.save( u4 ); + session.save( it ); + session.save( task2 ); + } + ); + + inTransaction( + session -> { + Producer p1 = new Producer( 1, "Acme" ); + Producer p2 = new Producer( 2, "ABC" ); + + session.save( p1 ); + session.save( p2 ); + + Vendor v1 = new Vendor( 1, "v1" ); + Vendor v2 = new Vendor( 2, "v2" ); + + session.save( v1 ); + session.save( v2 ); + + final Product product1 = new Product(1, "123", v1, p1); + final Product product2 = new Product(2, "456", v1, p1); + final Product product3 = new Product(3, "789", v1, p2); + + session.save( product1 ); + session.save( product2 ); + session.save( product3 ); + } + ); + } + + @After + public void deleteTestData() { + inTransaction( + s -> { + s.createQuery( "delete Task" ).executeUpdate(); + s.createQuery( "delete Resource" ).executeUpdate(); + s.createQuery( "delete User" ).executeUpdate(); + + s.createQuery( "delete Product" ).executeUpdate(); + s.createQuery( "delete Producer" ).executeUpdate(); + s.createQuery( "delete Vendor" ).executeUpdate(); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( Task.class ); + sources.addAnnotatedClass( User.class ); + sources.addAnnotatedClass( Resource.class ); + sources.addAnnotatedClass( Product.class ); + sources.addAnnotatedClass( Producer.class ); + sources.addAnnotatedClass( Vendor.class ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Collection fetch scrolling + + @Entity( name = "Producer" ) + public static class Producer { + @Id + private Integer id; + + private String name; + + @OneToMany( mappedBy = "producer", fetch = FetchType.LAZY ) + private Set products = new HashSet<>(); + + public Producer() { + } + + public Producer(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } + } + + @Entity( name = "Product" ) + public static class Product { + @Id + private Integer id; + private String sku; + + @ManyToOne( fetch = FetchType.LAZY ) + private Vendor vendor; + + @ManyToOne( fetch = FetchType.LAZY ) + private Producer producer; + + public Product() { + } + + public Product(Integer id, String sku, Vendor vendor, Producer producer) { + this.id = id; + this.sku = sku; + this.vendor = vendor; + this.producer = producer; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSku() { + return sku; + } + + public void setSku(String sku) { + this.sku = sku; + } + + public Vendor getVendor() { + return vendor; + } + + public void setVendor(Vendor vendor) { + this.vendor = vendor; + } + + public Producer getProducer() { + return producer; + } + + public void setProducer(Producer producer) { + this.producer = producer; + } + } + + @Entity( name = "Vendor" ) + public static class Vendor { + @Id + private Integer id; + private String name; + + @OneToMany(mappedBy = "vendor", fetch = FetchType.LAZY ) + private Set products = new HashSet<>(); + + public Vendor() { + } + + public Vendor(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Entity fetch scrolling + + @Entity( name = "Resource" ) + public static class Resource { + @Id + @GeneratedValue( generator = "increment" ) + private Long id; + private String name; + @ManyToOne( fetch = FetchType.LAZY ) + private User owner; + + public Resource() { + } + + public Resource(String name, User owner) { + this.name = name; + this.owner = owner; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + } + + @Entity( name = "User" ) + public static class User { + @Id + @GeneratedValue( generator = "increment" ) + private Long id; + private String name; + + public User() { + } + + public User(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity( name = "Task" ) + public static class Task { + @Id + @GeneratedValue( generator = "increment" ) + private Long id; + private String description; + @ManyToOne( fetch = FetchType.LAZY) + private User user; + @ManyToOne( fetch = FetchType.LAZY) + private Resource resource; + private Date dueDate; + private Date startDate; + private Date completionDate; + + public Task() { + } + + public Task(User user, String description, Resource resource, Date dueDate) { + this( user, description, resource, dueDate, null, null ); + } + + public Task(User user, String description, Resource resource, Date dueDate, Date startDate, Date completionDate) { + this.user = user; + this.resource = resource; + this.description = description; + this.dueDate = dueDate; + this.startDate = startDate; + this.completionDate = completionDate; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Resource getResource() { + return resource; + } + + public void setResource(Resource resource) { + this.resource = resource; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getDueDate() { + return dueDate; + } + + public void setDueDate(Date dueDate) { + this.dueDate = dueDate; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getCompletionDate() { + return completionDate; + } + + public void setCompletionDate(Date completionDate) { + this.completionDate = completionDate; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java index baacfbb3ee..93a66e308c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java @@ -64,7 +64,7 @@ public class SimpleLazyGroupUpdateTest extends BaseCoreFunctionalTestCase { @Test public void test() { doInHibernate( this::sessionFactory, s -> { - TestEntity entity = s.load( TestEntity.class, 1L ); + TestEntity entity = s.get( TestEntity.class, 1L ); assertLoaded( entity, "name" ); assertNotLoaded( entity, "lifeStory" ); assertNotLoaded( entity, "reallyBigString" ); @@ -76,7 +76,7 @@ public class SimpleLazyGroupUpdateTest extends BaseCoreFunctionalTestCase { } ); doInHibernate( this::sessionFactory, s -> { - TestEntity entity = s.load( TestEntity.class, 1L ); + TestEntity entity = s.get( TestEntity.class, 1L ); assertLoaded( entity, "name" ); assertNotLoaded( entity, "lifeStory" ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java new file mode 100644 index 0000000000..ca1ccf7066 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java @@ -0,0 +1,130 @@ +/* + * 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 . + */ + +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +@Entity +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name = "PP_DCKey") +public abstract class AbstractKey extends ModelEntity + implements Serializable { + + @Column(name = "Name") + String name; + + @OneToMany(targetEntity = RoleEntity.class, mappedBy = "key", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("R") + protected Set roles = new LinkedHashSet<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PR") + @JoinColumn + protected AbstractKey register; + + @OneToMany(targetEntity = AbstractKey.class, mappedBy = "register", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("RK") + protected Set keys = new LinkedHashSet(); + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PP") + @JoinColumn + protected AbstractKey parent; + + @OneToMany(targetEntity = AbstractKey.class, mappedBy = "parent", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PK") + protected Set otherKeys = new LinkedHashSet(); + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set role) { + this.roles = role; + } + + public void addRole(RoleEntity role) { + this.roles.add( role ); + } + + public AbstractKey getRegister() { + return register; + } + + public void setRegister(AbstractKey register) { + this.register = register; + } + + public Set getKeys() { + return keys; + } + + public void setKeys(Set keys) { + this.keys = keys; + } + + public void addRegisterKey(AbstractKey registerKey) { + keys.add( registerKey ); + } + + public AbstractKey getParent() { + return parent; + } + + public void setParent(AbstractKey parent) { + this.parent = parent; + } + + public Set getOtherKeys() { + return otherKeys; + } + + public void setOtherKeys(Set otherKeys) { + this.otherKeys = otherKeys; + } + + public void addPanelKey(AbstractKey panelKey) { + this.otherKeys.add( panelKey ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java new file mode 100644 index 0000000000..7b8b45a640 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java @@ -0,0 +1,79 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Activity") +@Table(name = "activity") +@SuppressWarnings("WeakerAccess") +public class Activity extends BaseEntity { + private String description; + private Instruction instruction; + + protected WebApplication webApplication = null; + + /** + * Used by Hibernate + */ + @SuppressWarnings("unused") + public Activity() { + super(); + } + + public Activity(Integer id, String description, Instruction instruction) { + super( id ); + this.description = description; + this.instruction = instruction; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("Instruction") + @JoinColumn(name = "Instruction_Id") + public Instruction getInstruction() { + return instruction; + } + + @SuppressWarnings("unused") + public void setInstruction(Instruction instruction) { + this.instruction = instruction; + } + + @SuppressWarnings("unused") + @ManyToOne(fetch=FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("webApplication") + @JoinColumn(name="web_app_oid") + public WebApplication getWebApplication() { + return webApplication; + } + + public void setWebApplication(WebApplication webApplication) { + this.webApplication = webApplication; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java new file mode 100644 index 0000000000..360a042081 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java @@ -0,0 +1,49 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Address") +@Table(name = "address") +public class Address { + private Integer id; + + private String text; + + public Address() { + } + + public Address(Integer id, String text) { + this.id = id; + this.text = text; + } + + @Id + @Column(name = "oid") + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java new file mode 100644 index 0000000000..463d1bb7d6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java @@ -0,0 +1,45 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * @author Steve Ebersole + */ +@MappedSuperclass +public class BaseEntity { + protected Integer id; + protected String nbr; + + public BaseEntity() { + } + + public BaseEntity(Integer id) { + this.id = id; + } + + @Id + @Column( name = "oid" ) + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getNbr() { + return nbr; + } + + public void setNbr(String nbr) { + this.nbr = nbr; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java new file mode 100644 index 0000000000..fd91eb9cf1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java @@ -0,0 +1,364 @@ +/* + * 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 . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +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 org.junit.Assert.assertEquals; + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class BidirectionalProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testIt() { + inTransaction( + session -> { + for (BEntity b : session.createQuery("from BEntity b", BEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AChildEntity a = b.getA(); + assertEquals( 0, stats.getPrepareStatementCount() ); + a.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getStringField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getIntegerField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setIntegerField( 1 ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getEntries(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setVersion( new Short( "2" ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setStringField( "this is a string" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + AMappedSuperclass mappedSuperclass = a; + mappedSuperclass.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + } + ); + + inTransaction( + session -> { + for (BEntity b : session.createQuery("from BEntity b", BEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AChildEntity a = b.getA(); + assertEquals( "this is a string", a.getStringField() ); + assertEquals( 2, a.getVersion() ); + assertEquals( new Integer( 1 ), a.getIntegerField() ); + } + } + ); + + inTransaction( + session -> { + for (CEntity c : session.createQuery("from CEntity c", CEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AEntity a = c.getA(); + assertEquals( 0, stats.getPrepareStatementCount() ); + a.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getIntegerField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setIntegerField( 1 ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setVersion( new Short( "2" ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + } + ); + + inTransaction( + session -> { + for (CEntity c : session.createQuery("from CEntity c", CEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AEntity a = c.getA(); + assertEquals( 2, a.getVersion() ); + assertEquals( new Integer( 1 ), a.getIntegerField() ); + } + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( BEntity.class ); + sources.addAnnotatedClass( CEntity.class ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AChildEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AChildEntity a = new AChildEntity("a"); + BEntity b = new BEntity("b"); + b.setA(a); + session.persist(a); + session.persist(b); + + AChildEntity a1 = new AChildEntity("a1"); + CEntity c = new CEntity( "c" ); + c.setA( a1 ); + session.persist( a1 ); + session.persist( c ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from BEntity" ).executeUpdate(); + session.createQuery( "delete from CEntity" ).executeUpdate(); + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + @Entity(name="CEntity") + @Table(name="C") + public static class CEntity implements Serializable { + @Id + private String id; + + public CEntity(String id) { + this(); + setId(id); + } + + protected CEntity() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public void setA(AEntity a) { + aChildEntity = a; + a.getcEntries().add(this); + } + + public AEntity getA() { + return aChildEntity; + } + + @ManyToOne(fetch= FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("aEntity") + @JoinColumn(name="aEntity") + protected AEntity aChildEntity = null; + } + + @Entity(name="BEntity") + @Table(name="B") + public static class BEntity implements Serializable { + @Id + private String id; + + public BEntity(String id) { + this(); + setId(id); + } + + protected BEntity() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public void setA(AChildEntity a) { + aChildEntity = a; + a.getEntries().add(this); + } + + public AChildEntity getA() { + return aChildEntity; + } + + @ManyToOne(fetch= FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("aEntity") + @JoinColumn(name="aEntity") + protected AChildEntity aChildEntity = null; + } + + @MappedSuperclass + public static class AMappedSuperclass implements Serializable { + + @Id + private String id; + + @Basic + private short version; + + @Column(name = "INTEGER_FIELD") + private Integer integerField; + + public AMappedSuperclass(String id) { + setId(id); + } + + protected AMappedSuperclass() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(Integer integerField) { + this.integerField = integerField; + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + @Table(name="A") + public static class AEntity extends AMappedSuperclass { + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + @OneToMany(targetEntity=CEntity.class, mappedBy="aChildEntity", fetch=FetchType.LAZY) + protected Set cEntries = new LinkedHashSet(); + + public Set getcEntries() { + return cEntries; + } + + public void setcEntries(Set cEntries) { + this.cEntries = cEntries; + } + } + + @Entity(name="AChildEntity") + @Table(name="ACChild") + public static class AChildEntity extends AEntity { + + private String stringField; + + @OneToMany(targetEntity=BEntity.class, mappedBy="aChildEntity", fetch=FetchType.LAZY) + protected Set entries = new LinkedHashSet(); + + public AChildEntity(String id) { + super(id); + } + + protected AChildEntity() { + } + + public Set getEntries() { + return entries; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(String stringField) { + this.stringField = stringField; + } + + @Override + public Integer getIntegerField() { + return super.getIntegerField(); + } + + @Override + public void setIntegerField(Integer integerField) { + super.setIntegerField( integerField ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java new file mode 100644 index 0000000000..f0529f83e8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java @@ -0,0 +1,35 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "CreditCardPayment") +@Table(name = "credit_payment") +public class CreditCardPayment extends Payment { + private String transactionId; + + public CreditCardPayment() { + } + + public CreditCardPayment(Integer oid, Float amount, String transactionId) { + super( oid, amount ); + this.transactionId = transactionId; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java new file mode 100644 index 0000000000..a709fe1fea --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java @@ -0,0 +1,106 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Customer") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public abstract class Customer { + private Integer oid; + private String name; + private Set orders = new HashSet<>(); + + private Address address; + + private Customer parentCustomer; + private Set childCustomers = new HashSet<>(); + + public Customer() { + } + + public Customer(Integer oid, String name, Address address, Customer parentCustomer) { + this.oid = oid; + this.name = name; + this.address = address; + this.parentCustomer = parentCustomer; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "customer") + public Set getOrders() { + return orders; + } + + public void setOrders(Set orders) { + this.orders = orders; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.NO_PROXY) + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.NO_PROXY) + public Customer getParentCustomer() { + return parentCustomer; + } + + public void setParentCustomer(Customer parentCustomer) { + this.parentCustomer = parentCustomer; + } + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentCustomer") + public Set getChildCustomers() { + return childCustomers; + } + + public void setChildCustomers(Set childCustomers) { + this.childCustomers = childCustomers; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java new file mode 100644 index 0000000000..9eb37212be --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java @@ -0,0 +1,35 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "DebitCardPayment") +@Table(name = "debit_payment") +public class DebitCardPayment extends Payment { + private String transactionId; + + public DebitCardPayment() { + } + + public DebitCardPayment(Integer oid, Float amount, String transactionId) { + super( oid, amount ); + this.transactionId = transactionId; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java new file mode 100644 index 0000000000..6b522ce761 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java @@ -0,0 +1,629 @@ +/* + * 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 . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; + +import org.hibernate.Hibernate; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class DeepInheritanceProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testRootGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity ) ); + aaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + aaaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAEntity( false ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAAAEntity( 4 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AAEntity.class ); + sources.addAnnotatedClass( AAAEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AEntity aEntity = new AEntity( "AEntity" ); + aEntity.setFieldInAMappedSuperclass( (short) 2 ); + aEntity.setFieldInAEntity( true ); + session.persist( aEntity ); + + AAEntity aaEntity = new AAAEntity( "AAEntity" ); + aaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaEntity.setFieldInAEntity( true ); + aaEntity.setFieldInAAEntity( "field in AAEntity" ); + session.persist( aaEntity ); + + AAAEntity aaaEntity = new AAAEntity( "AAAEntity" ); + aaaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaaEntity.setFieldInAEntity( true ); + aaaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaaEntity.setFieldInAAAEntity( 3 ); + session.persist( aaaEntity ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + @MappedSuperclass + public static class AMappedSuperclass implements Serializable { + + @Id + private String id; + + private short fieldInAMappedSuperclass; + + public short getFieldInAMappedSuperclass() { + return fieldInAMappedSuperclass; + } + + public void setFieldInAMappedSuperclass(short fieldInAMappedSuperclass) { + this.fieldInAMappedSuperclass = fieldInAMappedSuperclass; + } + + public AMappedSuperclass(String id) { + this.id = id; + } + + protected AMappedSuperclass() { + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class AEntity extends AMappedSuperclass { + + private Boolean fieldInAEntity; + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + public Boolean getFieldInAEntity() { + return fieldInAEntity; + } + public void setFieldInAEntity(Boolean fieldInAEntity) { + this.fieldInAEntity = fieldInAEntity; + } + } + + @Entity(name="AAEntity") + public static class AAEntity extends AEntity { + + private String fieldInAAEntity; + + public AAEntity(String id) { + super(id); + } + + protected AAEntity() { + } + + public String getFieldInAAEntity() { + return fieldInAAEntity; + } + + public void setFieldInAAEntity(String fieldInAAEntity) { + this.fieldInAAEntity = fieldInAAEntity; + } + } + + @Entity(name="AAAEntity") + public static class AAAEntity extends AAEntity { + + private long fieldInAAAEntity; + + public AAAEntity(String id) { + super(id); + } + + protected AAAEntity() { + } + + public long getFieldInAAAEntity() { + return fieldInAAAEntity; + } + + public void setFieldInAAAEntity(long fieldInAAAEntity) { + this.fieldInAAAEntity = fieldInAAAEntity; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java new file mode 100644 index 0000000000..5b1e466e3b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java @@ -0,0 +1,1514 @@ +/* + * 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 . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; + +import org.hibernate.Hibernate; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class DeepInheritanceWithNonEntitiesProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testRootGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.getFieldInNonEntityAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( (short) 5 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 5, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + assertEquals( 6, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.setFieldInNonEntityAEntitySuperclass( 10L ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 10 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + assertEquals( 6, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property + aaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( (short) 10 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 10, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 4 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAAEntitySuperclass( "xyz" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "xyz", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity only gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAAAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAEntity( false ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAAAEntity( 4 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 99 ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 99, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAEntitySuperclass( 10L ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 10 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "xyz" ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "xyz", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + // aaaEntity only gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( true ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( true, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AAEntity.class ); + sources.addAnnotatedClass( AAAEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AEntity aEntity = new AEntity( "AEntity" ); + aEntity.setFieldInAMappedSuperclass( (short) 2 ); + aEntity.setFieldInAEntity( true ); + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + session.persist( aEntity ); + + AAEntity aaEntity = new AAAEntity( "AAEntity" ); + aaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaEntity.setFieldInAEntity( true ); + aaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "abc" ); + session.persist( aaEntity ); + + AAAEntity aaaEntity = new AAAEntity( "AAAEntity" ); + aaaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaaEntity.setFieldInAEntity( true ); + aaaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaaEntity.setFieldInAAAEntity( 3 ); + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "abc" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( true ); + session.persist( aaaEntity ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + public static class NonEntityAMappedSuperclassSuperclass { + private int fieldInNonEntityAMappedSuperclassSuperclass; + + public int getFieldInNonEntityAMappedSuperclassSuperclass() { + return fieldInNonEntityAMappedSuperclassSuperclass; + } + + public void setFieldInNonEntityAMappedSuperclassSuperclass(int fieldInNonEntityAMappedSuperclassSuperclass) { + this.fieldInNonEntityAMappedSuperclassSuperclass = fieldInNonEntityAMappedSuperclassSuperclass; + } + } + + @MappedSuperclass + public static class AMappedSuperclass extends NonEntityAMappedSuperclassSuperclass implements Serializable { + + @Id + private String id; + + private short fieldInAMappedSuperclass; + + public short getFieldInAMappedSuperclass() { + return fieldInAMappedSuperclass; + } + + public void setFieldInAMappedSuperclass(short fieldInAMappedSuperclass) { + this.fieldInAMappedSuperclass = fieldInAMappedSuperclass; + } + + public AMappedSuperclass(String id) { + this.id = id; + } + + protected AMappedSuperclass() { + } + } + + public static class NonEntityAEntitySuperclass extends AMappedSuperclass { + + private Long fieldInNonEntityAEntitySuperclass; + + public NonEntityAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAEntitySuperclass() { + } + + public Long getFieldInNonEntityAEntitySuperclass() { + return fieldInNonEntityAEntitySuperclass; + } + + public void setFieldInNonEntityAEntitySuperclass(Long fieldInNonEntityAEntitySuperclass) { + this.fieldInNonEntityAEntitySuperclass = fieldInNonEntityAEntitySuperclass; + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class AEntity extends NonEntityAEntitySuperclass { + + private Boolean fieldInAEntity; + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + public Boolean getFieldInAEntity() { + return fieldInAEntity; + } + public void setFieldInAEntity(Boolean fieldInAEntity) { + this.fieldInAEntity = fieldInAEntity; + } + } + + public static class NonEntityAAEntitySuperclass extends AEntity { + + private String fieldInNonEntityAAEntitySuperclass; + + public NonEntityAAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAAEntitySuperclass() { + } + + public String getFieldInNonEntityAAEntitySuperclass() { + return fieldInNonEntityAAEntitySuperclass; + } + + public void setFieldInNonEntityAAEntitySuperclass(String fieldInNonEntityAAEntitySuperclass) { + this.fieldInNonEntityAAEntitySuperclass = fieldInNonEntityAAEntitySuperclass; + } + } + + @Entity(name="AAEntity") + public static class AAEntity extends NonEntityAAEntitySuperclass { + + private String fieldInAAEntity; + + public AAEntity(String id) { + super(id); + } + + protected AAEntity() { + } + + public String getFieldInAAEntity() { + return fieldInAAEntity; + } + + public void setFieldInAAEntity(String fieldInAAEntity) { + this.fieldInAAEntity = fieldInAAEntity; + } + } + + public static class NonEntityAAAEntitySuperclass extends AAEntity { + + private Boolean fieldInNonEntityAAAEntitySuperclass; + + public NonEntityAAAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAAAEntitySuperclass() { + } + + public Boolean getFieldInNonEntityAAAEntitySuperclass() { + return fieldInNonEntityAAAEntitySuperclass; + } + + public void setFieldInNonEntityAAAEntitySuperclass(Boolean fieldInNonEntityAAAEntitySuperclass) { + this.fieldInNonEntityAAAEntitySuperclass = fieldInNonEntityAAAEntitySuperclass; + } + } + + @Entity(name="AAAEntity") + public static class AAAEntity extends NonEntityAAAEntitySuperclass { + + private long fieldInAAAEntity; + + public AAAEntity(String id) { + super(id); + } + + protected AAAEntity() { + } + + public long getFieldInAAAEntity() { + return fieldInAAAEntity; + } + + public void setFieldInAAAEntity(long fieldInAAAEntity) { + this.fieldInAAAEntity = fieldInAAAEntity; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java new file mode 100644 index 0000000000..8647b578e4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java @@ -0,0 +1,40 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "DomesticCustomer") +@Table(name = "domestic_cust") +public class DomesticCustomer extends Customer { + private String taxId; + + public DomesticCustomer() { + } + + public DomesticCustomer( + Integer oid, + String name, + String taxId, + Address address, + Customer parentCustomer) { + super( oid, name, address, parentCustomer ); + this.taxId = taxId; + } + + public String getTaxId() { + return taxId; + } + + public void setTaxId(String taxId) { + this.taxId = taxId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java new file mode 100644 index 0000000000..1794cb01fd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java @@ -0,0 +1,252 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class EntitySharedInCollectionAndToOneTest extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void testIt() { + inTransaction( + session -> { + int passes = 0; + for ( CodeTable codeTable : session.createQuery( "from CodeTable ct where ct.id = 2", CodeTable.class ).list() ) { + assert 0 == passes; + passes++; + Hibernate.initialize( codeTable.getCodeTableItems() ); + } + + assertThat( session.getPersistenceContext().getNumberOfManagedEntities(), is( 2 ) ); + } + ); + } + + @Before + public void createTestData() { + inTransaction( + session -> { + final CodeTable codeTable1 = new CodeTable( 1, 1 ); + final CodeTableItem item1 = new CodeTableItem( 1, 1, "first" ); + final CodeTableItem item2 = new CodeTableItem( 2, 1, "second" ); + final CodeTableItem item3 = new CodeTableItem( 3, 1, "third" ); + + session.save( codeTable1 ); + session.save( item1 ); + session.save( item2 ); + session.save( item3 ); + + codeTable1.getCodeTableItems().add( item1 ); + item1.setCodeTable( codeTable1 ); + + codeTable1.getCodeTableItems().add( item2 ); + item2.setCodeTable( codeTable1 ); + + codeTable1.getCodeTableItems().add( item3 ); + item3.setCodeTable( codeTable1 ); + + codeTable1.setDefaultItem( item1 ); + item1.setDefaultItemInverse( codeTable1 ); + + final CodeTable codeTable2 = new CodeTable( 2, 1 ); + final CodeTableItem item4 = new CodeTableItem( 4, 1, "fourth" ); + + session.save( codeTable2 ); + session.save( item4 ); + + codeTable2.getCodeTableItems().add( item4 ); + item4.setCodeTable( codeTable2 ); + + codeTable2.setDefaultItem( item4 ); + item4.setDefaultItemInverse( codeTable2 ); + } + ); + } + +// @After +// public void deleteTestData() { +// inTransaction( +// session -> { +// for ( CodeTable codeTable : session.createQuery( "from CodeTable", CodeTable.class ).list() ) { +// session.delete( codeTable ); +// } +// } +// ); +// } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( CodeTableItem.class ); + sources.addAnnotatedClass( CodeTable.class ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + private Integer oid; + private int version; + + public BaseEntity() { + } + + public BaseEntity(Integer oid, int version) { + this.oid = oid; + this.version = version; + } + + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + } + + @Entity( name = "CodeTable" ) + @Table( name = "code_table" ) + public static class CodeTable extends BaseEntity { + @OneToOne( fetch = FetchType.LAZY ) + @LazyGroup( "defaultCodeTableItem" ) + @JoinColumn( name = "default_code_id" ) + private CodeTableItem defaultItem; + + @OneToMany( mappedBy = "codeTable" ) + private Set codeTableItems = new HashSet<>(); + + public CodeTable() { + } + + public CodeTable(Integer oid, int version) { + super( oid, version ); + } + + public CodeTableItem getDefaultItem() { + return defaultItem; + } + + public void setDefaultItem(CodeTableItem defaultItem) { + this.defaultItem = defaultItem; + } + + public Set getCodeTableItems() { + return codeTableItems; + } + + public void setCodeTableItems(Set codeTableItems) { + this.codeTableItems = codeTableItems; + } + } + + @Entity( name = "CodeTableItem" ) + @Table( name = "code_table_item" ) + public static class CodeTableItem extends BaseEntity { + private String name; + + @ManyToOne( fetch = FetchType.LAZY ) + @LazyGroup( "codeTable" ) + @JoinColumn( name = "code_table_oid" ) + private CodeTable codeTable; + + @OneToOne( mappedBy = "defaultItem", fetch=FetchType.LAZY ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + @LazyGroup( "defaultItemInverse" ) + protected CodeTable defaultItemInverse; + + + public CodeTableItem() { + } + + public CodeTableItem(Integer oid, int version, String name) { + super( oid, version ); + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public CodeTable getCodeTable() { + return codeTable; + } + + public void setCodeTable(CodeTable codeTable) { + this.codeTable = codeTable; + } + + public CodeTable getDefaultItemInverse() { + return defaultItemInverse; + } + + public void setDefaultItemInverse(CodeTable defaultItemInverse) { + this.defaultItemInverse = defaultItemInverse; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java new file mode 100644 index 0000000000..f3d38c16eb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java @@ -0,0 +1,926 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.sql.Blob; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollableResults; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +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 org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class FetchGraphTest extends BaseNonConfigCoreFunctionalTestCase { + + + @Test + public void testLoadNonOwningOneToOne() { + // Test loading D and accessing E + // E is the owner of the FK, not D. When `D#e` is accessed we + // need to actually load E because its table has the FK value, not + // D's table + + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final DEntity entityD = session.load( DEntity.class, 1L ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + + entityD.getE(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isInitialized( entityD.getA() ); + assert Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isInitialized( entityD.getC() ); + assert Hibernate.isPropertyInitialized( entityD, "e" ); + assert Hibernate.isInitialized( entityD.getE() ); + } + ); + } + + @Test + public void testLoadOwningOneToOne() { + // switch it around + + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final EEntity entityE = session.load( EEntity.class, 17L ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + assert !Hibernate.isPropertyInitialized( entityE, "d" ); + + final DEntity entityD = entityE.getD(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + } + ); + } + + @Test + public void testFetchingScroll() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + + inStatelessSession( + session -> { + final String qry = "select e from E e join fetch e.d"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + + inStatelessSession( + session -> { + final String qry = "select d from D d " + + "join fetch d.a " + + "join fetch d.bs " + + "join fetch d.c " + + "join fetch d.e " + + "join fetch d.g"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + + inStatelessSession( + session -> { + final String qry = "select g from G g join fetch g.dEntities"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + } + + + @Test + public void testLazyAssociationSameAsNonLazyInPC() { + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getInstrumentationMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final AEntity entityA = session.get( AEntity.class, 1L ); + + final DEntity entityD = session.load( DEntity.class, 1L ); + assert !Hibernate.isInitialized( entityD ); + Hibernate.initialize( entityD ); + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert entityA.getOid() == entityD.getA().getOid(); + assert session.getPersistenceContext().getEntry( entityA ) == + session.getPersistenceContext().getEntry( entityD.getA() ); + assert entityA == entityD.getA(); + } + ); + } + + @Test + public void testRandomAccess() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + final EntityPersister persister = sessionFactory().getMetamodel().entityPersister( DEntity.class ); + assert persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + + inSession( + session -> { + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Load a D + + final DEntity entityD = session.load( DEntity.class, 1L ); + + assertThat( entityD instanceof HibernateProxy, is(false) ); + assertThat( entityD instanceof PersistentAttributeInterceptable, is(true) ); + assertThat( Hibernate.isInitialized( entityD ), is(false) ); + // Because D is enhanced we should not have executed any SQL + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + // access the id. + // -since entityD is a "enhanced proxy", this should not trigger loading + assertThat( entityD.getOid(), is(1L) ); + assertThat( Hibernate.isInitialized( entityD ), is(false) ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + + // Because D is enhanced we should not have executed any SQL + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + assert !Hibernate.isPropertyInitialized( entityD, "g" ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access C + + final CEntity c = entityD.getC(); + + // make sure interception happened + assertThat( c, notNullValue() ); + + // See `#testLoadNonOwningOneToOne` + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + // The fields themselves are initialized - set to the + // enhanced entity "proxy" instance + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + + assert !Hibernate.isInitialized( entityD.getA() ); + assert !Hibernate.isInitialized( entityD.getC() ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access C again + + entityD.getC(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access E + + final EEntity e1 = entityD.getE(); + assert Hibernate.isPropertyInitialized( entityD, "e" ); + + assert Hibernate.isInitialized( entityD.getE() ); + + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + assert Hibernate.isInitialized( e1 ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access E again + + entityD.getE(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + assertThat( entityD.getE().getOid(), is(17L) ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // now lets access the attribute "proxies" + + // this will load the table C data + entityD.getC().getC1(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + assert Hibernate.isInitialized( entityD.getC() ); + + // this should not - it was already loaded above + entityD.getE().getE1(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + + Set bs = entityD.getBs(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + assertThat( bs.size(), is( 2 ) ); + assertThat( stats.getPrepareStatementCount(), is( 4L ) ); + + entityD.getG().getOid(); + assertThat( stats.getPrepareStatementCount(), is( 4L ) ); + } + ); + } + + @Test + public void testNullManyToOneHql() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + final String qry = "select e from Activity e"; + final List activities = session.createQuery( qry, Activity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + long expectedCount = 1L; + + for ( Activity activity : activities ) { + if ( activity.getInstruction() != null ) { + // do something special + // - here we just access an attribute to trigger + // the initialization of the association + + activity.getInstruction().getSummary(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + } + + if ( activity.getWebApplication() != null ) { + // trigger base group initialization + activity.getWebApplication().getName(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + // trigger initialization + activity.getWebApplication().getSiteUrl(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + } + } + } + ); + } + + @Test + public void testAbstractClassAssociation() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( RoleEntity.class ) + .getInstrumentationMetadata() + .isEnhancedForLazyLoading(); + + inTransaction( + session -> { + final String qry = "select e from RoleEntity e"; + final List keyRoleEntities = session.createQuery( qry, RoleEntity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + for ( RoleEntity keyRoleEntity : keyRoleEntities ) { + Object key = Hibernate.unproxy( keyRoleEntity.getKey() ); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + Set specializedEntities = ( (SpecializedKey) key ) + .getSpecializedEntities(); + + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + Iterator iterator = specializedEntities.iterator(); + while ( iterator.hasNext() ) { + SpecializedEntity specializedEntity = iterator.next(); + assertThat( specializedEntity.getId(), notNullValue() ); + specializedEntity.getValue(); + } + + // but regardless there should not be an additional query + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + } + } + ); + } + + @Test + public void testNonAbstractAssociationWithSubclassValue() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + final String qry = "select e from RoleEntity e"; + final List keyRoleEntities = session.createQuery( qry, RoleEntity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + assertThat( keyRoleEntities.size(), is( 1 ) ); + + RoleEntity roleEntity = keyRoleEntities.get( 0 ); + assertThat( + Hibernate.unproxy( roleEntity.getKey() ).getClass().getName(), + is( SpecializedKey.class.getName() ) + ); + + assertThat( + Hibernate.unproxy( roleEntity.getSpecializedKey() ).getClass().getName(), + is( MoreSpecializedKey.class.getName() ) + ); + } + ); + } + + @Test + public void testQueryAndDeleteDEntity() { + inTransaction( + session -> { + List result = session.createQuery( + "select d from D d ", + DEntity.class + ).list(); + result.forEach( entity -> { + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + + } ); + } + ); + } + + @Test + public void testLoadAndDeleteDEntity() { + inTransaction( + session -> { + DEntity entity = session.load( DEntity.class, 1L ); + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + } + ); + } + + @Test + public void testGetAndDeleteDEntity() { + inTransaction( + session -> { + DEntity entity = session.get( DEntity.class, 1L ); + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + } + ); + } + + @Test + public void testGetAndDeleteEEntity() { + inTransaction( + session -> { + EEntity entity = session.get( EEntity.class, 17L ); + session.delete( entity ); + session.delete( entity.getD() ); + } + ); + } + + @Test + public void testLoadAndDeleteEEntity() { + inTransaction( + session -> { + EEntity entity = session.load( EEntity.class, 17L ); + session.delete( entity ); + session.delete( entity.getD() ); + } + ); + } + + @Test + public void testQueryAndDeleteEEntity() { + inTransaction( + session -> { + List result = session.createQuery( + "select e from E e", + EEntity.class + ).list(); + result.forEach( entity -> { + session.delete( entity ); + session.delete( entity.getD() ); + } ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( BEntity.class ); + sources.addAnnotatedClass( CEntity.class ); + sources.addAnnotatedClass( DEntity.class ); + sources.addAnnotatedClass( EEntity.class ); + sources.addAnnotatedClass( GEntity.class ); + + sources.addAnnotatedClass( Activity.class ); + sources.addAnnotatedClass( Instruction.class ); + sources.addAnnotatedClass( WebApplication.class ); + + sources.addAnnotatedClass( SpecializedKey.class ); + sources.addAnnotatedClass( MoreSpecializedKey.class ); + sources.addAnnotatedClass( RoleEntity.class ); + sources.addAnnotatedClass( AbstractKey.class ); + sources.addAnnotatedClass( GenericKey.class ); + sources.addAnnotatedClass( SpecializedEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + DEntity d = new DEntity(); + d.setD( "bla" ); + d.setOid( 1 ); + + byte[] lBytes = "agdfagdfagfgafgsfdgasfdgfgasdfgadsfgasfdgasfdgasdasfdg".getBytes(); + Blob lBlob = Hibernate.getLobCreator( session ).createBlob( lBytes ); + d.setBlob( lBlob ); + + BEntity b1 = new BEntity(); + b1.setOid( 1 ); + b1.setB1( 34 ); + b1.setB2( "huhu" ); + + BEntity b2 = new BEntity(); + b2.setOid( 2 ); + b2.setB1( 37 ); + b2.setB2( "haha" ); + + Set lBs = new HashSet<>(); + lBs.add( b1 ); + lBs.add( b2 ); + d.setBs( lBs ); + + AEntity a = new AEntity(); + a.setOid( 1 ); + a.setA( "hihi" ); + d.setA( a ); + + EEntity e = new EEntity(); + e.setOid( 17 ); + e.setE1( "Balu" ); + e.setE2( "Bär" ); + + e.setD( d ); + d.setE( e ); + + CEntity c = new CEntity(); + c.setOid( 1 ); + c.setC1( "ast" ); + c.setC2( "qwert" ); + c.setC3( "yxcv" ); + d.setC( c ); + + GEntity g = new GEntity(); + g.setOid( 1 ); + g.getdEntities().add( d ); + d.setG( g ); + + + session.save( b1 ); + session.save( b2 ); + session.save( a ); + session.save( c ); + session.save( g ); + session.save( d ); + session.save( e ); + + + // create a slew of Activity objects, some with Instruction reference + // some without. + + for ( int i = 0; i < 30; i++ ) { + final Activity activity = new Activity( i, "Activity #" + i, null ); + if ( i % 2 == 0 ) { + final Instruction instr = new Instruction( i, "Instruction #" + i ); + activity.setInstruction( instr ); + session.save( instr ); + } + else { + final WebApplication webApplication = new WebApplication( i, "http://" + i + ".com" ); + activity.setWebApplication( webApplication ); + webApplication.getActivities().add( activity ); + session.save( webApplication ); + } + + session.save( activity ); + } + + RoleEntity roleEntity = new RoleEntity(); + roleEntity.setOid( 1L ); + + SpecializedKey specializedKey = new SpecializedKey(); + specializedKey.setOid(1L); + + MoreSpecializedKey moreSpecializedKey = new MoreSpecializedKey(); + moreSpecializedKey.setOid( 3L ); + + SpecializedEntity specializedEntity = new SpecializedEntity(); + specializedEntity.setId( 2L ); + specializedKey.addSpecializedEntity( specializedEntity ); + specializedEntity.setSpecializedKey( specializedKey); + + specializedKey.addRole( roleEntity ); + roleEntity.setKey( specializedKey ); + roleEntity.setSpecializedKey( moreSpecializedKey ); + moreSpecializedKey.addRole( roleEntity ); + session.save( specializedEntity ); + session.save( roleEntity ); + session.save( specializedKey ); + session.save( moreSpecializedKey ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from E" ).executeUpdate(); + session.createQuery( "delete from D" ).executeUpdate(); + session.createQuery( "delete from C" ).executeUpdate(); + session.createQuery( "delete from B" ).executeUpdate(); + session.createQuery( "delete from A" ).executeUpdate(); + session.createQuery( "delete from G" ).executeUpdate(); + + session.createQuery( "delete from Activity" ).executeUpdate(); + session.createQuery( "delete from Instruction" ).executeUpdate(); + session.createQuery( "delete from WebApplication" ).executeUpdate(); + + session.createQuery( "delete from SpecializedEntity" ).executeUpdate(); + session.createQuery( "delete from RoleEntity" ).executeUpdate(); + session.createQuery( "delete from MoreSpecializedKey" ).executeUpdate(); + session.createQuery( "delete from SpecializedKey" ).executeUpdate(); + session.createQuery( "delete from GenericKey" ).executeUpdate(); + session.createQuery( "delete from AbstractKey" ).executeUpdate(); + } + ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + private long oid; + private short version; + + public long getOid() { + return oid; + } + + public void setOid(long oid) { + this.oid = oid; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + } + + @Entity(name = "A") + @Table(name = "A") + public static class AEntity extends BaseEntity { + @Column(name = "A") + private String a; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + } + + + @Entity(name = "B") + @Table(name = "B") + public static class BEntity extends BaseEntity { + private Integer b1; + private String b2; + + public Integer getB1() { + return b1; + } + + public void setB1(Integer b1) { + this.b1 = b1; + } + + public String getB2() { + return b2; + } + + public void setB2(String b2) { + this.b2 = b2; + } + } + + + @Entity(name = "C") + @Table(name = "C") + public static class CEntity extends BaseEntity { + private String c1; + private String c2; + private String c3; + private Long c4; + + public String getC1() { + return c1; + } + + public void setC1(String c1) { + this.c1 = c1; + } + + public String getC2() { + return c2; + } + + @Basic(fetch = FetchType.LAZY) + public void setC2(String c2) { + this.c2 = c2; + } + + public String getC3() { + return c3; + } + + public void setC3(String c3) { + this.c3 = c3; + } + + public Long getC4() { + return c4; + } + + public void setC4(Long c4) { + this.c4 = c4; + } + } + + @Entity(name = "D") + @Table(name = "D") + public static class DEntity extends BaseEntity { + private String d; + + // ****** Relations ***************** + @OneToOne(fetch = FetchType.LAZY) +// @LazyToOne(LazyToOneOption.PROXY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("a") + public AEntity a; + + @OneToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) +// @LazyToOne(LazyToOneOption.PROXY) + @LazyGroup("c") + public CEntity c; + + @OneToMany(targetEntity = BEntity.class) + public Set bs; + + @OneToOne(mappedBy = "d", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("e") + private EEntity e; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn() + @LazyGroup("g") + public GEntity g; + + @Lob + @Basic(fetch = FetchType.LAZY) + @LazyGroup("blob") + private Blob blob; + + public String getD() { + return d; + } + + public void setD(String d) { + this.d = d; + } + + + public AEntity getA() { + return a; + } + + public void setA(AEntity a) { + this.a = a; + } + + public Set getBs() { + return bs; + } + + public void setBs(Set bs) { + this.bs = bs; + } + + public CEntity getC() { + return c; + } + + public void setC(CEntity c) { + this.c = c; + } + + public Blob getBlob() { + return blob; + } + + public void setBlob(Blob blob) { + this.blob = blob; + } + + public EEntity getE() { + return e; + } + + public void setE(EEntity e) { + this.e = e; + } + + public GEntity getG() { + return g; + } + + public void setG(GEntity g) { + this.g = g; + } + } + + @Entity(name = "E") + @Table(name = "E") + public static class EEntity extends BaseEntity { + private String e1; + private String e2; + + @OneToOne(fetch = FetchType.LAZY) + private DEntity d; + + public String getE1() { + return e1; + } + + public void setE1(String e1) { + this.e1 = e1; + } + + public String getE2() { + return e2; + } + + public void setE2(String e2) { + this.e2 = e2; + } + + public DEntity getD() { + return d; + } + + public void setD(DEntity d) { + this.d = d; + } + } + + @Entity(name = "G") + @Table(name = "G") + public static class GEntity extends BaseEntity { + + @OneToMany(mappedBy = "g") + public Set dEntities = new HashSet<>(); + + public Set getdEntities() { + return dEntities; + } + + public void setdEntities(Set dEntities) { + this.dEntities = dEntities; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java new file mode 100644 index 0000000000..a9b0c6acc3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java @@ -0,0 +1,48 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "ForeignCustomer") +@Table(name = "foreign_cust") +public class ForeignCustomer extends Customer { + private String vat; + + public ForeignCustomer() { + } + + public ForeignCustomer( + Integer oid, + String name, + Address address, + String vat, + Customer parentCustomer) { + super( oid, name, address, parentCustomer ); + this.vat = vat; + } + + public ForeignCustomer( + Integer oid, + String name, + Address address, + String vat) { + this( oid, name, address, vat, null ); + } + + public String getVat() { + return vat; + } + + public void setVat(String vat) { + this.vat = vat; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java new file mode 100644 index 0000000000..6f52802abf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java @@ -0,0 +1,27 @@ +/* + * 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 . + */ + +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +@Entity(name="GenericKey") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name="PP_GenericDCKey") +public abstract class GenericKey extends AbstractKey implements Serializable { + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java new file mode 100644 index 0000000000..56d594b368 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java @@ -0,0 +1,41 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Instruction") +@Table(name = "instruction") +public class Instruction extends BaseEntity { + private String summary; + + /** + * Used by Hibernate + */ + @SuppressWarnings("unused") + public Instruction() { + super(); + } + + @SuppressWarnings("WeakerAccess") + public Instruction(Integer id, String summary) { + super( id ); + this.summary = summary; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceAllowProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceAllowProxyTest.java new file mode 100644 index 0000000000..535a3b5296 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceAllowProxyTest.java @@ -0,0 +1,297 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +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 org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +/** + * @author Steve Ebersole + */ + +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions( lazyLoading = true ) +public class LazyGroupWithInheritanceAllowProxyTest extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void baseline() { + inTransaction( + session -> { + final List orders = session.createQuery( "select o from Order o", Order.class ).list(); + for ( Order order : orders ) { + if ( order.getCustomer().getOid() == null ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + } + + } + + } + ); + } + + @Test + public void testMergingUninitializedProxy() { + inTransaction( + session -> { + final List orders = session.createQuery( "select o from Order o", Order.class ).list(); + for ( Order order : orders ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + assertThat( order.getCustomer().getAddress(), notNullValue() ); + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) order.getCustomer().getAddress(); + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + } + } + ); + } + + + @Test + public void queryEntityWithAssociationToAbstract() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final AtomicInteger expectedQueryCount = new AtomicInteger( 0 ); + + inTransaction( + session -> { + final List orders = session.createQuery( "select o from Order o", Order.class ).list(); + + expectedQueryCount.set( 1 ); + + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + for ( Order order : orders ) { + System.out.println( "############################################" ); + System.out.println( "Starting Order #" + order.getOid() ); + + // accessing the many-to-one's id should not trigger a load + if ( order.getCustomer().getOid() == null ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + } + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // accessing the one-to-many should trigger a load + final Set orderPayments = order.getPayments(); + System.out.println( "Number of payments = " + orderPayments.size() ); + expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // access the non-inverse, logical 1-1 + order.getSupplemental(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental() != null ) { + System.out.println( "Got Order#supplemental = " + order.getSupplemental().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + + // access the inverse, logical 1-1 + order.getSupplemental2(); + expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental2() != null ) { + System.out.println( "Got Order#supplemental2 = " + order.getSupplemental2().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + } + } + ); + } + + /** + * Same test as {@link #queryEntityWithAssociationToAbstract()}, but using runtime + * fetching to issues just a single select + */ + @Test + public void queryEntityWithAssociationToAbstractRuntimeFetch() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final AtomicInteger expectedQueryCount = new AtomicInteger( 0 ); + + inTransaction( + session -> { + final String qry = "select o from Order o join fetch o.customer c join fetch o.payments join fetch o.supplemental join fetch o.supplemental2"; + + final List orders = session.createQuery( qry, Order.class ).list(); + + // oh look - just a single query for all the data we will need. hmm, crazy + expectedQueryCount.set( 1 ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + for ( Order order : orders ) { + System.out.println( "############################################" ); + System.out.println( "Starting Order #" + order.getOid() ); + + // accessing the many-to-one's id should not trigger a load + if ( order.getCustomer().getOid() == null ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + } + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // accessing the one-to-many should trigger a load + final Set orderPayments = order.getPayments(); + System.out.println( "Number of payments = " + orderPayments.size() ); + + // loaded already + // expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // access the non-inverse, logical 1-1 + order.getSupplemental(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental() != null ) { + System.out.println( "Got Order#supplemental = " + order.getSupplemental().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + + // access the inverse, logical 1-1 + order.getSupplemental2(); + + // loaded already + // expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental2() != null ) { + System.out.println( "Got Order#supplemental2 = " + order.getSupplemental2().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + } + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + + // todo : this is the only difference between this test and LazyGroupWithInheritanceTest + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.USE_QUERY_CACHE, "false" ); + } + + + @Before + public void prepareTestData() { + inTransaction( + session -> { + final Address austin = new Address( 1, "Austin" ); + final Address london = new Address( 2, "London" ); + + session.save( austin ); + session.save( london ); + + final ForeignCustomer acme = new ForeignCustomer( 1, "Acme", london, "1234" ); + final ForeignCustomer acmeBrick = new ForeignCustomer( 2, "Acme Brick", london, "9876", acme ); + + final ForeignCustomer freeBirds = new ForeignCustomer( 3, "Free Birds", austin, "13579" ); + + session.save( acme ); + session.save( acmeBrick ); + session.save( freeBirds ); + + final Order order1 = new Order( 1, "some text", freeBirds ); + freeBirds.getOrders().add( order1 ); + session.save( order1 ); + + final OrderSupplemental orderSupplemental = new OrderSupplemental( 1, 1 ); + order1.setSupplemental( orderSupplemental ); + final OrderSupplemental2 orderSupplemental2_1 = new OrderSupplemental2( 2, 2 ); + order1.setSupplemental2( orderSupplemental2_1 ); + orderSupplemental2_1.setOrder( order1 ); + session.save( orderSupplemental ); + session.save( orderSupplemental2_1 ); + + final Order order2 = new Order( 2, "some text", acme ); + acme.getOrders().add( order2 ); + session.save( order2 ); + + final OrderSupplemental2 orderSupplemental2_2 = new OrderSupplemental2( 3, 3 ); + order2.setSupplemental2( orderSupplemental2_2 ); + orderSupplemental2_2.setOrder( order2 ); + session.save( orderSupplemental2_2 ); + + final CreditCardPayment payment1 = new CreditCardPayment( 1, 1F, "1" ); + session.save( payment1 ); + order1.getPayments().add( payment1 ); + + final DebitCardPayment payment2 = new DebitCardPayment( 2, 2F, "2" ); + session.save( payment2 ); + order1.getPayments().add( payment2 ); + + + + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from CreditCardPayment" ).executeUpdate(); + session.createQuery( "delete from DebitCardPayment" ).executeUpdate(); + + session.createQuery( "delete from OrderSupplemental2" ).executeUpdate(); + + session.createQuery( "delete from Order" ).executeUpdate(); + + session.createQuery( "delete from OrderSupplemental" ).executeUpdate(); + + session.createQuery( "delete from DomesticCustomer" ).executeUpdate(); + session.createQuery( "delete from ForeignCustomer" ).executeUpdate(); + + session.createQuery( "delete from Address" ).executeUpdate(); + } + ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + + sources.addAnnotatedClass( Customer.class ); + sources.addAnnotatedClass( ForeignCustomer.class ); + sources.addAnnotatedClass( DomesticCustomer.class ); + + sources.addAnnotatedClass( Payment.class ); + sources.addAnnotatedClass( CreditCardPayment.class ); + sources.addAnnotatedClass( DebitCardPayment.class ); + + sources.addAnnotatedClass( Address.class ); + + sources.addAnnotatedClass( Order.class ); + sources.addAnnotatedClass( OrderSupplemental.class ); + sources.addAnnotatedClass( OrderSupplemental2.class ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceTest.java new file mode 100644 index 0000000000..fbb13398ed --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceTest.java @@ -0,0 +1,267 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.test.bytecode.enhancement.lazy.group.BidirectionalLazyGroupsInEmbeddableTest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +/** + * @author Steve Ebersole + */ + +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions( lazyLoading = true ) +public class LazyGroupWithInheritanceTest extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void queryEntityWithAssociationToAbstract() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final AtomicInteger expectedQueryCount = new AtomicInteger( 0 ); + + inTransaction( + session -> { + final List orders = session.createQuery( "select o from Order o", Order.class ).list(); + + // todo (HHH-11147) : this is a regression from 4.x + // - the condition is that the association from Order to Customer points to the non-root + // entity (Customer) rather than one of its concrete sub-types (DomesticCustomer, + // ForeignCustomer). We'd have to read the "other table" to be able to resolve the + // concrete type. The same holds true for associations to versioned entities as well. + // The only viable solution I see would be to join to the "other side" and read the + // version/discriminator[1]. But of course that means doing the join which is generally + // what the application is trying to avoid in the first place + //expectedQueryCount.set( 1 ); + expectedQueryCount.set( 4 ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + for ( Order order : orders ) { + System.out.println( "############################################" ); + System.out.println( "Starting Order #" + order.getOid() ); + + // accessing the many-to-one's id should not trigger a load + if ( order.getCustomer().getOid() == null ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + } + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // accessing the one-to-many should trigger a load + final Set orderPayments = order.getPayments(); + System.out.println( "Number of payments = " + orderPayments.size() ); + expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // access the non-inverse, logical 1-1 + order.getSupplemental(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental() != null ) { + System.out.println( "Got Order#supplemental = " + order.getSupplemental().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + + // access the inverse, logical 1-1 + order.getSupplemental2(); + expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental2() != null ) { + System.out.println( "Got Order#supplemental2 = " + order.getSupplemental2().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + } + } + ); + } + + /** + * Same test as {@link #queryEntityWithAssociationToAbstract()}, but using runtime + * fetching to issues just a single select + */ + @Test + public void queryEntityWithAssociationToAbstractRuntimeFetch() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final AtomicInteger expectedQueryCount = new AtomicInteger( 0 ); + + inTransaction( + session -> { + final String qry = "select o from Order o join fetch o.customer c join fetch o.payments join fetch o.supplemental join fetch o.supplemental2"; + + final List orders = session.createQuery( qry, Order.class ).list(); + + // oh look - just a single query for all the data we will need. hmm, crazy + expectedQueryCount.set( 1 ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + for ( Order order : orders ) { + System.out.println( "############################################" ); + System.out.println( "Starting Order #" + order.getOid() ); + + // accessing the many-to-one's id should not trigger a load + if ( order.getCustomer().getOid() == null ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + } + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // accessing the one-to-many should trigger a load + final Set orderPayments = order.getPayments(); + System.out.println( "Number of payments = " + orderPayments.size() ); + + // loaded already + // expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // access the non-inverse, logical 1-1 + order.getSupplemental(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental() != null ) { + System.out.println( "Got Order#supplemental = " + order.getSupplemental().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + + // access the inverse, logical 1-1 + order.getSupplemental2(); + + // loaded already + // expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental2() != null ) { + System.out.println( "Got Order#supplemental2 = " + order.getSupplemental2().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + } + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.USE_QUERY_CACHE, "false" ); + } + + + @Before + public void prepareTestData() { + inTransaction( + session -> { + final Address austin = new Address( 1, "Austin" ); + final Address london = new Address( 2, "London" ); + + session.save( austin ); + session.save( london ); + + final ForeignCustomer acme = new ForeignCustomer( 1, "Acme", london, "1234" ); + final ForeignCustomer acmeBrick = new ForeignCustomer( 2, "Acme Brick", london, "9876", acme ); + + final ForeignCustomer freeBirds = new ForeignCustomer( 3, "Free Birds", austin, "13579" ); + + session.save( acme ); + session.save( acmeBrick ); + session.save( freeBirds ); + + final Order order1 = new Order( 1, "some text", freeBirds ); + freeBirds.getOrders().add( order1 ); + session.save( order1 ); + + final OrderSupplemental orderSupplemental = new OrderSupplemental( 1, 1 ); + order1.setSupplemental( orderSupplemental ); + final OrderSupplemental2 orderSupplemental2_1 = new OrderSupplemental2( 2, 2 ); + order1.setSupplemental2( orderSupplemental2_1 ); + orderSupplemental2_1.setOrder( order1 ); + session.save( orderSupplemental ); + session.save( orderSupplemental2_1 ); + + final Order order2 = new Order( 2, "some text", acme ); + acme.getOrders().add( order2 ); + session.save( order2 ); + + final OrderSupplemental2 orderSupplemental2_2 = new OrderSupplemental2( 3, 3 ); + order2.setSupplemental2( orderSupplemental2_2 ); + orderSupplemental2_2.setOrder( order2 ); + session.save( orderSupplemental2_2 ); + + final CreditCardPayment payment1 = new CreditCardPayment( 1, 1F, "1" ); + session.save( payment1 ); + order1.getPayments().add( payment1 ); + + final DebitCardPayment payment2 = new DebitCardPayment( 2, 2F, "2" ); + session.save( payment2 ); + order1.getPayments().add( payment2 ); + + + + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from CreditCardPayment" ).executeUpdate(); + session.createQuery( "delete from DebitCardPayment" ).executeUpdate(); + + session.createQuery( "delete from OrderSupplemental2" ).executeUpdate(); + + session.createQuery( "delete from Order" ).executeUpdate(); + + session.createQuery( "delete from OrderSupplemental" ).executeUpdate(); + + session.createQuery( "delete from DomesticCustomer" ).executeUpdate(); + session.createQuery( "delete from ForeignCustomer" ).executeUpdate(); + + session.createQuery( "delete from Address" ).executeUpdate(); + } + ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + + sources.addAnnotatedClass( Customer.class ); + sources.addAnnotatedClass( ForeignCustomer.class ); + sources.addAnnotatedClass( DomesticCustomer.class ); + + sources.addAnnotatedClass( Payment.class ); + sources.addAnnotatedClass( CreditCardPayment.class ); + sources.addAnnotatedClass( DebitCardPayment.class ); + + sources.addAnnotatedClass( Address.class ); + + sources.addAnnotatedClass( Order.class ); + sources.addAnnotatedClass( OrderSupplemental.class ); + sources.addAnnotatedClass( OrderSupplemental2.class ); + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeDetachedToProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeDetachedToProxyTest.java new file mode 100644 index 0000000000..953bde61dd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeDetachedToProxyTest.java @@ -0,0 +1,192 @@ +/* + * 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 . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +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 org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class MergeDetachedToProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testMergeInitializedDetachedOntoProxy() { + final StatisticsImplementor statistics = sessionFactory().getStatistics(); + + final AEntity aEntityDetached = fromTransaction( + session -> { + AEntity aEntity = session.get( AEntity.class, 1 ); + assertIsEnhancedProxy( aEntity.bEntity ); + Hibernate.initialize( aEntity.bEntity ); + return aEntity; + } + ); + + statistics.clear(); + assertThat( statistics.getPrepareStatementCount(), is( 0L ) ); + + inSession( + session -> { + BEntity bEntity = session.getReference( BEntity.class, 2 ); + assertIsEnhancedProxy( bEntity ); + assertThat( statistics.getPrepareStatementCount(), is( 0L ) ); + + AEntity aEntityMerged = (AEntity) session.merge( aEntityDetached ); + assertThat( statistics.getPrepareStatementCount(), is( 1L ) ); + + assertSame( bEntity, aEntityMerged.bEntity ); + assertEquals( "a description", aEntityDetached.bEntity.description ); + assertTrue( Hibernate.isInitialized( bEntity ) ); + } + ); + + assertThat( statistics.getPrepareStatementCount(), is( 1L ) ); + } + + @Test + public void testMergeUpdatedDetachedOntoProxy() { + final StatisticsImplementor statistics = sessionFactory().getStatistics(); + + final AEntity aEntityDetached = fromTransaction( + session -> { + AEntity aEntity = session.get( AEntity.class, 1 ); + assertIsEnhancedProxy( aEntity.bEntity ); + Hibernate.initialize( aEntity.bEntity ); + return aEntity; + } + ); + + aEntityDetached.bEntity.description = "new description"; + + statistics.clear(); + assertThat( statistics.getPrepareStatementCount(), is( 0L ) ); + + inSession( + session -> { + BEntity bEntity = session.getReference( BEntity.class, 2 ); + assertIsEnhancedProxy( bEntity ); + assertThat( statistics.getPrepareStatementCount(), is( 0L ) ); + + AEntity aEntityMerged = (AEntity) session.merge( aEntityDetached ); + assertThat( statistics.getPrepareStatementCount(), is( 1L ) ); + + assertSame( bEntity, aEntityMerged.bEntity ); + assertEquals( "new description", aEntityDetached.bEntity.description ); + assertTrue( Hibernate.isInitialized( bEntity ) ); + } + ); + + assertThat( statistics.getPrepareStatementCount(), is( 1L ) ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( BEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + final AEntity aEntity = new AEntity(); + aEntity.id = 1; + final BEntity bEntity = new BEntity(); + bEntity.id = 2; + bEntity.description = "a description"; + aEntity.bEntity = bEntity; + session.persist( aEntity ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from AEntity" ).executeUpdate(); + session.createQuery( "delete from BEntity" ).executeUpdate(); + } + ); + } + + private void assertIsEnhancedProxy(Object entity) { + assertTrue( PersistentAttributeInterceptable.class.isInstance( entity ) ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + assertTrue( EnhancementAsProxyLazinessInterceptor.class.isInstance( interceptor ) ); + } + + @Entity(name = "AEntity") + public static class AEntity { + @Id + private int id; + + @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private BEntity bEntity; + } + + @Entity(name = "BEntity") + public static class BEntity { + @Id + private int id; + + private String description; + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeProxyTest.java new file mode 100644 index 0000000000..c68b8c7a68 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeProxyTest.java @@ -0,0 +1,230 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import org.hibernate.Hibernate; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.proxy.HibernateProxy; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.hamcrest.CoreMatchers; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + * @author Gail Badner + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class MergeProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testMergeDetachUninitializedProxy() { + final Activity activity = fromTransaction( + session -> session.get( Activity.class, 0 ) + ); + + assertThat( Hibernate.isInitialized( activity ), is( true ) ); + assertThat( Hibernate.isPropertyInitialized( activity, "instruction" ), is( true ) ); + final Instruction instruction = activity.getInstruction(); + assertThat( Hibernate.isInitialized( instruction ), is( false ) ); + assertThat( instruction, instanceOf( PersistentAttributeInterceptable.class ) ); + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) instruction ).$$_hibernate_getInterceptor(); + assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + + activity.setNbr( "2" ); + + final Activity mergedActivity = fromTransaction( + session -> (Activity) session.merge( activity ) + ); + + assertTrue( Hibernate.isInitialized( mergedActivity ) ); + assertThat( activity, not( CoreMatchers.sameInstance( mergedActivity ) ) ); + + inTransaction( + session -> { + final Instruction persistentInstruction = session.get( Instruction.class, 0 ); + assertThat( persistentInstruction.getSummary(), is( "Instruction #0" ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testMergeDetachInitializedProxy() { + final Activity activityProxy = fromTransaction( + session -> { + final Activity activity = session.load( Activity.class, 0 ); + assertFalse( Hibernate.isInitialized( activity) ); + Hibernate.initialize( activity ); + return activity; + } + ); + + assertThat( activityProxy, not( instanceOf( HibernateProxy.class ) ) ); + assertThat( Hibernate.isInitialized( activityProxy), is( true ) ); + + { + assertThat( Hibernate.isPropertyInitialized( activityProxy, "instruction" ), is( true ) ); + final Instruction instruction = activityProxy.getInstruction(); + assertThat( instruction, instanceOf( PersistentAttributeInterceptable.class ) ); + final PersistentAttributeInterceptor instructionInterceptor = ( (PersistentAttributeInterceptable) instruction ).$$_hibernate_getInterceptor(); + assertThat( instructionInterceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + } + + { + assertThat( Hibernate.isPropertyInitialized( activityProxy, "webApplication" ), is( true ) ); + assertThat( activityProxy.getWebApplication(), nullValue() ); + } + + activityProxy.setNbr( "2" ); + + final Activity mergedActivity = fromTransaction( + session -> (Activity) session.merge( activityProxy ) + ); + + assertTrue( Hibernate.isInitialized( mergedActivity ) ); + assertThat( activityProxy, not( CoreMatchers.sameInstance( mergedActivity ) ) ); + + inTransaction( + session -> { + final Instruction instruction = session.get( Instruction.class, 0 ); + assertThat( instruction.getSummary(), is( "Instruction #0" ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testMergeDetachInitializedByAccessProxy() { + final Activity activityProxy = fromTransaction( + session -> { + final Activity activity = session.load( Activity.class, 0 ); + assertFalse( Hibernate.isInitialized( activity) ); + activity.getDescription(); + return activity; + } + ); + + assertThat( activityProxy, not( instanceOf( HibernateProxy.class ) ) ); + assertThat( Hibernate.isInitialized( activityProxy), is( true ) ); + + { + assertThat( Hibernate.isPropertyInitialized( activityProxy, "instruction" ), is( true ) ); + final Instruction instruction = activityProxy.getInstruction(); + assertThat( instruction, instanceOf( PersistentAttributeInterceptable.class ) ); + final PersistentAttributeInterceptor instructionInterceptor = ( (PersistentAttributeInterceptable) instruction ).$$_hibernate_getInterceptor(); + assertThat( instructionInterceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + } + + { + assertThat( Hibernate.isPropertyInitialized( activityProxy, "webApplication" ), is( true ) ); + assertThat( activityProxy.getWebApplication(), nullValue() ); + } + + activityProxy.setNbr( "2" ); + + final Activity mergedActivity = fromTransaction( + session -> (Activity) session.merge( activityProxy ) + ); + + assertTrue( Hibernate.isInitialized( mergedActivity ) ); + assertThat( activityProxy, not( CoreMatchers.sameInstance( mergedActivity ) ) ); + + inTransaction( + session -> { + final Instruction instruction = session.get( Instruction.class, 0 ); + assertThat( instruction.getSummary(), is( "Instruction #0" ) ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( Activity.class ); + sources.addAnnotatedClass( Instruction.class ); + sources.addAnnotatedClass( WebApplication.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + // create a slew of Activity objects, some with Instruction reference + // some without. + + for ( int i = 0; i < 30; i++ ) { + final Activity activity = new Activity( i, "Activity #" + i, null ); + if ( i % 2 == 0 ) { + final Instruction instr = new Instruction( i, "Instruction #" + i ); + activity.setInstruction( instr ); + session.save( instr ); + } + else { + final WebApplication webApplication = new WebApplication( i, "http://" + i + ".com" ); + activity.setWebApplication( webApplication ); + webApplication.getActivities().add( activity ); + session.save( webApplication ); + } + + session.save( activity ); + } + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from Activity" ).executeUpdate(); + session.createQuery( "delete from Instruction" ).executeUpdate(); + session.createQuery( "delete from WebApplication" ).executeUpdate(); + } + ); + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ModelEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ModelEntity.java new file mode 100644 index 0000000000..1bf0415967 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ModelEntity.java @@ -0,0 +1,72 @@ +/* + * 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 . + */ + +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.sql.Timestamp; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +@MappedSuperclass +public abstract class ModelEntity { + + @Id + @Column(name="Oid") + private Long Oid = null; + + @Basic + @Column(name="CreatedAt") + private Timestamp CreatedAt = null; + + @Basic + @Column(name="CreatedBy") + private String CreatedBy = null; + + @Basic + @Column(name="VersionNr") + private short VersionNr = 0; + + public short getVersionNr() { + return VersionNr; + } + + public void setVersionNr(short versionNr) { + this.VersionNr = versionNr; + } + + public Long getOid() { + return Oid; + } + + public void setOid(Long oid) { + this.Oid = oid; + } + + public Timestamp getCreatedAt() { + return CreatedAt; + } + + public void setCreatedAt(Timestamp createdAt) { + this.CreatedAt = createdAt; + } + + public String getCreatedBy() { + return CreatedBy; + } + + public void setCreatedBy(String createdBy) { + this.CreatedBy = createdBy; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MoreSpecializedKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MoreSpecializedKey.java new file mode 100644 index 0000000000..4eecb7acbb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MoreSpecializedKey.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +@Entity(name="MoreSpecializedKey") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public class MoreSpecializedKey extends SpecializedKey implements Serializable { +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Order.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Order.java new file mode 100644 index 0000000000..b5fbf1bf99 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Order.java @@ -0,0 +1,109 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Order") +@Table(name = "`order`") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public class Order { + private Integer oid; + + private String theText; + + private Customer customer; + private OrderSupplemental supplemental; + private OrderSupplemental2 supplemental2; + + private Set payments = new HashSet(); + + public Order() { + } + + public Order(Integer oid, String theText, Customer customer) { + this.oid = oid; + this.theText = theText; + this.customer = customer; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public String getTheText() { + return theText; + } + + public void setTheText(String theText) { + this.theText = theText; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn +// @LazyToOne( LazyToOneOption.NO_PROXY ) + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + + @OneToMany(fetch = FetchType.LAZY) + public Set getPayments() { + return payments; + } + + public void setPayments(Set payments) { + this.payments = payments; + } + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "supp_info_id") + public OrderSupplemental getSupplemental() { + return supplemental; + } + + public void setSupplemental(OrderSupplemental supplemental) { + this.supplemental = supplemental; + } + + @OneToOne(fetch = FetchType.LAZY, mappedBy = "order") + @LazyToOne(LazyToOneOption.NO_PROXY) + public OrderSupplemental2 getSupplemental2() { + return supplemental2; + } + + public void setSupplemental2(OrderSupplemental2 supplemental2) { + this.supplemental2 = supplemental2; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental.java new file mode 100644 index 0000000000..50f2fe5409 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental.java @@ -0,0 +1,48 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "OrderSupplemental") +@Table(name = "order_supp") +public class OrderSupplemental { + private Integer oid; + private Integer receivablesId; + + public OrderSupplemental() { + } + + public OrderSupplemental(Integer oid, Integer receivablesId) { + this.oid = oid; + this.receivablesId = receivablesId; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public Integer getReceivablesId() { + return receivablesId; + } + + public void setReceivablesId(Integer receivablesId) { + this.receivablesId = receivablesId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental2.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental2.java new file mode 100644 index 0000000000..c2bf3baaa5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental2.java @@ -0,0 +1,63 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "OrderSupplemental2") +@Table(name = "order_supp2") +public class OrderSupplemental2 { + private Integer oid; + private Integer receivablesId; + + private Order order; + + public OrderSupplemental2() { + } + + public OrderSupplemental2(Integer oid, Integer receivablesId) { + this.oid = oid; + this.receivablesId = receivablesId; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public Integer getReceivablesId() { + return receivablesId; + } + + public void setReceivablesId(Integer receivablesId) { + this.receivablesId = receivablesId; + } + + @OneToOne(fetch = FetchType.LAZY) + @MapsId + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Payment.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Payment.java new file mode 100644 index 0000000000..cac4adc2db --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Payment.java @@ -0,0 +1,49 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Payment") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public abstract class Payment { + private Integer oid; + private Float amount; + + public Payment() { + } + + public Payment(Integer oid, Float amount) { + this.oid = oid; + this.amount = amount; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public Float getAmount() { + return amount; + } + + public void setAmount(Float amount) { + this.amount = amount; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyDeletionTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyDeletionTest.java new file mode 100644 index 0000000000..05926d24b7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyDeletionTest.java @@ -0,0 +1,461 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.sql.Blob; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Steve Ebersole + */ +public class ProxyDeletionTest extends BaseNonConfigCoreFunctionalTestCase { + + + @Test + public void testGetAndDeleteEEntity() { + inTransaction( + session -> { + EEntity entity = session.get( EEntity.class, 17L ); + session.delete( entity ); + session.delete( entity.getD() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( BEntity.class ); + sources.addAnnotatedClass( CEntity.class ); + sources.addAnnotatedClass( DEntity.class ); + sources.addAnnotatedClass( EEntity.class ); + sources.addAnnotatedClass( GEntity.class ); + + sources.addAnnotatedClass( Activity.class ); + sources.addAnnotatedClass( Instruction.class ); + sources.addAnnotatedClass( WebApplication.class ); + + sources.addAnnotatedClass( SpecializedKey.class ); + sources.addAnnotatedClass( MoreSpecializedKey.class ); + sources.addAnnotatedClass( RoleEntity.class ); + sources.addAnnotatedClass( AbstractKey.class ); + sources.addAnnotatedClass( GenericKey.class ); + sources.addAnnotatedClass( SpecializedEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + DEntity d = new DEntity(); + d.setD( "bla" ); + d.setOid( 1 ); + + byte[] lBytes = "agdfagdfagfgafgsfdgasfdgfgasdfgadsfgasfdgasfdgasdasfdg".getBytes(); + Blob lBlob = Hibernate.getLobCreator( session ).createBlob( lBytes ); + d.setBlob( lBlob ); + + BEntity b1 = new BEntity(); + b1.setOid( 1 ); + b1.setB1( 34 ); + b1.setB2( "huhu" ); + + BEntity b2 = new BEntity(); + b2.setOid( 2 ); + b2.setB1( 37 ); + b2.setB2( "haha" ); + + Set lBs = new HashSet<>(); + lBs.add( b1 ); + lBs.add( b2 ); + d.setBs( lBs ); + + AEntity a = new AEntity(); + a.setOid( 1 ); + a.setA( "hihi" ); + d.setA( a ); + + EEntity e = new EEntity(); + e.setOid( 17 ); + e.setE1( "Balu" ); + e.setE2( "Bär" ); + + e.setD( d ); + d.setE( e ); + + CEntity c = new CEntity(); + c.setOid( 1 ); + c.setC1( "ast" ); + c.setC2( "qwert" ); + c.setC3( "yxcv" ); + d.setC( c ); + + GEntity g = new GEntity(); + g.setOid( 1 ); + g.getdEntities().add( d ); + d.setG( g ); + + + session.save( b1 ); + session.save( b2 ); + session.save( a ); + session.save( c ); + session.save( g ); + session.save( d ); + session.save( e ); + + + // create a slew of Activity objects, some with Instruction reference + // some without. + + for ( int i = 0; i < 30; i++ ) { + final Activity activity = new Activity( i, "Activity #" + i, null ); + if ( i % 2 == 0 ) { + final Instruction instr = new Instruction( i, "Instruction #" + i ); + activity.setInstruction( instr ); + session.save( instr ); + } + else { + final WebApplication webApplication = new WebApplication( i, "http://" + i + ".com" ); + activity.setWebApplication( webApplication ); + webApplication.getActivities().add( activity ); + session.save( webApplication ); + } + + session.save( activity ); + } + + RoleEntity roleEntity = new RoleEntity(); + roleEntity.setOid( 1L ); + + SpecializedKey specializedKey = new SpecializedKey(); + specializedKey.setOid(1L); + + MoreSpecializedKey moreSpecializedKey = new MoreSpecializedKey(); + moreSpecializedKey.setOid( 3L ); + + SpecializedEntity specializedEntity = new SpecializedEntity(); + specializedEntity.setId( 2L ); + specializedKey.addSpecializedEntity( specializedEntity ); + specializedEntity.setSpecializedKey( specializedKey); + + specializedKey.addRole( roleEntity ); + roleEntity.setKey( specializedKey ); + roleEntity.setSpecializedKey( moreSpecializedKey ); + moreSpecializedKey.addRole( roleEntity ); + session.save( specializedEntity ); + session.save( roleEntity ); + session.save( specializedKey ); + session.save( moreSpecializedKey ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from E" ).executeUpdate(); + session.createQuery( "delete from D" ).executeUpdate(); + session.createQuery( "delete from C" ).executeUpdate(); + session.createQuery( "delete from B" ).executeUpdate(); + session.createQuery( "delete from A" ).executeUpdate(); + session.createQuery( "delete from G" ).executeUpdate(); + + session.createQuery( "delete from Activity" ).executeUpdate(); + session.createQuery( "delete from Instruction" ).executeUpdate(); + session.createQuery( "delete from WebApplication" ).executeUpdate(); + + session.createQuery( "delete from SpecializedEntity" ).executeUpdate(); + session.createQuery( "delete from RoleEntity" ).executeUpdate(); + session.createQuery( "delete from MoreSpecializedKey" ).executeUpdate(); + session.createQuery( "delete from SpecializedKey" ).executeUpdate(); + session.createQuery( "delete from GenericKey" ).executeUpdate(); + session.createQuery( "delete from AbstractKey" ).executeUpdate(); + } + ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + private long oid; + private short version; + + public long getOid() { + return oid; + } + + public void setOid(long oid) { + this.oid = oid; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + } + + @Entity(name = "A") + @Table(name = "A") + public static class AEntity extends BaseEntity { + @Column(name = "A") + private String a; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + } + + @Entity(name = "B") + @Table(name = "B") + public static class BEntity extends BaseEntity { + private Integer b1; + private String b2; + + public Integer getB1() { + return b1; + } + + public void setB1(Integer b1) { + this.b1 = b1; + } + + public String getB2() { + return b2; + } + + public void setB2(String b2) { + this.b2 = b2; + } + } + + @Entity(name = "C") + @Table(name = "C") + public static class CEntity extends BaseEntity { + private String c1; + private String c2; + private String c3; + private Long c4; + + public String getC1() { + return c1; + } + + public void setC1(String c1) { + this.c1 = c1; + } + + public String getC2() { + return c2; + } + + @Basic(fetch = FetchType.LAZY) + public void setC2(String c2) { + this.c2 = c2; + } + + public String getC3() { + return c3; + } + + public void setC3(String c3) { + this.c3 = c3; + } + + public Long getC4() { + return c4; + } + + public void setC4(Long c4) { + this.c4 = c4; + } + } + + @Entity(name = "D") + @Table(name = "D") + public static class DEntity extends BaseEntity { + private String d; + + @OneToOne(fetch = FetchType.LAZY) + public AEntity a; + + @OneToOne(fetch = FetchType.LAZY) + public CEntity c; + + @OneToMany(targetEntity = BEntity.class) + public Set bs; + + @OneToOne(mappedBy = "d", fetch = FetchType.LAZY) + private EEntity e; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn() + @LazyGroup("g") + public GEntity g; + + @Lob + @Basic(fetch = FetchType.LAZY) + @LazyGroup("blob") + private Blob blob; + + public String getD() { + return d; + } + + public void setD(String d) { + this.d = d; + } + + + public AEntity getA() { + return a; + } + + public void setA(AEntity a) { + this.a = a; + } + + public Set getBs() { + return bs; + } + + public void setBs(Set bs) { + this.bs = bs; + } + + public CEntity getC() { + return c; + } + + public void setC(CEntity c) { + this.c = c; + } + + public Blob getBlob() { + return blob; + } + + public void setBlob(Blob blob) { + this.blob = blob; + } + + public EEntity getE() { + return e; + } + + public void setE(EEntity e) { + this.e = e; + } + + public GEntity getG() { + return g; + } + + public void setG(GEntity g) { + this.g = g; + } + } + + @Entity(name = "E") + @Table(name = "E") + public static class EEntity extends BaseEntity { + private String e1; + private String e2; + + @OneToOne(fetch = FetchType.LAZY) + private DEntity d; + + public String getE1() { + return e1; + } + + public void setE1(String e1) { + this.e1 = e1; + } + + public String getE2() { + return e2; + } + + public void setE2(String e2) { + this.e2 = e2; + } + + public DEntity getD() { + return d; + } + + public void setD(DEntity d) { + this.d = d; + } + } + + @Entity(name = "G") + @Table(name = "G") + public static class GEntity extends BaseEntity { + + @OneToMany(mappedBy = "g") + public Set dEntities = new HashSet<>(); + + public Set getdEntities() { + return dEntities; + } + + public void setdEntities(Set dEntities) { + this.dEntities = dEntities; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/RoleEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/RoleEntity.java new file mode 100644 index 0000000000..18ae96adb2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/RoleEntity.java @@ -0,0 +1,74 @@ +/* + * 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 . + */ + +/* + * 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.test.bytecode.enhancement.lazy.proxy; + + +import java.io.Serializable; +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +@Entity(name = "RoleEntity") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name = "PP_DCRolleKey") +public class RoleEntity extends ModelEntity implements Serializable { + + @Basic + Short value; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.PROXY) + @LazyGroup("Key") + @JoinColumn + protected AbstractKey key = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.PROXY) + @LazyGroup("Key") + @JoinColumn + protected SpecializedKey specializedKey = null; + + public Short getValue() { + return value; + } + + public void setvalue(Short value) { + this.value = value; + } + + public AbstractKey getKey() { + return key; + } + + public void setKey(AbstractKey key) { + this.key = key; + } + + public SpecializedKey getSpecializedKey() { + return specializedKey; + } + + public void setSpecializedKey(SpecializedKey specializedKey) { + this.specializedKey = specializedKey; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoading.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoading.java new file mode 100644 index 0000000000..6f31d92af6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoading.java @@ -0,0 +1,329 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.hamcrest.MatcherAssert; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions(lazyLoading = true) +public class SimpleUpdateTestWithLazyLoading extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + private static final int CHILDREN_SIZE = 10; + private Long lastChildID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class, Person.class }; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = new Parent(); + for ( int i = 0; i < CHILDREN_SIZE; i++ ) { + Child child = new Child(); + // Association management should kick in here + child.parent = parent; + + Person relative = new Person(); + relative.setName( "Luigi" ); + child.addRelative( relative ); + + s.persist( relative ); + + s.persist( child ); + lastChildID = child.id; + } + s.persist( parent ); + } ); + } + + + @After + public void tearDown() { + doInHibernate( this::sessionFactory, s -> { + s.createQuery( "delete from Child" ).executeUpdate(); + s.createQuery( "delete from Parent" ).executeUpdate(); + } ); + } + + @Test + public void updateSimpleField() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final String updatedName = "Barrabas_"; + + final EntityPersister childPersister = sessionFactory().getMetamodel().entityPersister( Child.class.getName() ); + + final int relativesAttributeIndex = childPersister.getEntityMetamodel().getPropertyIndex( "relatives" ); + + inTransaction( + session -> { + stats.clear(); + Child loadedChild = session.load( Child.class, lastChildID ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) loadedChild; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor; + + loadedChild.setName( updatedName ); + + // ^ should have triggered "base fetch group" initialization which would mean a SQL select + assertEquals( 1, stats.getPrepareStatementCount() ); + + // check that the `#setName` "persisted" + assertThat( loadedChild.getName(), is( updatedName ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + final EntityEntry entry = session.getPersistenceContext().getEntry( loadedChild ); + assertThat( + entry.getLoadedState()[ relativesAttributeIndex ], + is( LazyPropertyInitializer.UNFETCHED_PROPERTY ) + ); + + // force a flush - the relatives collection should still be UNFETCHED_PROPERTY afterwards + session.flush(); + + final EntityEntry updatedEntry = session.getPersistenceContext().getEntry( loadedChild ); + assertThat( updatedEntry, sameInstance( entry ) ); + + assertThat( + entry.getLoadedState()[ relativesAttributeIndex ], + is( LazyPropertyInitializer.UNFETCHED_PROPERTY ) + ); + + session.getEventListenerManager(); + } + ); + + inTransaction( + session -> { + Child loadedChild = session.load( Child.class, lastChildID ); + assertThat( loadedChild.getName(), is( updatedName ) ); + + final EntityEntry entry = session.getPersistenceContext().getEntry( loadedChild ); + assertThat( + entry.getLoadedState()[ relativesAttributeIndex ], + is( LazyPropertyInitializer.UNFETCHED_PROPERTY ) + ); + } + ); + + } + + @Test + public void testUpdateAssociation() { + String updatedName = "Barrabas_"; + String parentName = "Yodit"; + doInHibernate( this::sessionFactory, s -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + Child loadedChild = s.load( Child.class, lastChildID ); + + loadedChild.setName( updatedName ); + + Parent parent = new Parent(); + parent.setName( parentName ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + loadedChild.setParent( parent ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertThat( loadedChild.getParent().getName(), is( parentName ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + s.save( parent ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + assertThat( loadedChild.getName(), is( updatedName ) ); + assertThat( loadedChild.getParent().getName(), is( parentName ) ); + } ); + } + + @Test + public void testUpdateCollection() { + doInHibernate( this::sessionFactory, s -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + Child loadedChild = s.load( Child.class, lastChildID ); + + + assertEquals( 0, stats.getPrepareStatementCount() ); + Person relative = new Person(); + relative.setName( "Luis" ); + loadedChild.addRelative( relative ); + assertEquals( 2, stats.getPrepareStatementCount() ); + s.persist( relative ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + assertThat( loadedChild.getRelatives().size(), is( 2 ) ); + } ); + } + + @Entity(name = "Parent") + @Table(name = "PARENT") + private static class Parent { + + String name; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + List children; + + void setChildren(List children) { + this.children = children; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Person") + @Table(name = "Person") + private static class Person { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Child") + @Table(name = "CHILD") + private static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + Parent parent; + + @OneToMany + List relatives; + + String name; + + Child() { + } + + Child(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + public List getRelatives() { + return relatives; + } + + public void setRelatives(List relatives) { + this.relatives = relatives; + } + + public void addRelative(Person person) { + if ( this.relatives == null ) { + this.relatives = new ArrayList<>(); + } + this.relatives.add( person ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoadingAndInlineDirtyTracking.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoadingAndInlineDirtyTracking.java new file mode 100644 index 0000000000..15b49622e0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoadingAndInlineDirtyTracking.java @@ -0,0 +1,284 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.hamcrest.MatcherAssert; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions( lazyLoading = true, inlineDirtyChecking = true ) +public class SimpleUpdateTestWithLazyLoadingAndInlineDirtyTracking extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + private static final int CHILDREN_SIZE = 10; + private Long lastChildID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class, Person.class }; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = new Parent(); + for ( int i = 0; i < CHILDREN_SIZE; i++ ) { + Child child = new Child(); + // Association management should kick in here + child.parent = parent; + + Person relative = new Person(); + relative.setName( "Luigi" ); + child.addRelative( relative ); + + s.persist( relative ); + + s.persist( child ); + lastChildID = child.id; + } + s.persist( parent ); + } ); + } + + + @After + public void tearDown() { + doInHibernate( this::sessionFactory, s -> { + s.createQuery( "delete from Child" ).executeUpdate(); + s.createQuery( "delete from Parent" ).executeUpdate(); + } ); + } + + @Test + public void updateSimpleField() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + String updatedName = "Barrabas_"; + doInHibernate( this::sessionFactory, s -> { + stats.clear(); + Child loadedChild = s.load( Child.class, lastChildID ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) loadedChild; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + + loadedChild.setName( updatedName ); + assertEquals( 0, stats.getPrepareStatementCount() ); + assertThat( loadedChild.getName(), is( updatedName ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + } ); + + // the UPDATE + assertEquals( 1, stats.getPrepareStatementCount() ); + + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + assertThat( loadedChild.getName(), is( updatedName ) ); + } ); + } + + @Test + public void testUpdateAssociation() { + String updatedName = "Barrabas_"; + String parentName = "Yodit"; + doInHibernate( this::sessionFactory, s -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + Child loadedChild = s.load( Child.class, lastChildID ); + + loadedChild.setName( updatedName ); + + Parent parent = new Parent(); + parent.setName( parentName ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + loadedChild.setParent( parent ); + assertEquals( 0, stats.getPrepareStatementCount() ); + assertThat( loadedChild.getParent().getName(), is( parentName ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + s.save( parent ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + assertThat( loadedChild.getName(), is( updatedName ) ); + assertThat( loadedChild.getParent().getName(), is( parentName ) ); + } ); + } + + @Test + public void testUpdateCollection() { + doInHibernate( this::sessionFactory, s -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + Child loadedChild = s.load( Child.class, lastChildID ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + Person relative = new Person(); + relative.setName( "Luis" ); + loadedChild.addRelative( relative ); + + // forces SELECT related to uninitialized Child, and then forces + // SELECT of Child#relatives collection for the add + assertEquals( 2, stats.getPrepareStatementCount() ); + s.persist( relative ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + assertThat( loadedChild.getRelatives().size(), is( 2 ) ); + } ); + } + + @Entity(name = "Parent") + @Table(name = "PARENT") + private static class Parent { + + String name; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + List children; + + void setChildren(List children) { + this.children = children; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Person") + @Table(name = "Person") + private static class Person { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Child") + @Table(name = "CHILD") + private static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + Parent parent; + + @OneToMany + List relatives; + + String name; + + Child() { + } + + Child(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + public List getRelatives() { + return relatives; + } + + public void setRelatives(List relatives) { + this.relatives = relatives; + } + + public void addRelative(Person person) { + if ( this.relatives == null ) { + this.relatives = new ArrayList<>(); + } + this.relatives.add( person ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java new file mode 100644 index 0000000000..875cd594b2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java @@ -0,0 +1,72 @@ +/* + * 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 . + */ + +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + + +@Entity(name="SpecializedEntity") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name="PP_PartnerZusatzKuerzelZR") +public class SpecializedEntity implements Serializable { + + @Id + Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Column(name="Value") + String value; + + @ManyToOne(fetch=FetchType.LAZY) + @LazyToOne(LazyToOneOption.PROXY) + @LazyGroup("SpecializedKey") + @JoinColumn + protected SpecializedKey specializedKey = null; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public SpecializedKey getSpecializedKey() { + return specializedKey; + } + + public void setSpecializedKey(SpecializedKey specializedKey) { + this.specializedKey = specializedKey; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedKey.java new file mode 100644 index 0000000000..682f0a4fb0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedKey.java @@ -0,0 +1,51 @@ +/* + * 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 . + */ + +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; + +@Entity(name="SpecializedKey") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name="PP_PartnerDCKey") +public class SpecializedKey extends GenericKey implements Serializable +{ + + @OneToMany(targetEntity= SpecializedEntity.class, mappedBy="specializedKey", fetch=FetchType.LAZY) +// @LazyCollection( LazyCollectionOption.EXTRA ) + protected Set specializedEntities = new LinkedHashSet(); + + public Set getSpecializedEntities() { + return specializedEntities; + } + + public void setSpecializedEntities(Set specializedEntities) { + this. specializedEntities = specializedEntities; + } + + public void addSpecializedEntity(SpecializedEntity pPartnerZusatzkuerzelZR) { + this.specializedEntities.add( pPartnerZusatzkuerzelZR); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/WebApplication.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/WebApplication.java new file mode 100644 index 0000000000..d25377109c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/WebApplication.java @@ -0,0 +1,72 @@ +/* + * 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.test.bytecode.enhancement.lazy.proxy; + +import java.net.URL; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.annotations.NaturalId; + +/** + * @author Steve Ebersole + */ +@Entity +@Table( name="web_app" ) +public class WebApplication extends BaseEntity { + private String name; + private String siteUrl; + + private Set activities = new HashSet<>(); + + @SuppressWarnings("unused") + public WebApplication() { + } + + public WebApplication(Integer id, String siteUrl) { + super( id ); + this.siteUrl = siteUrl; + } + + @NaturalId + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Basic( fetch = FetchType.LAZY ) + public String getSiteUrl() { + return siteUrl; + } + + public void setSiteUrl(String siteUrl) { + this.siteUrl = siteUrl; + } + + @OneToMany(mappedBy="webApplication", fetch= FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("app_activity_group") +// @CollectionType(type="baseutil.technology.hibernate.IskvLinkedSetCollectionType") + public Set getActivities() { + return activities; + } + + public void setActivities(Set activities) { + this.activities = activities; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/package-info.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/package-info.java new file mode 100644 index 0000000000..54d07339c7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/package-info.java @@ -0,0 +1,11 @@ +/* + * 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 + */ + +/** + * Tests for lazy-enhancement-as-proxy support + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java index 68d676d84a..2f8d8d1911 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java @@ -71,7 +71,7 @@ public class CustomPersister implements EntityPersister { NaturalIdDataAccess naturalIdRegionAccessStrategy, PersisterCreationContext creationContext) { this.factory = creationContext.getSessionFactory(); - this.entityMetamodel = new EntityMetamodel( model, this, factory ); + this.entityMetamodel = new EntityMetamodel( model, this, creationContext ); } public boolean hasLazyProperties() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Producer.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Producer.java new file mode 100644 index 0000000000..af8864bd07 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Producer.java @@ -0,0 +1,57 @@ +/* + * 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.test.stateless.fetching; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.HashSet; +import java.util.Set; + +@Entity +public class Producer { + @Id + private Integer id; + + private String name; + + @OneToMany( mappedBy = "producer", fetch = FetchType.LAZY ) + private Set products = new HashSet<>(); + + public Producer() { + } + + public Producer(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Product.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Product.java new file mode 100644 index 0000000000..bc384a6f07 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Product.java @@ -0,0 +1,67 @@ +/* + * 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.test.stateless.fetching; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@Entity +public class Product { + @Id + private Integer id; + private String sku; + + @ManyToOne( fetch = FetchType.LAZY ) + private Vendor vendor; + + @ManyToOne( fetch = FetchType.LAZY ) + private Producer producer; + + public Product() { + } + + public Product(Integer id, String sku, Vendor vendor, Producer producer) { + this.id = id; + this.sku = sku; + this.vendor = vendor; + this.producer = producer; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSku() { + return sku; + } + + public void setSku(String sku) { + this.sku = sku; + } + + public Vendor getVendor() { + return vendor; + } + + public void setVendor(Vendor vendor) { + this.vendor = vendor; + } + + public Producer getProducer() { + return producer; + } + + public void setProducer(Producer producer) { + this.producer = producer; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java index fabd46cf36..01f7d0e75f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java @@ -10,6 +10,9 @@ import java.util.Date; import java.util.Locale; import org.hibernate.Hibernate; +import org.hibernate.Query; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; import org.hibernate.Session; import org.hibernate.StatelessSession; import org.hibernate.boot.model.naming.Identifier; @@ -21,8 +24,6 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; -import org.jboss.logging.Logger; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -36,6 +37,11 @@ public class StatelessSessionFetchingTest extends BaseCoreFunctionalTestCase { return new String[] { "stateless/fetching/Mappings.hbm.xml" }; } + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Producer.class, Product.class, Vendor.class }; + } + @Override public void configure(Configuration cfg) { super.configure( cfg ); @@ -91,12 +97,161 @@ public class StatelessSessionFetchingTest extends BaseCoreFunctionalTestCase { cleanup(); } + @Test + public void testDynamicFetchScroll() { + Session s = openSession(); + s.beginTransaction(); + Date now = new Date(); + + User me = new User( "me" ); + User you = new User( "you" ); + Resource yourClock = new Resource( "clock", you ); + Task task = new Task( me, "clean", yourClock, now ); // :) + + s.save( me ); + s.save( you ); + s.save( yourClock ); + s.save( task ); + + User u3 = new User( "U3" ); + User u4 = new User( "U4" ); + Resource it = new Resource( "it", u4 ); + Task task2 = new Task( u3, "beat", it, now ); // :)) + + s.save( u3 ); + s.save( u4 ); + s.save( it ); + s.save( task2 ); + + s.getTransaction().commit(); + s.close(); + + StatelessSession ss = sessionFactory().openStatelessSession(); + ss.beginTransaction(); + + final Query query = ss.createQuery( "from Task t join fetch t.resource join fetch t.user"); + final ScrollableResults scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY); + while ( scrollableResults.next() ) { + Task taskRef = (Task) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( taskRef ) ); + assertTrue( Hibernate.isInitialized( taskRef.getUser() ) ); + assertTrue( Hibernate.isInitialized( taskRef.getResource() ) ); + assertFalse( Hibernate.isInitialized( taskRef.getResource().getOwner() ) ); + } + + ss.getTransaction().commit(); + ss.close(); + + cleanup(); + } + + @Test + public void testDynamicFetchScrollSession() { + Session s = openSession(); + s.beginTransaction(); + Date now = new Date(); + + User me = new User( "me" ); + User you = new User( "you" ); + Resource yourClock = new Resource( "clock", you ); + Task task = new Task( me, "clean", yourClock, now ); // :) + + s.save( me ); + s.save( you ); + s.save( yourClock ); + s.save( task ); + + User u3 = new User( "U3" ); + User u4 = new User( "U4" ); + Resource it = new Resource( "it", u4 ); + Task task2 = new Task( u3, "beat", it, now ); // :)) + + s.save( u3 ); + s.save( u4 ); + s.save( it ); + s.save( task2 ); + + s.getTransaction().commit(); + s.close(); + + inTransaction( + session -> { + final Query query = session.createQuery( "from Task t join fetch t.resource join fetch t.user"); + final ScrollableResults scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + while ( scrollableResults.next() ) { + Task taskRef = (Task) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( taskRef ) ); + assertTrue( Hibernate.isInitialized( taskRef.getUser() ) ); + assertTrue( Hibernate.isInitialized( taskRef.getResource() ) ); + assertFalse( Hibernate.isInitialized( taskRef.getResource().getOwner() ) ); + } + + } + ); + + cleanup(); + } + + @Test + public void testDynamicFetchCollectionScroll() { + Session s = openSession(); + s.beginTransaction(); + + Producer p1 = new Producer( 1, "Acme" ); + Producer p2 = new Producer( 2, "ABC" ); + + session.save( p1 ); + session.save( p2 ); + + Vendor v1 = new Vendor( 1, "v1" ); + Vendor v2 = new Vendor( 2, "v2" ); + + session.save( v1 ); + session.save( v2 ); + + final Product product1 = new Product(1, "123", v1, p1); + final Product product2 = new Product(2, "456", v1, p1); + final Product product3 = new Product(3, "789", v1, p2); + + session.save( product1 ); + session.save( product2 ); + session.save( product3 ); + + s.getTransaction().commit(); + s.close(); + + StatelessSession ss = sessionFactory().openStatelessSession(); + ss.beginTransaction(); + + final Query query = ss.createQuery( "select p from Producer p join fetch p.products" ); + final ScrollableResults scrollableResults = query.scroll(ScrollMode.FORWARD_ONLY); + while ( scrollableResults.next() ) { + Producer producer = (Producer) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( producer ) ); + assertTrue( Hibernate.isInitialized( producer.getProducts() ) ); + + for (Product product : producer.getProducts()) { + assertTrue( Hibernate.isInitialized( product ) ); + assertFalse( Hibernate.isInitialized( product.getVendor() ) ); + } + } + + ss.getTransaction().commit(); + ss.close(); + + cleanup(); + } + private void cleanup() { Session s = openSession(); s.beginTransaction(); s.createQuery( "delete Task" ).executeUpdate(); s.createQuery( "delete Resource" ).executeUpdate(); s.createQuery( "delete User" ).executeUpdate(); + + s.createQuery( "delete Product" ).executeUpdate(); + s.createQuery( "delete Producer" ).executeUpdate(); + s.createQuery( "delete Vendor" ).executeUpdate(); s.getTransaction().commit(); s.close(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Vendor.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Vendor.java new file mode 100644 index 0000000000..5f022d2dd6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Vendor.java @@ -0,0 +1,56 @@ +/* + * 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.test.stateless.fetching; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.HashSet; +import java.util.Set; + +@Entity +public class Vendor { + @Id + private Integer id; + private String name; + + @OneToMany(mappedBy = "vendor", fetch = FetchType.LAZY ) + private Set products = new HashSet<>(); + + public Vendor() { + } + + public Vendor(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java index 25ce2fcade..092e268d47 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java @@ -115,6 +115,7 @@ public class LobUnfetchedPropertyTest extends BaseCoreFunctionalTestCase { FileNClob file = s.get( FileNClob.class, id ); assertFalse( Hibernate.isPropertyInitialized( file, "clob" ) ); NClob nClob = file.getClob(); + assertTrue( Hibernate.isPropertyInitialized( file, "clob" ) ); try { final char[] chars = new char[(int) file.getClob().length()]; nClob.getCharacterStream().read( chars ); diff --git a/hibernate-core/src/test/resources/log4j.properties b/hibernate-core/src/test/resources/log4j.properties index ce6c182142..77b4eac553 100644 --- a/hibernate-core/src/test/resources/log4j.properties +++ b/hibernate-core/src/test/resources/log4j.properties @@ -49,9 +49,9 @@ log4j.logger.org.hibernate.engine.internal.StatisticalLoggingSessionEventListene log4j.logger.org.hibernate.boot.model.source.internal.hbm.ModelBinder=debug log4j.logger.org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry=debug -log4j.logger.org.hibernate.action.internal.EntityAction=debug -log4j.logger.org.hibernate.engine.internal.Cascade=trace +#log4j.logger.org.hibernate.action.internal.EntityAction=debug +#log4j.logger.org.hibernate.engine.internal.Cascade=trace ### When entity copy merge functionality is enabled using: ### hibernate.event.merge.entity_copy_observer=log, the following will diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java index 4801752bc5..af5626531d 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java @@ -10,13 +10,20 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; +import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.bytecode.enhance.spi.UnloadedField; import org.hibernate.cfg.Environment; + import org.hibernate.testing.junit4.CustomRunner; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; @@ -46,7 +53,16 @@ public class BytecodeEnhancerRunner extends Suite { List> classList = new ArrayList<>(); try { - if ( klass.isAnnotationPresent( CustomEnhancementContext.class ) ) { + if ( klass.isAnnotationPresent( EnhancementOptions.class ) + || klass.isAnnotationPresent( ClassEnhancementSelector.class ) + || klass.isAnnotationPresent( ClassEnhancementSelectors.class ) + || klass.isAnnotationPresent( PackageEnhancementSelector.class ) + || klass.isAnnotationPresent( PackageEnhancementSelectors.class ) + || klass.isAnnotationPresent( ImplEnhancementSelector.class ) + || klass.isAnnotationPresent( ImplEnhancementSelectors.class ) ) { + classList.add( buildEnhancerClassLoader( klass ).loadClass( klass.getName() ) ); + } + else if ( klass.isAnnotationPresent( CustomEnhancementContext.class ) ) { for ( Class contextClass : klass.getAnnotation( CustomEnhancementContext.class ).value() ) { EnhancementContext enhancementContextInstance = contextClass.getConstructor().newInstance(); classList.add( getEnhancerClassLoader( enhancementContextInstance, packageName ).loadClass( klass.getName() ) ); @@ -65,52 +81,166 @@ public class BytecodeEnhancerRunner extends Suite { // --- // - private static ClassLoader getEnhancerClassLoader(EnhancementContext context, String packageName) { - return new ClassLoader() { - private final String debugOutputDir = System.getProperty( "java.io.tmpdir" ); - - private final Enhancer enhancer = Environment.getBytecodeProvider().getEnhancer( context ); - - @SuppressWarnings( "ResultOfMethodCallIgnored" ) - @Override - public Class loadClass(String name) throws ClassNotFoundException { - if ( !name.startsWith( packageName ) ) { - return getParent().loadClass( name ); - } - Class c = findLoadedClass( name ); - if ( c != null ) { - return c; + private static ClassLoader buildEnhancerClassLoader(Class klass) { + final EnhancementOptions options = klass.getAnnotation( EnhancementOptions.class ); + final EnhancementContext enhancerContext; + if ( options == null ) { + enhancerContext = new EnhancerTestContext(); + } + else { + enhancerContext = new EnhancerTestContext() { + @Override + public boolean doBiDirectionalAssociationManagement(UnloadedField field) { + return options.biDirectionalAssociationManagement() && super.doBiDirectionalAssociationManagement( field ); } - try ( InputStream is = getResourceAsStream( name.replace( '.', '/' ) + ".class" ) ) { - if ( is == null ) { - throw new ClassNotFoundException( name + " not found" ); - } - - byte[] original = new byte[is.available()]; - try ( BufferedInputStream bis = new BufferedInputStream( is ) ) { - bis.read( original ); - } - - byte[] enhanced = enhancer.enhance( name, original ); - if ( enhanced == null ) { - return defineClass( name, original, 0, original.length ); - } - - File f = new File( debugOutputDir + File.separator + name.replace( ".", File.separator ) + ".class" ); - f.getParentFile().mkdirs(); - f.createNewFile(); - try ( FileOutputStream out = new FileOutputStream( f ) ) { - out.write( enhanced ); - } - return defineClass( name, enhanced, 0, enhanced.length ); + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return options.inlineDirtyChecking() && super.doDirtyCheckingInline( classDescriptor ); } - catch ( Throwable t ) { - throw new ClassNotFoundException( name + " not found", t ); + + @Override + public boolean doExtendedEnhancement(UnloadedClass classDescriptor) { + return options.extendedEnhancement() && super.doExtendedEnhancement( classDescriptor ); + } + + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + return options.lazyLoading() && super.hasLazyLoadableAttributes( classDescriptor ); + } + + @Override + public boolean isLazyLoadable(UnloadedField field) { + return options.lazyLoading() && super.isLazyLoadable( field ); + } + }; + } + + final List selectors = new ArrayList<>(); + selectors.add( new PackageSelector( klass.getPackage().getName() ) ); + applySelectors( + klass, + ClassEnhancementSelector.class, + ClassEnhancementSelectors.class, + selectorAnnotation -> selectors.add( new ClassSelector( selectorAnnotation.value().getName() ) ) + ); + applySelectors( + klass, + PackageEnhancementSelector.class, + PackageEnhancementSelectors.class, + selectorAnnotation -> selectors.add( new PackageSelector( selectorAnnotation.value() ) ) + ); + applySelectors( + klass, + ImplEnhancementSelector.class, + ImplEnhancementSelectors.class, + selectorAnnotation -> { + try { + selectors.add( selectorAnnotation.impl().newInstance() ); + } + catch ( RuntimeException re ) { + throw re; + } + catch ( Exception e ) { + throw new RuntimeException( e ); + } + } + ); + + return buildEnhancerClassLoader( enhancerContext, selectors ); + } + + private static void applySelectors( + Class klass, + Class selectorAnnotationType, + Class selectorsAnnotationType, + Consumer action) { + final A selectorAnnotation = klass.getAnnotation( selectorAnnotationType ); + final Annotation selectorsAnnotation = klass.getAnnotation( selectorsAnnotationType ); + + if ( selectorAnnotation != null ) { + action.accept( selectorAnnotation ); + } + else if ( selectorsAnnotation != null ) { + try { + final Method valuesMethod = selectorsAnnotationType.getDeclaredMethods()[0]; + //noinspection unchecked + final A[] selectorAnnotations = (A[]) valuesMethod.invoke( selectorsAnnotation ); + for ( A groupedSelectorAnnotation : selectorAnnotations ) { + action.accept( groupedSelectorAnnotation ); + } + + } + catch (Exception e) { + throw new RuntimeException( e ); + } + } + } + + private static ClassLoader buildEnhancerClassLoader( + EnhancementContext enhancerContext, + List selectors) { + return new EnhancingClassLoader( + Environment.getBytecodeProvider().getEnhancer( enhancerContext ), + selectors + ); + } + + private static class EnhancingClassLoader extends ClassLoader { + private static final String debugOutputDir = System.getProperty( "java.io.tmpdir" ); + + private final Enhancer enhancer; + private final List selectors; + + public EnhancingClassLoader(Enhancer enhancer, List selectors) { + this.enhancer = enhancer; + this.selectors = selectors; + } + + public Class loadClass(String name) throws ClassNotFoundException { + for ( EnhancementSelector selector : selectors ) { + if ( selector.select( name ) ) { + final Class c = findLoadedClass( name ); + if ( c != null ) { + return c; + } + + try ( InputStream is = getResourceAsStream( name.replace( '.', '/' ) + ".class" ) ) { + if ( is == null ) { + throw new ClassNotFoundException( name + " not found" ); + } + + byte[] original = new byte[is.available()]; + try ( BufferedInputStream bis = new BufferedInputStream( is ) ) { + bis.read( original ); + } + + byte[] enhanced = enhancer.enhance( name, original ); + if ( enhanced == null ) { + return defineClass( name, original, 0, original.length ); + } + + File f = new File( debugOutputDir + File.separator + name.replace( ".", File.separator ) + ".class" ); + f.getParentFile().mkdirs(); + f.createNewFile(); + try ( FileOutputStream out = new FileOutputStream( f ) ) { + out.write( enhanced ); + } + return defineClass( name, enhanced, 0, enhanced.length ); + } + catch ( Throwable t ) { + throw new ClassNotFoundException( name + " not found", t ); + } } } - }; + + return getParent().loadClass( name ); + } + } + + private static ClassLoader getEnhancerClassLoader(EnhancementContext context, String packageName) { + return buildEnhancerClassLoader( context, Collections.singletonList( new PackageSelector( packageName ) ) ); } @Override diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelector.java new file mode 100644 index 0000000000..3ab99fbf40 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelector.java @@ -0,0 +1,25 @@ +/* + * 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.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +@Repeatable( ClassEnhancementSelectors.class ) +public @interface ClassEnhancementSelector { + Class value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelectors.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelectors.java new file mode 100644 index 0000000000..c27b42e7e0 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelectors.java @@ -0,0 +1,23 @@ +/* + * 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.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +public @interface ClassEnhancementSelectors { + ClassEnhancementSelector[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassSelector.java new file mode 100644 index 0000000000..7a170473a7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassSelector.java @@ -0,0 +1,25 @@ +/* + * 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.testing.bytecode.enhancement; + +/** + * EnhancementSelector based on class name + * + * @author Steve Ebersole + */ +public class ClassSelector implements EnhancementSelector { + private final String className; + + public ClassSelector(String className) { + this.className = className; + } + + @Override + public boolean select(String name) { + return name.equals( className ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java new file mode 100644 index 0000000000..7853126bec --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java @@ -0,0 +1,26 @@ +/* + * 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.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target( ElementType.TYPE ) +@Inherited +public @interface EnhancementOptions { + boolean biDirectionalAssociationManagement() default false; + boolean inlineDirtyChecking() default false; + boolean lazyLoading() default false; + boolean extendedEnhancement() default false; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementSelector.java new file mode 100644 index 0000000000..2a598c4984 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementSelector.java @@ -0,0 +1,20 @@ +/* + * 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.testing.bytecode.enhancement; + +/** + * Used by {@link BytecodeEnhancerRunner} to determine which classes should + * be enhanced. + * + * @author Steve Ebersole + */ +public interface EnhancementSelector { + /** + * Determine whether the named class should be enhanced. + */ + boolean select(String name); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EverythingSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EverythingSelector.java new file mode 100644 index 0000000000..f2249fcac9 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EverythingSelector.java @@ -0,0 +1,22 @@ +/* + * 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.testing.bytecode.enhancement; + +/** + * @author Steve Ebersole + */ +public class EverythingSelector implements EnhancementSelector { + /** + * Singleton access + */ + public static final EverythingSelector INSTANCE = new EverythingSelector(); + + @Override + public boolean select(String name) { + return true; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelector.java new file mode 100644 index 0000000000..93bbded1f2 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelector.java @@ -0,0 +1,25 @@ +/* + * 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.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +@Repeatable( ImplEnhancementSelectors.class ) +public @interface ImplEnhancementSelector { + Class impl(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelectors.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelectors.java new file mode 100644 index 0000000000..bf11867a78 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelectors.java @@ -0,0 +1,23 @@ +/* + * 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.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +public @interface ImplEnhancementSelectors { + ImplEnhancementSelector[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelector.java new file mode 100644 index 0000000000..b0deaf9015 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelector.java @@ -0,0 +1,27 @@ +/* + * 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.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +@Repeatable( PackageEnhancementSelectors.class ) +public @interface PackageEnhancementSelector { + String value(); + + boolean includeSubPackages() default true; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelectors.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelectors.java new file mode 100644 index 0000000000..c4c611d401 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelectors.java @@ -0,0 +1,23 @@ +/* + * 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.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +public @interface PackageEnhancementSelectors { + PackageEnhancementSelector[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageSelector.java new file mode 100644 index 0000000000..1983607848 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageSelector.java @@ -0,0 +1,25 @@ +/* + * 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.testing.bytecode.enhancement; + +/** + * EnhancementSelector based on package name + * + * @author Steve Ebersole + */ +public class PackageSelector implements EnhancementSelector { + private final String packageName; + + public PackageSelector(String packageName) { + this.packageName = packageName; + } + + @Override + public boolean select(String name) { + return name.startsWith( packageName ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseNonConfigCoreFunctionalTestCase.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseNonConfigCoreFunctionalTestCase.java index 37257aeec2..7a9640806f 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseNonConfigCoreFunctionalTestCase.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseNonConfigCoreFunctionalTestCase.java @@ -16,10 +16,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.function.Consumer; +import java.util.function.Function; import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.Session; +import org.hibernate.StatelessSession; import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.MetadataSources; @@ -555,8 +557,28 @@ public class BaseNonConfigCoreFunctionalTestCase extends BaseUnitTestCase { TransactionUtil2.inSession( sessionFactory(), action ); } + public void inStatelessSession(Consumer action) { + log.trace( "#inSession(action)" ); + TransactionUtil2.inStatelessSession( sessionFactory(), action ); + } + + public R fromSession(Function action) { + log.trace( "#inSession(action)" ); + return TransactionUtil2.fromSession( sessionFactory(), action ); + } + public void inTransaction(Consumer action) { log.trace( "#inTransaction(action)" ); TransactionUtil2.inTransaction( sessionFactory(), action ); } + + public void inStatelessTransaction(Consumer action) { + log.trace( "#inTransaction(action)" ); + TransactionUtil2.inStatelessTransaction( sessionFactory(), action ); + } + + public R fromTransaction(Function action) { + log.trace( "#inTransaction(action)" ); + return TransactionUtil2.fromTransaction( sessionFactory(), action ); + } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil2.java b/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil2.java index d91a3f64e8..c8c834182b 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil2.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil2.java @@ -7,7 +7,9 @@ package org.hibernate.testing.transaction; import java.util.function.Consumer; +import java.util.function.Function; +import org.hibernate.StatelessSession; import org.hibernate.Transaction; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -21,6 +23,10 @@ public class TransactionUtil2 { private static final Logger log = Logger.getLogger( TransactionUtil2.class ); public static final String ACTION_COMPLETED_TXN = "Execution of action caused managed transaction to be completed"; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // in/from Session + public static void inSession(SessionFactoryImplementor sfi, Consumer action) { log.trace( "#inSession(SF,action)" ); @@ -34,15 +40,33 @@ public class TransactionUtil2 { } } + public static R fromSession(SessionFactoryImplementor sfi, Function action) { + log.trace( "#inSession(SF,action)" ); + + try (SessionImplementor session = (SessionImplementor) sfi.openSession()) { + log.trace( "Session opened, calling action" ); + return action.apply( session ); + } + finally { + log.trace( "Session closed (AutoCloseable)" ); + } + } + public static void inTransaction(SessionFactoryImplementor factory, Consumer action) { log.trace( "#inTransaction(factory, action)"); inSession( factory, - session -> { - inTransaction( session, action ); - } + session -> inTransaction( session, action ) + ); + } + public static R fromTransaction(SessionFactoryImplementor factory, Function action) { + log.trace( "#inTransaction(factory, action)"); + + return fromSession( + factory, + session -> fromTransaction( session, action ) ); } @@ -95,6 +119,138 @@ public class TransactionUtil2 { } } + public static R fromTransaction(SessionImplementor session, Function action) { + log.trace( "inTransaction(session,action)" ); + + final Transaction txn = session.beginTransaction(); + log.trace( "Started transaction" ); + + final R result; + try { + log.trace( "Calling action in txn" ); + result = action.apply( session ); + log.trace( "Called action - in txn" ); + + if ( !txn.isActive() ) { + throw new TransactionManagementException( ACTION_COMPLETED_TXN ); + } + } + catch (Exception e) { + // an error happened in the action + if ( ! txn.isActive() ) { + log.warn( ACTION_COMPLETED_TXN, e ); + } + else { + log.trace( "Rolling back transaction due to action error" ); + try { + txn.rollback(); + log.trace( "Rolled back transaction due to action error" ); + } + catch (Exception inner) { + log.trace( "Rolling back transaction due to action error failed; throwing original error" ); + } + } + + throw e; + } + + assert result != null; + + // action completed with no errors - attempt to commit the transaction allowing + // any RollbackException to propagate. Note that when we get here we know the + // txn is active + + log.trace( "Committing transaction after successful action execution" ); + try { + txn.commit(); + log.trace( "Committing transaction after successful action execution - success" ); + } + catch (Exception e) { + log.trace( "Committing transaction after successful action execution - failure" ); + throw e; + } + + return result; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // in/from StatelessSession + + public static void inStatelessSession(SessionFactoryImplementor sfi, Consumer action) { + log.trace( "#inSession(SF,action)" ); + + try (StatelessSession session = sfi.openStatelessSession()) { + log.trace( "StatelessSession opened, calling action" ); + action.accept( session ); + log.trace( "called action" ); + } + finally { + log.trace( "Session closed (AutoCloseable)" ); + } + } + + + public static void inStatelessTransaction(SessionFactoryImplementor factory, Consumer action) { + log.trace( "#inTransaction(factory, action)"); + + inStatelessSession( + factory, + session -> inStatelessTransaction( session, action ) + ); + } + + public static void inStatelessTransaction(StatelessSession session, Consumer action) { + log.trace( "inTransaction(session,action)" ); + + final Transaction txn = session.beginTransaction(); + log.trace( "Started transaction" ); + + try { + log.trace( "Calling action in txn" ); + action.accept( session ); + log.trace( "Called action - in txn" ); + + if ( !txn.isActive() ) { + throw new TransactionManagementException( ACTION_COMPLETED_TXN ); + } + } + catch (Exception e) { + // an error happened in the action + if ( ! txn.isActive() ) { + log.warn( ACTION_COMPLETED_TXN, e ); + } + else { + log.trace( "Rolling back transaction due to action error" ); + try { + txn.rollback(); + log.trace( "Rolled back transaction due to action error" ); + } + catch (Exception inner) { + log.trace( "Rolling back transaction due to action error failed; throwing original error" ); + } + } + + throw e; + } + + // action completed with no errors - attempt to commit the transaction allowing + // any RollbackException to propagate. Note that when we get here we know the + // txn is active + + log.trace( "Committing transaction after successful action execution" ); + try { + txn.commit(); + log.trace( "Committing transaction after successful action execution - success" ); + } + catch (Exception e) { + log.trace( "Committing transaction after successful action execution - failure" ); + throw e; + } + } + + + private static class TransactionManagementException extends RuntimeException { public TransactionManagementException(String message) { super( message );