diff --git a/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java b/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java index de68078d28..8556485ba2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java @@ -62,10 +62,10 @@ public class MetadataSources implements Serializable { private XmlMappingBinderAccess xmlMappingBinderAccess; - private List xmlBindings = new ArrayList<>(); - private LinkedHashSet> annotatedClasses = new LinkedHashSet<>(); - private LinkedHashSet annotatedClassNames = new LinkedHashSet<>(); - private LinkedHashSet annotatedPackages = new LinkedHashSet<>(); + private List xmlBindings; + private LinkedHashSet> annotatedClasses; + private LinkedHashSet annotatedClassNames; + private LinkedHashSet annotatedPackages; private Map> extraQueryImports; @@ -81,14 +81,15 @@ public class MetadataSources implements Serializable { public MetadataSources(ServiceRegistry serviceRegistry) { // service registry really should be either BootstrapServiceRegistry or StandardServiceRegistry type... if ( ! isExpectedServiceRegistryType( serviceRegistry ) ) { - LOG.debugf( - "Unexpected ServiceRegistry type [%s] encountered during building of MetadataSources; may cause " + - "problems later attempting to construct MetadataBuilder", - serviceRegistry.getClass().getName() - ); + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "Unexpected ServiceRegistry type [%s] encountered during building of MetadataSources; may cause " + + "problems later attempting to construct MetadataBuilder", + serviceRegistry.getClass().getName() + ); + } } this.serviceRegistry = serviceRegistry; - this.xmlMappingBinderAccess = new XmlMappingBinderAccess( serviceRegistry ); } protected static boolean isExpectedServiceRegistryType(ServiceRegistry serviceRegistry) { @@ -97,23 +98,26 @@ public class MetadataSources implements Serializable { } public XmlMappingBinderAccess getXmlMappingBinderAccess() { + if ( xmlMappingBinderAccess == null ) { + xmlMappingBinderAccess = new XmlMappingBinderAccess( serviceRegistry ); + } return xmlMappingBinderAccess; } public List getXmlBindings() { - return xmlBindings; + return xmlBindings == null ? Collections.emptyList() : xmlBindings; } public Collection getAnnotatedPackages() { - return annotatedPackages; + return annotatedPackages == null ? Collections.emptySet() : annotatedPackages; } public Collection> getAnnotatedClasses() { - return annotatedClasses; + return annotatedClasses == null ? Collections.emptySet() : annotatedClasses; } public Collection getAnnotatedClassNames() { - return annotatedClassNames; + return annotatedClassNames == null ? Collections.emptySet() : annotatedClassNames; } public Map> getExtraQueryImports() { @@ -201,6 +205,9 @@ public class MetadataSources implements Serializable { * @return this (for method chaining) */ public MetadataSources addAnnotatedClass(Class annotatedClass) { + if ( annotatedClasses == null ) { + annotatedClasses = new LinkedHashSet<>(); + } annotatedClasses.add( annotatedClass ); return this; } @@ -225,6 +232,9 @@ public class MetadataSources implements Serializable { * @return this (for method chaining) */ public MetadataSources addAnnotatedClassName(String annotatedClassName) { + if ( annotatedClassNames == null ) { + annotatedClassNames = new LinkedHashSet<>(); + } annotatedClassNames.add( annotatedClassName ); return this; } @@ -265,11 +275,17 @@ public class MetadataSources implements Serializable { packageName = packageName.substring( 0, packageName.length() - 1 ); } - annotatedPackages.add( packageName ); - + addPackageInternal( packageName ); return this; } + private void addPackageInternal(String packageName) { + if ( annotatedPackages == null ) { + annotatedPackages = new LinkedHashSet<>(); + } + annotatedPackages.add( packageName ); + } + /** * Read package-level metadata. * @@ -278,7 +294,7 @@ public class MetadataSources implements Serializable { * @return this (for method chaining) */ public MetadataSources addPackage(Package packageRef) { - annotatedPackages.add( packageRef.getName() ); + addPackageInternal( packageRef.getName() ); return this; } @@ -297,7 +313,9 @@ public class MetadataSources implements Serializable { if ( entityClass == null ) { throw new IllegalArgumentException( "The specified class cannot be null" ); } - LOG.debugf( "adding resource mappings from class convention : %s", entityClass.getName() ); + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "adding resource mappings from class convention : %s", entityClass.getName() ); + } final String mappingResourceName = entityClass.getName().replace( '.', '/' ) + ".hbm.xml"; addResource( mappingResourceName ); return this; @@ -311,7 +329,7 @@ public class MetadataSources implements Serializable { * @return this (for method chaining purposes) */ public MetadataSources addResource(String name) { - xmlBindings.add( getXmlMappingBinderAccess().bind( name ) ); + getXmlBindingsForWrite().add( getXmlMappingBinderAccess().bind( name ) ); return this; } @@ -337,7 +355,7 @@ public class MetadataSources implements Serializable { * @return this (for method chaining purposes) */ public MetadataSources addFile(File file) { - xmlBindings.add( getXmlMappingBinderAccess().bind( file ) ); + getXmlBindingsForWrite().add( getXmlMappingBinderAccess().bind( file ) ); return this; } @@ -357,7 +375,7 @@ public class MetadataSources implements Serializable { } private void addCacheableFile(Origin origin, File file) { - xmlBindings.add( new CacheableFileXmlSource( origin, file, false ).doBind( getXmlMappingBinderAccess().getMappingBinder() ) ); + getXmlBindingsForWrite().add( new CacheableFileXmlSource( origin, file, false ).doBind( getXmlMappingBinderAccess().getMappingBinder() ) ); } /** @@ -394,7 +412,7 @@ public class MetadataSources implements Serializable { */ public MetadataSources addCacheableFileStrictly(File file) throws SerializationException, FileNotFoundException { final Origin origin = new Origin( SourceType.FILE, file.getAbsolutePath() ); - xmlBindings.add( new CacheableFileXmlSource( origin, file, true ).doBind( getXmlMappingBinderAccess().getMappingBinder() ) ); + getXmlBindingsForWrite().add( new CacheableFileXmlSource( origin, file, true ).doBind( getXmlMappingBinderAccess().getMappingBinder() ) ); return this; } @@ -406,7 +424,7 @@ public class MetadataSources implements Serializable { * @return this (for method chaining purposes) */ public MetadataSources addInputStream(InputStreamAccess xmlInputStreamAccess) { - xmlBindings.add( getXmlMappingBinderAccess().bind( xmlInputStreamAccess ) ); + getXmlBindingsForWrite().add( getXmlMappingBinderAccess().bind( xmlInputStreamAccess ) ); return this; } @@ -418,7 +436,7 @@ public class MetadataSources implements Serializable { * @return this (for method chaining purposes) */ public MetadataSources addInputStream(InputStream xmlInputStream) { - xmlBindings.add( getXmlMappingBinderAccess().bind( xmlInputStream ) ); + getXmlBindingsForWrite().add( getXmlMappingBinderAccess().bind( xmlInputStream ) ); return this; } @@ -430,7 +448,7 @@ public class MetadataSources implements Serializable { * @return this (for method chaining purposes) */ public MetadataSources addURL(URL url) { - xmlBindings.add( getXmlMappingBinderAccess().bind( url ) ); + getXmlBindingsForWrite().add( getXmlMappingBinderAccess().bind( url ) ); return this; } @@ -446,7 +464,7 @@ public class MetadataSources implements Serializable { @Deprecated public MetadataSources addDocument(Document document) { final Origin origin = new Origin( SourceType.DOM, Origin.UNKNOWN_FILE_PATH ); - xmlBindings.add( new JaxpSourceXmlSource( origin, new DOMSource( document ) ).doBind( getXmlMappingBinderAccess().getMappingBinder() ) ); + getXmlBindingsForWrite().add( new JaxpSourceXmlSource( origin, new DOMSource( document ) ).doBind( getXmlMappingBinderAccess().getMappingBinder() ) ); return this; } @@ -460,17 +478,22 @@ public class MetadataSources implements Serializable { * @return this (for method chaining purposes) */ public MetadataSources addJar(File jar) { - LOG.debugf( "Seeking mapping documents in jar file : %s", jar.getName() ); + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "Seeking mapping documents in jar file : %s", jar.getName() ); + } final Origin origin = new Origin( SourceType.JAR, jar.getAbsolutePath() ); try { JarFile jarFile = new JarFile( jar ); + final boolean TRACE = LOG.isTraceEnabled(); try { Enumeration jarEntries = jarFile.entries(); while ( jarEntries.hasMoreElements() ) { final ZipEntry zipEntry = (ZipEntry) jarEntries.nextElement(); if ( zipEntry.getName().endsWith( ".hbm.xml" ) ) { - LOG.tracef( "found mapping document : %s", zipEntry.getName() ); - xmlBindings.add( + if ( TRACE ) { + LOG.tracef( "found mapping document : %s", zipEntry.getName() ); + } + getXmlBindingsForWrite().add( new JarFileEntryXmlSource( origin, jarFile, zipEntry ).doBind( getXmlMappingBinderAccess().getMappingBinder() ) ); } @@ -490,6 +513,13 @@ public class MetadataSources implements Serializable { return this; } + private List getXmlBindingsForWrite() { + if ( xmlBindings == null ) { + xmlBindings = new ArrayList<>(); + } + return xmlBindings; + } + /** * Read all mapping documents from a directory tree. *

diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java index 6f9b88b7a6..44d304fa0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java @@ -437,7 +437,7 @@ public class MapBinder extends CollectionBinder { referencedEntityColumns = referencedProperty.getColumnIterator(); } fromAndWhere = getFromAndWhereFormula( - associatedClass.getTable().getName(), + associatedClass.getTable().getQualifiedTableName().toString(), element.getColumnIterator(), referencedEntityColumns ); diff --git a/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java b/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java index b87cab0e14..c49cfc0e2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java @@ -332,6 +332,7 @@ public class ThreadLocalSessionContext extends AbstractCurrentSessionContext { || "getTransaction".equals( methodName ) || "isTransactionInProgress".equals( methodName ) || "setFlushMode".equals( methodName ) + || "setHibernateFlushMode".equals( methodName ) || "getFactory".equals( methodName ) || "getSessionFactory".equals( methodName ) || "getTenantIdentifier".equals( methodName ) ) { 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 8e4d8e0be6..2bc9794cfb 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 @@ -260,15 +260,6 @@ 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. 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 d2b0ab1d7c..bd73ad8e1b 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 @@ -299,15 +299,18 @@ public class DefaultLoadEventListener implements LoadEventListener { return createProxy( event, persister, keyToLoad, persistenceContext ); } } + if ( !entityMetamodel.hasSubclasses() ) { + if ( keyToLoad.isBatchLoadable() ) { + // Add a batch-fetch entry into the queue for this entity + persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad ); + } - if ( keyToLoad.isBatchLoadable() ) { - // Add a batch-fetch entry into the queue for this entity - persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad ); + // This is the crux of HHH-11147 + // create the (uninitialized) entity instance - has only id set + return persister.getBytecodeEnhancementMetadata().createEnhancedProxy( keyToLoad, true, session ); } - - // This is the crux of HHH-11147 - // create the (uninitialized) entity instance - has only id set - return persister.getBytecodeEnhancementMetadata().createEnhancedProxy( keyToLoad, true, session ); + // If we get here, then the entity class has subclasses and there is no HibernateProxy factory. + // The entity will get loaded below. } else { if ( persister.hasProxy() ) { 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 efdf2b45d4..9dc50b69db 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -975,18 +975,7 @@ public final class SessionImpl 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, - Boolean unwrapProxy) { + boolean nullable) { final EffectiveEntityGraph effectiveEntityGraph = getLoadQueryInfluencers().getEffectiveEntityGraph(); final GraphSemantic semantic = effectiveEntityGraph.getSemantic(); final RootGraphImplementor graph = effectiveEntityGraph.getGraph(); 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 8b566f4638..566cc2db46 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -298,23 +298,48 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = entityMetamodel.getBytecodeEnhancementMetadata(); if ( allowBytecodeProxy && bytecodeEnhancementMetadata.isEnhancedForLazyLoading() ) { - // we cannot use bytecode proxy for entities with subclasses - if ( !entityMetamodel.hasSubclasses() ) { + // if the entity defines a HibernateProxy factory, see if there is an + // existing proxy associated with the PC - and if so, use it + if ( persister.getEntityMetamodel().getTuplizer().getProxyFactory() != null ) { + final PersistenceContext persistenceContext = getPersistenceContext(); + final Object proxy = persistenceContext.getProxy( entityKey ); + + if ( proxy != null ) { + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Entity proxy found in session cache" ); + } + if ( LOG.isDebugEnabled() && ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUnwrap() ) { + LOG.debug( "Ignoring NO_PROXY to honor laziness" ); + } + + return persistenceContext.narrowProxy( proxy, persister, entityKey, null ); + } + + // specialized handling for entities with subclasses with a HibernateProxy factory + if ( entityMetamodel.hasSubclasses() ) { + // entities with subclasses that define a ProxyFactory can create + // a HibernateProxy. + LOG.debugf( "Creating a HibernateProxy for to-one association with subclasses to honor laziness" ); + return createProxy( entityKey ); + } return bytecodeEnhancementMetadata.createEnhancedProxy( entityKey, false, this ); } - } - - // we could not use bytecode proxy, check to see if we can use HibernateProxy - if ( persister.hasProxy() ) { - final PersistenceContext persistenceContext = getPersistenceContext(); - final Object existingProxy = persistenceContext.getProxy( entityKey ); - if ( existingProxy != null ) { - return persistenceContext.narrowProxy( existingProxy, persister, entityKey, null ); + else if ( !entityMetamodel.hasSubclasses() ) { + return bytecodeEnhancementMetadata.createEnhancedProxy( entityKey, false, this ); } - else { - final Object proxy = persister.createProxy( id, this ); - persistenceContext.addProxy( entityKey, proxy ); - return proxy; + // If we get here, then the entity class has subclasses and there is no HibernateProxy factory. + // The entity will get loaded below. + } + else { + if ( persister.hasProxy() ) { + final PersistenceContext persistenceContext = getPersistenceContext(); + final Object existingProxy = persistenceContext.getProxy( entityKey ); + if ( existingProxy != null ) { + return persistenceContext.narrowProxy( existingProxy, persister, entityKey, null ); + } + else { + return createProxy( entityKey ); + } } } } @@ -323,6 +348,12 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen return get( entityName, id ); } + private Object createProxy(EntityKey entityKey) { + final Object proxy = entityKey.getPersister().createProxy( entityKey.getIdentifier(), this ); + getPersistenceContext().addProxy( entityKey, proxy ); + return proxy; + } + @Override public boolean isAutoCloseSessionEnabled() { return getFactory().getSessionFactoryOptions().isAutoCloseSessionEnabled(); 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 98ac9c773a..65361816ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -1733,16 +1733,21 @@ public abstract class Loader { // see if the entity defines reference caching, and if so use the cached reference (if one). if ( session.getCacheMode().isGetEnabled() && persister.canUseReferenceCacheEntries() ) { final EntityDataAccess cache = persister.getCacheAccessStrategy(); - final Object ck = cache.generateCacheKey( - key.getIdentifier(), - persister, - session.getFactory(), - session.getTenantIdentifier() + if ( cache != null ) { + final Object ck = cache.generateCacheKey( + key.getIdentifier(), + persister, + session.getFactory(), + session.getTenantIdentifier() + ); + final Object cachedEntry = CacheHelper.fromSharedCache( session, ck, cache ); + if ( cachedEntry != null ) { + CacheEntry entry = (CacheEntry) persister.getCacheEntryStructure().destructure( + cachedEntry, + factory ); - final Object cachedEntry = CacheHelper.fromSharedCache( session, ck, cache ); - if ( cachedEntry != null ) { - CacheEntry entry = (CacheEntry) persister.getCacheEntryStructure().destructure( cachedEntry, factory ); - return ( (ReferenceCacheEntryImpl) entry ).getReference(); + return ( (ReferenceCacheEntryImpl) entry ).getReference(); + } } } 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 1521b4ea32..7dec4e963d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -655,8 +655,7 @@ public abstract class EntityType extends AbstractType implements AssociationType getAssociatedEntityName(), id, eager, - isNullable(), - unwrapProxy + isNullable() ); if ( proxyOrEntity instanceof HibernateProxy ) { diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceTest.java new file mode 100644 index 0000000000..e9be4a677e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceTest.java @@ -0,0 +1,306 @@ +/* + * 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.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +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.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.StatelessSession; +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.dialect.DB2Dialect; +import org.hibernate.query.Query; +import org.hibernate.stat.spi.StatisticsImplementor; + +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.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Andrea Boriero + */ +@RunWith(BytecodeEnhancerRunner.class) +public class QueryScrollingWithInheritanceTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + } + + @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( EmployeeParent.class ); + sources.addAnnotatedClass( Employee.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void testScrollableWithStatelessSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + + try { + statelessSession.beginTransaction(); + Query query = statelessSession.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( false ) ); + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + statelessSession.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( statelessSession.getTransaction().isActive() ) { + statelessSession.getTransaction().rollback(); + } + statelessSession.close(); + } + } + + @Test + public void testScrollableWithSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final Session session = sessionFactory().openSession(); + + try { + session.beginTransaction(); + Query query = session.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( false ) ); + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + session.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( session.getTransaction().isActive() ) { + session.getTransaction().rollback(); + } + session.close(); + } + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + Employee e1 = new Employee( "ENG1" ); + Employee e2 = new Employee( "ENG2" ); + OtherEntity other1 = new OtherEntity( "test1" ); + OtherEntity other2 = new OtherEntity( "test2" ); + e1.getOtherEntities().add( other1 ); + e1.getOtherEntities().add( other2 ); + e1.getParentOtherEntities().add( other1 ); + e1.getParentOtherEntities().add( other2 ); + other1.employee = e1; + other2.employee = e1; + other1.employeeParent = e1; + other2.employeeParent = e2; + session.persist( other1 ); + session.persist( other2 ); + session.persist( e1 ); + session.persist( e2 ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from EmployeeParent" ).executeUpdate(); + } + ); + } + + @Entity(name = "EmployeeParent") + @Table(name = "EmployeeParent") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static abstract class EmployeeParent { + + @Id + private String dept; + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employeeParent", fetch = FetchType.LAZY) + protected Set parentOtherEntities = new HashSet<>(); + + public Set getParentOtherEntities() { + if ( parentOtherEntities == null ) { + parentOtherEntities = new LinkedHashSet(); + } + return parentOtherEntities; + } + + public void setOtherEntities(Set pParentOtherEntites) { + parentOtherEntities = pParentOtherEntites; + } + + public String getDept() { + return dept; + } + + protected void setDept(String dept) { + this.dept = dept; + } + + } + + @Entity(name = "Employee") + @Table(name = "Employee") + public static class Employee extends EmployeeParent { + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employee", fetch = FetchType.LAZY) + protected Set otherEntities = new HashSet<>(); + + public Employee(String dept) { + this(); + setDept( dept ); + } + + protected Employee() { + // this form used by Hibernate + } + + public Set getOtherEntities() { + if ( otherEntities == null ) { + otherEntities = new LinkedHashSet(); + } + return otherEntities; + } + + public void setOtherEntities(Set pOtherEntites) { + otherEntities = pOtherEntites; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "Employee_Id") + protected Employee employee = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "EmployeeParent_Id") + protected EmployeeParent employeeParent = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesNoProxyFactoryWithSubclassesStatefulTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesNoProxyFactoryWithSubclassesStatefulTest.java new file mode 100644 index 0000000000..b6b7840f8b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesNoProxyFactoryWithSubclassesStatefulTest.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.proxy; + +import java.io.Serializable; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.EntityMode; +import org.hibernate.EntityNameResolver; +import org.hibernate.Hibernate; +import org.hibernate.HibernateException; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.annotations.Tuplizer; +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.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.property.access.spi.Getter; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.ProxyFactory; +import org.hibernate.stat.Statistics; +import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.tuple.entity.EntityTuplizer; +import org.hibernate.tuple.entity.PojoEntityTuplizer; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +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.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +@TestForIssue( jiraKey = "HHH-13640" ) +@RunWith(BytecodeEnhancerRunner.class) +public class LazyToOnesNoProxyFactoryWithSubclassesStatefulTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "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( Animal.class ); + sources.addAnnotatedClass( Primate.class ); + sources.addAnnotatedClass( Human.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void testNewEnhancedProxyAssociation() { + inTransaction( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.human = human; + + session.persist( human ); + session.persist( otherEntity ); + } + ); + + inSession( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.animal ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testExistingInitializedAssociationLeafSubclass() { + inTransaction( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.animal = human; + otherEntity.primate = human; + otherEntity.human = human; + session.persist( human ); + session.persist( otherEntity ); + } + ); + + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + inSession( + session -> { + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "animal" ) ); + assertTrue( Hibernate.isInitialized( otherEntity.animal ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.animal ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "primate" ) ); + assertTrue( Hibernate.isInitialized( otherEntity.primate ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.primate ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertTrue( Hibernate.isInitialized( otherEntity.human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.human ) ); + assertSame( otherEntity.human, otherEntity.animal ); + assertSame( otherEntity.human, otherEntity.primate ); + assertEquals( 2, stats.getPrepareStatementCount() ); + } + ); + + assertEquals( 2, stats.getPrepareStatementCount() ); + } + + @Test + public void testExistingEnhancedProxyAssociationLeafSubclassOnly() { + inTransaction( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.human = human; + otherEntity.otherHuman = human; + session.persist( human ); + session.persist( otherEntity ); + } + ); + + inSession( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertNull( otherEntity.animal ); + assertNull( otherEntity.primate ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.human ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "otherHuman" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.otherHuman ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.otherHuman ) ); + assertSame( otherEntity.human, otherEntity.otherHuman ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Human" ).executeUpdate(); + session.createQuery( "delete from Primate" ).executeUpdate(); + session.createQuery( "delete from Animal" ).executeUpdate(); + } + ); + } + + @Entity(name = "Animal") + @Table(name = "Animal") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + @Tuplizer(impl=NoProxyFactoryPojoEntityTuplizer.class) + public static abstract class Animal { + + @Id + private String name; + + private int age; + + public String getName() { + return name; + } + + protected void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + } + + @Entity(name = "Primate") + @Table(name = "Primate") + public static class Primate extends Animal { + + public Primate(String name) { + this(); + setName( name ); + } + + protected Primate() { + // this form used by Hibernate + } + } + + @Entity(name = "Human") + @Table(name = "Human") + public static class Human extends Primate { + + private String sex; + + public Human(String name) { + this(); + setName( name ); + } + + protected Human() { + // this form used by Hibernate + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Animal animal = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Primate primate = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Human human = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Human otherHuman = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public Human getHuman() { + return human; + } + + public void setHuman(Human human) { + this.human = human; + } + } + + public static class NoProxyFactoryPojoEntityTuplizer implements EntityTuplizer { + + private final PojoEntityTuplizer pojoEntityTuplizer; + + public NoProxyFactoryPojoEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) { + pojoEntityTuplizer = new PojoEntityTuplizer( entityMetamodel, mappedEntity ); + } + + @Override + public EntityMode getEntityMode() { + return pojoEntityTuplizer.getEntityMode(); + } + + @Override + public Object instantiate(Serializable id) throws HibernateException { + return pojoEntityTuplizer.instantiate( id ); + } + + @Override + public Object instantiate(Serializable id, SharedSessionContractImplementor session) { + return pojoEntityTuplizer.instantiate( id, session ); + + } + + @Override + public Serializable getIdentifier(Object entity) throws HibernateException { + return pojoEntityTuplizer.getIdentifier( entity ); + } + + @Override + public Serializable getIdentifier(Object entity, SharedSessionContractImplementor session) { + return pojoEntityTuplizer.getIdentifier( entity, session ); + } + + @Override + public void setIdentifier(Object entity, Serializable id) throws HibernateException { + pojoEntityTuplizer.setIdentifier( entity, id ); + } + + @Override + public void setIdentifier(Object entity, Serializable id, SharedSessionContractImplementor session) { + pojoEntityTuplizer.setIdentifier( entity, id, session ); + } + + @Override + public void resetIdentifier(Object entity, Serializable currentId, Object currentVersion) { + pojoEntityTuplizer.resetIdentifier( entity, currentId, currentVersion ); + } + + @Override + public void resetIdentifier( + Object entity, + Serializable currentId, + Object currentVersion, + SharedSessionContractImplementor session) { + pojoEntityTuplizer.resetIdentifier( entity, currentId, currentVersion, session ); + } + + @Override + public Object getVersion(Object entity) throws HibernateException { + return pojoEntityTuplizer.getVersion( entity ); + } + + @Override + public void setPropertyValue(Object entity, int i, Object value) throws HibernateException { + pojoEntityTuplizer. setPropertyValue( entity, i, value ); + } + + @Override + public void setPropertyValue(Object entity, String propertyName, Object value) throws HibernateException { + pojoEntityTuplizer.setPropertyValue( entity, propertyName, value ); + } + + @Override + public Object[] getPropertyValuesToInsert( + Object entity, + Map mergeMap, + SharedSessionContractImplementor session) throws HibernateException { + return pojoEntityTuplizer.getPropertyValuesToInsert( entity, mergeMap, session ); + } + + @Override + public Object getPropertyValue(Object entity, String propertyName) throws HibernateException { + return pojoEntityTuplizer.getPropertyValue( entity, propertyName ); + } + + @Override + public void afterInitialize(Object entity, SharedSessionContractImplementor session) { + pojoEntityTuplizer.afterInitialize( entity, session ); + + } + + @Override + public boolean hasProxy() { + return pojoEntityTuplizer.hasProxy(); + } + + @Override + public Object createProxy(Serializable id, SharedSessionContractImplementor session) throws HibernateException { + return pojoEntityTuplizer.createProxy( id, session ); + } + + @Override + public boolean isLifecycleImplementor() { + return pojoEntityTuplizer.isLifecycleImplementor(); + } + + @Override + public Class getConcreteProxyClass() { + return pojoEntityTuplizer.getConcreteProxyClass(); + } + + @Override + public EntityNameResolver[] getEntityNameResolvers() { + return pojoEntityTuplizer.getEntityNameResolvers(); + } + + @Override + public String determineConcreteSubclassEntityName( + Object entityInstance, SessionFactoryImplementor factory) { + return pojoEntityTuplizer.determineConcreteSubclassEntityName( entityInstance, factory ); + } + + @Override + public Getter getIdentifierGetter() { + return pojoEntityTuplizer.getIdentifierGetter(); + } + + @Override + public Getter getVersionGetter() { + return pojoEntityTuplizer.getVersionGetter(); + } + + @Override + public ProxyFactory getProxyFactory() { + return null; + } + + @Override + public Object[] getPropertyValues(Object entity) { + return pojoEntityTuplizer.getPropertyValues( entity ); + } + + @Override + public void setPropertyValues(Object entity, Object[] values) { + pojoEntityTuplizer.setPropertyValues( entity, values ); + } + + @Override + public Object getPropertyValue(Object entity, int i) { + return pojoEntityTuplizer.getPropertyValue( entity, i ); + } + + @Override + public Object instantiate() { + return pojoEntityTuplizer.instantiate(); + } + + @Override + public boolean isInstance(Object object) { + return pojoEntityTuplizer.isInstance( object ); + } + + @Override + public Class getMappedClass() { + return pojoEntityTuplizer.getMappedClass(); + } + + @Override + public Getter getGetter(int i) { + return pojoEntityTuplizer.getGetter( i ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesNoProxyFactoryWithSubclassesStatelessTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesNoProxyFactoryWithSubclassesStatelessTest.java new file mode 100644 index 0000000000..9ba59657e3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesNoProxyFactoryWithSubclassesStatelessTest.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.proxy; + +import java.io.Serializable; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.EntityMode; +import org.hibernate.EntityNameResolver; +import org.hibernate.Hibernate; +import org.hibernate.HibernateException; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.annotations.Tuplizer; +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.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.property.access.spi.Getter; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.ProxyFactory; +import org.hibernate.stat.Statistics; +import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.tuple.entity.EntityTuplizer; +import org.hibernate.tuple.entity.PojoEntityTuplizer; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +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.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +@TestForIssue( jiraKey = "HHH-13640" ) +@RunWith(BytecodeEnhancerRunner.class) +public class LazyToOnesNoProxyFactoryWithSubclassesStatelessTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "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( Animal.class ); + sources.addAnnotatedClass( Primate.class ); + sources.addAnnotatedClass( Human.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void testNewEnhancedProxyAssociation() { + inStatelessTransaction( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.human = human; + + session.insert( human ); + session.insert( otherEntity ); + } + ); + + inStatelessSession( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + final OtherEntity otherEntity = (OtherEntity) session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.animal ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testExistingInitializedAssociationLeafSubclass() { + inStatelessSession( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.animal = human; + otherEntity.primate = human; + otherEntity.human = human; + session.insert( human ); + session.insert( otherEntity ); + } + ); + + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + inStatelessSession( + session -> { + + final OtherEntity otherEntity = (OtherEntity) session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "animal" ) ); + assertTrue( Hibernate.isInitialized( otherEntity.animal ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.animal ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "primate" ) ); + assertTrue( Hibernate.isInitialized( otherEntity.primate ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.primate ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertTrue( Hibernate.isInitialized( otherEntity.human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.human ) ); + assertSame( otherEntity.human, otherEntity.animal ); + assertSame( otherEntity.human, otherEntity.primate ); + assertEquals( 2, stats.getPrepareStatementCount() ); + } + ); + + assertEquals( 2, stats.getPrepareStatementCount() ); + } + + @Test + public void testExistingEnhancedProxyAssociationLeafSubclassOnly() { + inStatelessSession( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.human = human; + otherEntity.otherHuman = human; + session.insert( human ); + session.insert( otherEntity ); + } + ); + + inStatelessSession( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final OtherEntity otherEntity = (OtherEntity) session.get( OtherEntity.class, "test1" ); + assertNull( otherEntity.animal ); + assertNull( otherEntity.primate ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.human ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "otherHuman" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.otherHuman ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.otherHuman ) ); + assertSame( otherEntity.human, otherEntity.otherHuman ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Human" ).executeUpdate(); + session.createQuery( "delete from Primate" ).executeUpdate(); + session.createQuery( "delete from Animal" ).executeUpdate(); + } + ); + } + + @Entity(name = "Animal") + @Table(name = "Animal") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + @Tuplizer(impl=NoProxyFactoryPojoEntityTuplizer.class) + public static abstract class Animal { + + @Id + private String name; + + private int age; + + public String getName() { + return name; + } + + protected void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + } + + @Entity(name = "Primate") + @Table(name = "Primate") + public static class Primate extends Animal { + + public Primate(String name) { + this(); + setName( name ); + } + + protected Primate() { + // this form used by Hibernate + } + } + + @Entity(name = "Human") + @Table(name = "Human") + public static class Human extends Primate { + + private String sex; + + public Human(String name) { + this(); + setName( name ); + } + + protected Human() { + // this form used by Hibernate + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Animal animal = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Primate primate = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Human human = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Human otherHuman = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public Human getHuman() { + return human; + } + + public void setHuman(Human human) { + this.human = human; + } + } + + public static class NoProxyFactoryPojoEntityTuplizer implements EntityTuplizer { + + private final PojoEntityTuplizer pojoEntityTuplizer; + + public NoProxyFactoryPojoEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) { + pojoEntityTuplizer = new PojoEntityTuplizer( entityMetamodel, mappedEntity ); + } + + @Override + public EntityMode getEntityMode() { + return pojoEntityTuplizer.getEntityMode(); + } + + @Override + public Object instantiate(Serializable id) throws HibernateException { + return pojoEntityTuplizer.instantiate( id ); + } + + @Override + public Object instantiate(Serializable id, SharedSessionContractImplementor session) { + return pojoEntityTuplizer.instantiate( id, session ); + + } + + @Override + public Serializable getIdentifier(Object entity) throws HibernateException { + return pojoEntityTuplizer.getIdentifier( entity ); + } + + @Override + public Serializable getIdentifier(Object entity, SharedSessionContractImplementor session) { + return pojoEntityTuplizer.getIdentifier( entity, session ); + } + + @Override + public void setIdentifier(Object entity, Serializable id) throws HibernateException { + pojoEntityTuplizer.setIdentifier( entity, id ); + } + + @Override + public void setIdentifier(Object entity, Serializable id, SharedSessionContractImplementor session) { + pojoEntityTuplizer.setIdentifier( entity, id, session ); + } + + @Override + public void resetIdentifier(Object entity, Serializable currentId, Object currentVersion) { + pojoEntityTuplizer.resetIdentifier( entity, currentId, currentVersion ); + } + + @Override + public void resetIdentifier( + Object entity, + Serializable currentId, + Object currentVersion, + SharedSessionContractImplementor session) { + pojoEntityTuplizer.resetIdentifier( entity, currentId, currentVersion, session ); + } + + @Override + public Object getVersion(Object entity) throws HibernateException { + return pojoEntityTuplizer.getVersion( entity ); + } + + @Override + public void setPropertyValue(Object entity, int i, Object value) throws HibernateException { + pojoEntityTuplizer. setPropertyValue( entity, i, value ); + } + + @Override + public void setPropertyValue(Object entity, String propertyName, Object value) throws HibernateException { + pojoEntityTuplizer.setPropertyValue( entity, propertyName, value ); + } + + @Override + public Object[] getPropertyValuesToInsert( + Object entity, + Map mergeMap, + SharedSessionContractImplementor session) throws HibernateException { + return pojoEntityTuplizer.getPropertyValuesToInsert( entity, mergeMap, session ); + } + + @Override + public Object getPropertyValue(Object entity, String propertyName) throws HibernateException { + return pojoEntityTuplizer.getPropertyValue( entity, propertyName ); + } + + @Override + public void afterInitialize(Object entity, SharedSessionContractImplementor session) { + pojoEntityTuplizer.afterInitialize( entity, session ); + + } + + @Override + public boolean hasProxy() { + return pojoEntityTuplizer.hasProxy(); + } + + @Override + public Object createProxy(Serializable id, SharedSessionContractImplementor session) throws HibernateException { + return pojoEntityTuplizer.createProxy( id, session ); + } + + @Override + public boolean isLifecycleImplementor() { + return pojoEntityTuplizer.isLifecycleImplementor(); + } + + @Override + public Class getConcreteProxyClass() { + return pojoEntityTuplizer.getConcreteProxyClass(); + } + + @Override + public EntityNameResolver[] getEntityNameResolvers() { + return pojoEntityTuplizer.getEntityNameResolvers(); + } + + @Override + public String determineConcreteSubclassEntityName( + Object entityInstance, SessionFactoryImplementor factory) { + return pojoEntityTuplizer.determineConcreteSubclassEntityName( entityInstance, factory ); + } + + @Override + public Getter getIdentifierGetter() { + return pojoEntityTuplizer.getIdentifierGetter(); + } + + @Override + public Getter getVersionGetter() { + return pojoEntityTuplizer.getVersionGetter(); + } + + @Override + public ProxyFactory getProxyFactory() { + return null; + } + + @Override + public Object[] getPropertyValues(Object entity) { + return pojoEntityTuplizer.getPropertyValues( entity ); + } + + @Override + public void setPropertyValues(Object entity, Object[] values) { + pojoEntityTuplizer.setPropertyValues( entity, values ); + } + + @Override + public Object getPropertyValue(Object entity, int i) { + return pojoEntityTuplizer.getPropertyValue( entity, i ); + } + + @Override + public Object instantiate() { + return pojoEntityTuplizer.instantiate(); + } + + @Override + public boolean isInstance(Object object) { + return pojoEntityTuplizer.isInstance( object ); + } + + @Override + public Class getMappedClass() { + return pojoEntityTuplizer.getMappedClass(); + } + + @Override + public Getter getGetter(int i) { + return pojoEntityTuplizer.getGetter( i ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesProxyMergeWithSubclassesTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesProxyMergeWithSubclassesTest.java new file mode 100644 index 0000000000..e4342c5424 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesProxyMergeWithSubclassesTest.java @@ -0,0 +1,865 @@ +/* + * 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.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +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.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.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +@TestForIssue( jiraKey = "HHH-13640" ) +@RunWith(BytecodeEnhancerRunner.class ) +@EnhancementOptions(lazyLoading = true) +public class LazyToOnesProxyMergeWithSubclassesTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "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( Animal.class ); + sources.addAnnotatedClass( Primate.class ); + sources.addAnnotatedClass( Human.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void mergeUpdatedHibernateProxy() { + + checkAgeInNewSession( 1 ); + + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final OtherEntity withInitializedHibernateProxy = doInHibernate( + this::sessionFactory, + session -> { + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + otherEntity.getHuman().getSex(); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + return otherEntity; + } + ); + + assertEquals( 2, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 1 ); + stats.clear(); + + // merge updated HibernateProxy to updated HibernateProxy + + withInitializedHibernateProxy.getHuman().setAge( 2 ); + + doInHibernate( + this::sessionFactory, + session -> { + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + otherEntity.getHuman().setAge( 3 ); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + final Human humanImpl = (Human) ( (HibernateProxy) otherEntity.getHuman() ) + .getHibernateLazyInitializer() + .getImplementation(); + + session.merge( withInitializedHibernateProxy ); + // TODO: Reference to associated HibernateProxy is changed + // to the HibernateProxy's implementation. + assertSame( humanImpl, otherEntity.getHuman() ); + assertEquals( 2, otherEntity.getHuman().getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 2 ); + stats.clear(); + + // merge updated HibernateProxy to updated enhanced entity + + withInitializedHibernateProxy.getHuman().setAge( 4 ); + + doInHibernate( + this::sessionFactory, + session -> { + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( human ) ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertSame( human, otherEntity.getHuman() ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + + human.setAge( 5 ); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + session.merge( withInitializedHibernateProxy ); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertSame( human, otherEntity.getHuman() ); + assertEquals( 4, human.getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 4 ); + stats.clear(); + + // merge updated HibernateProxy to uninitialized HibernateProxy + + withInitializedHibernateProxy.getHuman().setAge( 6 ); + + doInHibernate( + this::sessionFactory, + session -> { + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + final Human humanHibernateProxy = otherEntity.getHuman(); + + session.merge( withInitializedHibernateProxy ); + + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + // TODO: Reference to associated HibernateProxy is changed + // reference to the HibernateProxy's implementation. + assertFalse( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + assertSame( + otherEntity.getHuman(), + ( (HibernateProxy) humanHibernateProxy ).getHibernateLazyInitializer().getImplementation() + ); + + assertEquals( 6, otherEntity.getHuman().getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 6 ); + stats.clear(); + + // merge updated HibernateProxy To uninitialized enhanced proxy + + withInitializedHibernateProxy.getHuman().setAge( 7 ); + + doInHibernate( + this::sessionFactory, + session -> { + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( human ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertSame( human, otherEntity.getHuman() ); + assertFalse( Hibernate.isInitialized( human ) ); + + session.merge( withInitializedHibernateProxy ); + assertSame( human, otherEntity.getHuman() ); + assertTrue( Hibernate.isInitialized( human ) ); + + assertEquals( 7, otherEntity.getHuman().getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 7 ); + } + + @Test + public void mergeUpdatedEnhancedProxy() { + + checkAgeInNewSession( 1 ); + + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final OtherEntity withInitializedEnhancedProxy = doInHibernate( + this::sessionFactory, + session -> { + + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( human ) ); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertSame( human, otherEntity.getHuman() ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + otherEntity.getHuman().getSex(); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + return otherEntity; + } + ); + + assertEquals( 2, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 1 ); + stats.clear(); + + // merge updated enhanced proxy to updated HibernateProxy + + withInitializedEnhancedProxy.getHuman().setAge( 2 ); + + doInHibernate( + this::sessionFactory, + session -> { + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + otherEntity.getHuman().setAge( 3 ); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + final Human humanImpl = (Human) ( (HibernateProxy) otherEntity.getHuman() ) + .getHibernateLazyInitializer() + .getImplementation(); + + session.merge( withInitializedEnhancedProxy ); + // TODO: Reference to HibernateProxy is changed + // to the HibernateProxy's implementation. + assertSame( humanImpl, otherEntity.getHuman() ); + + assertEquals( 2, otherEntity.getHuman().getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 2 ); + stats.clear(); + + // merge updated enhanced proxy to updated enhanced proxy + + withInitializedEnhancedProxy.getHuman().setAge( 4 ); + + doInHibernate( + this::sessionFactory, + session -> { + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( human ) ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertSame( human, otherEntity.getHuman() ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + + human.setAge( 5 ); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + session.merge( withInitializedEnhancedProxy ); + assertEquals( 4, human.getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 4 ); + stats.clear(); + + // merge updated enhanced proxy to uninitialized HibernateProxy + + withInitializedEnhancedProxy.getHuman().setAge( 6 ); + + doInHibernate( + this::sessionFactory, + session -> { + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + session.merge( withInitializedEnhancedProxy ); + + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + // TODO: Reference in managed entity gets changed from a HibernateProxy + // to an initialized entity. This happens without enhancement as well. + //assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + assertEquals( 6, otherEntity.getHuman().getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 6 ); + stats.clear(); + + // merge updated enhanced proxy to uninitialized enhanced proxy + + withInitializedEnhancedProxy.getHuman().setAge( 7 ); + + doInHibernate( + this::sessionFactory, + session -> { + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( human ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertSame( human, otherEntity.getHuman() ); + assertFalse( Hibernate.isInitialized( human ) ); + + session.merge( withInitializedEnhancedProxy ); + assertSame( human, otherEntity.getHuman() ); + assertTrue( Hibernate.isInitialized( human ) ); + + assertEquals( 7, otherEntity.getHuman().getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 7 ); + } + + @Test + public void mergeUninitializedHibernateProxy() { + + checkAgeInNewSession( 1 ); + + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final OtherEntity withUninitializedHibernateProxy = doInHibernate( + this::sessionFactory, + session -> { + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + return otherEntity; + } + ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 1 ); + stats.clear(); + + // merge uninitialized HibernateProxy to updated HibernateProxy + + doInHibernate( + this::sessionFactory, + session -> { + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + otherEntity.getHuman().setAge( 3 ); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + final Human humanImpl = (Human) ( (HibernateProxy) otherEntity.getHuman() ) + .getHibernateLazyInitializer() + .getImplementation(); + + session.merge( withUninitializedHibernateProxy ); + //assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + // TODO: Reference to HibernateProxy is changed + // to the HibernateProxy's implementation. + assertSame( humanImpl, otherEntity.getHuman() ); + + assertEquals( 3, otherEntity.getHuman().getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 3 ); + stats.clear(); + + // merge uninitialized HibernateProxy to updated enhanced proxy + + doInHibernate( + this::sessionFactory, + session -> { + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( human ) ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertSame( human, otherEntity.getHuman() ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + + human.setAge( 5 ); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + session.merge( withUninitializedHibernateProxy ); + assertEquals( 5, human.getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 5 ); + stats.clear(); + + // merge uninitialized HibernateProxy to uninitialized HibernateProxy + + doInHibernate( + this::sessionFactory, + session -> { + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + session.merge( withUninitializedHibernateProxy ); + + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + } + ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 5 ); + stats.clear(); + + // merge uninitialized HibernateProxy to uninitialized enhanced proxy + + doInHibernate( + this::sessionFactory, + session -> { + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( human ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertSame( human, otherEntity.getHuman() ); + assertFalse( Hibernate.isInitialized( human ) ); + + session.merge( withUninitializedHibernateProxy ); + assertSame( human, otherEntity.getHuman() ); + assertFalse( Hibernate.isInitialized( human ) ); + } + ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 5 ); + } + + @Test + public void testmergeUninitializedEnhancedProxy() { + + checkAgeInNewSession( 1 ); + + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final OtherEntity withUninitializedEnhancedProxy = doInHibernate( + this::sessionFactory, + session -> { + + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( human ) ); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertSame( human, otherEntity.getHuman() ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + return otherEntity; + } + ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 1 ); + stats.clear(); + + // merge uninitialized enhanced proxy to updated HibernateProxy + + doInHibernate( + this::sessionFactory, + session -> { + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + otherEntity.getHuman().setAge( 3 ); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + final Human humanImpl = (Human) ( (HibernateProxy) otherEntity.getHuman() ) + .getHibernateLazyInitializer() + .getImplementation(); + + session.merge( withUninitializedEnhancedProxy ); + // TODO: Reference to HibernateProxy is changed + // to the HibernateProxy's implementation. + assertSame( humanImpl, otherEntity.getHuman() ); + + assertEquals( 3, otherEntity.getHuman().getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 3 ); + stats.clear(); + + // merge uninitialized enhanced proxy to updated enhanced entity + + doInHibernate( + this::sessionFactory, + session -> { + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( human ) ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertSame( human, otherEntity.getHuman() ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + + human.setAge( 5 ); + assertTrue( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + session.merge( withUninitializedEnhancedProxy ); + assertEquals( 5, human.getAge() ); + } + ); + + assertEquals( 3, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 5 ); + stats.clear(); + + // merge uninitialized enhanced proxy to uninitialized HibernateProxy + + doInHibernate( + this::sessionFactory, + session -> { + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + session.merge( withUninitializedEnhancedProxy ); + + assertFalse( Hibernate.isInitialized( otherEntity.getHuman() ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + } + ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 5 ); + stats.clear(); + + // merge uninitialized enhanced proxy to uninitialized enhanced proxy + + doInHibernate( + this::sessionFactory, + session -> { + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( human ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertSame( human, otherEntity.getHuman() ); + assertFalse( Hibernate.isInitialized( human ) ); + + session.merge( withUninitializedEnhancedProxy ); + assertSame( human, otherEntity.getHuman() ); + assertFalse( Hibernate.isInitialized( human ) ); + } + ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + stats.clear(); + + checkAgeInNewSession( 5 ); + } + + private void checkAgeInNewSession(int expectedAge) { + + doInHibernate( + this::sessionFactory, + session -> { + final Human human = session.get( Human.class, "A Human" ); + assertEquals( expectedAge, human.getAge() ); + } + ); + + } + + @Before + public void setupData() { + inTransaction( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.animal = human; + otherEntity.primate = human; + otherEntity.human = human; + otherEntity.human.setAge( 1 ); + + session.persist( human ); + session.persist( otherEntity ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Human" ).executeUpdate(); + session.createQuery( "delete from Primate" ).executeUpdate(); + session.createQuery( "delete from Animal" ).executeUpdate(); + } + ); + } + + @Entity(name = "Animal") + @Table(name = "Animal") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static abstract class Animal { + + @Id + private String name; + + private int age; + + public String getName() { + return name; + } + + protected void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + } + + @Entity(name = "Primate") + @Table(name = "Primate") + public static class Primate extends Animal { + + public Primate(String name) { + this(); + setName( name ); + } + + protected Primate() { + // this form used by Hibernate + } + } + + @Entity(name = "Human") + @Table(name = "Human") + public static class Human extends Primate { + + private String sex; + + public Human(String name) { + this(); + setName( name ); + } + + protected Human() { + // this form used by Hibernate + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Animal animal = null; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Primate primate = null; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Human human = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public Human getHuman() { + return human; + } + + public void setHuman(Human human) { + this.human = human; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesProxyWithSubclassesStatelessTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesProxyWithSubclassesStatelessTest.java new file mode 100644 index 0000000000..28b03e2301 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesProxyWithSubclassesStatelessTest.java @@ -0,0 +1,332 @@ +/* + * 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.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +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.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.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +@TestForIssue( jiraKey = "HHH-13640" ) +@RunWith(BytecodeEnhancerRunner.class) +public class LazyToOnesProxyWithSubclassesStatelessTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "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( Animal.class ); + sources.addAnnotatedClass( Primate.class ); + sources.addAnnotatedClass( Human.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void testNewHibernateProxyAssociation() { + inStatelessTransaction( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.animal = human; + session.insert( human ); + session.insert( otherEntity ); + } + ); + + inStatelessSession( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + final OtherEntity otherEntity = (OtherEntity) session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "animal" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.animal ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.animal ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testNewEnhancedProxyAssociation() { + inStatelessTransaction( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.human = human; + + session.insert( human ); + session.insert( otherEntity ); + } + ); + + inStatelessSession( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + final OtherEntity otherEntity = (OtherEntity) session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.animal ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + } + + @Test + public void testExistingProxyAssociation() { + inStatelessTransaction( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.animal = human; + otherEntity.primate = human; + session.insert( human ); + session.insert( otherEntity ); + } + ); + + inStatelessSession( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + final OtherEntity otherEntity = (OtherEntity) session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "animal" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.animal ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.animal ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "primate" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.primate ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.primate ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testExistingHibernateProxyAssociationLeafSubclass() { + inStatelessSession( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.animal = human; + otherEntity.primate = human; + otherEntity.human = human; + session.insert( human ); + session.insert( otherEntity ); + } + ); + + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + inStatelessSession( + session -> { + + final OtherEntity otherEntity = (OtherEntity) session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "animal" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.animal ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.animal ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "primate" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.primate ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.primate ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.human ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + } + + @Test + public void testExistingEnhancedProxyAssociationLeafSubclassOnly() { + inStatelessSession( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.human = human; + session.insert( human ); + session.insert( otherEntity ); + } + ); + + inStatelessSession( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final OtherEntity otherEntity = (OtherEntity) session.get( OtherEntity.class, "test1" ); + assertNull( otherEntity.animal ); + assertNull( otherEntity.primate ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.human ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Human" ).executeUpdate(); + session.createQuery( "delete from Primate" ).executeUpdate(); + session.createQuery( "delete from Animal" ).executeUpdate(); + } + ); + } + + @Entity(name = "Animal") + @Table(name = "Animal") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static abstract class Animal { + + @Id + private String name; + + private int age; + + public String getName() { + return name; + } + + protected void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + } + + @Entity(name = "Primate") + @Table(name = "Primate") + public static class Primate extends Animal { + + public Primate(String name) { + this(); + setName( name ); + } + + protected Primate() { + // this form used by Hibernate + } + } + + @Entity(name = "Human") + @Table(name = "Human") + public static class Human extends Primate { + + private String sex; + + public Human(String name) { + this(); + setName( name ); + } + + protected Human() { + // this form used by Hibernate + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Animal animal = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Primate primate = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Human human = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public Human getHuman() { + return human; + } + + public void setHuman(Human human) { + this.human = human; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesProxyWithSubclassesTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesProxyWithSubclassesTest.java index f19dd3d1cc..0f853fdd06 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesProxyWithSubclassesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyToOnesProxyWithSubclassesTest.java @@ -31,8 +31,10 @@ import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; /** @@ -154,7 +156,7 @@ public class LazyToOnesProxyWithSubclassesTest extends BaseNonConfigCoreFunction } @Test - public void testExistingProxyAssociationLeafSubclass() { + public void testExistingHibernateProxyAssociationLeafSubclass() { inTransaction( session -> { Human human = new Human( "A Human" ); @@ -167,10 +169,11 @@ public class LazyToOnesProxyWithSubclassesTest extends BaseNonConfigCoreFunction } ); + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + inSession( session -> { - final Statistics stats = sessionFactory().getStatistics(); - stats.clear(); final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); assertTrue( Hibernate.isPropertyInitialized( otherEntity, "animal" ) ); @@ -181,18 +184,101 @@ public class LazyToOnesProxyWithSubclassesTest extends BaseNonConfigCoreFunction assertTrue( HibernateProxy.class.isInstance( otherEntity.primate ) ); assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); assertFalse( Hibernate.isInitialized( otherEntity.human ) ); - // TODO: Should otherEntity.human be a narrowed HibernateProxy or - // an uninitialized non-HibernateProxy proxy? + assertEquals( 1, stats.getPrepareStatementCount() ); - Human human = otherEntity.getHuman(); + // Make sure human can still get loaded and not initialized. + final Human human = session.getReference( Human.class, "A Human" ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.human ) ); + assertFalse( Hibernate.isInitialized( human ) ); + human.getName(); assertEquals( 1, stats.getPrepareStatementCount() ); + assertFalse( Hibernate.isInitialized( human ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.human ) ); human.getSex(); + + assertTrue( Hibernate.isInitialized( human ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.human ) ); assertEquals( 2, stats.getPrepareStatementCount() ); } ); + + assertEquals( 2, stats.getPrepareStatementCount() ); + stats.clear(); + + inSession( + session -> { + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "animal" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.animal ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.animal ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "primate" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.primate ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.primate ) ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.human ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + + // Make sure human can still get loaded + final Human human = session.get( Human.class, "A Human" ); + assertTrue( !HibernateProxy.class.isInstance( human ) ); + assertTrue( Hibernate.isInitialized( human ) ); + assertTrue( HibernateProxy.class.isInstance( otherEntity.getHuman() ) ); + + assertEquals( 2, stats.getPrepareStatementCount() ); + } + ); + + assertEquals( 2, stats.getPrepareStatementCount() ); + + } + + @Test + public void testExistingEnhancedProxyAssociationLeafSubclassOnly() { + inTransaction( + session -> { + Human human = new Human( "A Human" ); + OtherEntity otherEntity = new OtherEntity( "test1" ); + otherEntity.human = human; + session.persist( human ); + session.persist( otherEntity ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" ); + assertNull( otherEntity.animal ); + assertNull( otherEntity.primate ); + assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) ); + assertFalse( Hibernate.isInitialized( otherEntity.human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.human ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // Make sure human can still get loaded and not initialized. + final Human human = session.getReference( Human.class, "A Human" ); + assertFalse( Hibernate.isInitialized( human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.human ) ); + + human.getName(); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.human ) ); + + human.getSex(); + assertTrue( Hibernate.isInitialized( otherEntity.human ) ); + assertFalse( HibernateProxy.class.isInstance( otherEntity.human ) ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + return otherEntity; + } + ); } @After @@ -215,6 +301,8 @@ public class LazyToOnesProxyWithSubclassesTest extends BaseNonConfigCoreFunction @Id private String name; + private int age; + public String getName() { return name; } @@ -223,6 +311,13 @@ public class LazyToOnesProxyWithSubclassesTest extends BaseNonConfigCoreFunction this.name = name; } + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } } @Entity(name = "Primate") @@ -272,15 +367,15 @@ public class LazyToOnesProxyWithSubclassesTest extends BaseNonConfigCoreFunction @ManyToOne(fetch = FetchType.LAZY) @LazyToOne(LazyToOneOption.NO_PROXY) - protected Animal animal = null; + private Animal animal = null; @ManyToOne(fetch = FetchType.LAZY) @LazyToOne(LazyToOneOption.NO_PROXY) - protected Primate primate = null; + private Primate primate = null; @ManyToOne(fetch = FetchType.LAZY) @LazyToOne(LazyToOneOption.NO_PROXY) - protected Human human = null; + private Human human = null; protected OtherEntity() { // this form used by Hibernate diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyInitializeAndUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyInitializeAndUpdateTest.java index 562d68bc40..f5a4ba2dc9 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyInitializeAndUpdateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyInitializeAndUpdateTest.java @@ -27,6 +27,7 @@ import org.junit.runner.RunWith; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; /** @@ -155,7 +156,8 @@ public class ProxyInitializeAndUpdateTest extends BaseNonConfigCoreFunctionalTes session -> { final Animal animal = session.load( Animal.class, "animal" ); assertFalse( Hibernate.isInitialized( animal ) ); - session.merge( animalInitialized ); + final Animal animalMerged = (Animal) session.merge( animalInitialized ); + assertSame( animal, animalMerged ); assertTrue( Hibernate.isInitialized( animal ) ); assertEquals( 4, animal.getAge() ); assertEquals( "other", animal.getSex() ); @@ -204,7 +206,8 @@ public class ProxyInitializeAndUpdateTest extends BaseNonConfigCoreFunctionalTes assertTrue( Hibernate.isInitialized( animal ) ); animal.setAge( 5 ); animal.setSex( "male" ); - session.merge( animalInitialized ); + final Animal animalMerged = (Animal) session.merge( animalInitialized ); + assertSame( animal, animalMerged ); assertEquals( 4, animal.getAge() ); assertEquals( "other", animal.getSex() ); } @@ -245,7 +248,8 @@ public class ProxyInitializeAndUpdateTest extends BaseNonConfigCoreFunctionalTes session -> { final Animal animal = session.load( Animal.class, "animal" ); assertFalse( Hibernate.isInitialized( animal ) ); - session.merge( animalUninitialized ); + final Animal animalMerged = (Animal) session.merge( animalUninitialized ); + assertSame( animal, animalMerged ); assertFalse( Hibernate.isInitialized( animal ) ); } ); @@ -283,11 +287,13 @@ public class ProxyInitializeAndUpdateTest extends BaseNonConfigCoreFunctionalTes inTransaction( session -> { - final Animal animal = session.get( Animal.class, "animal" ); - assertTrue( Hibernate.isInitialized( animal ) ); + final Animal animal = session.load( Animal.class, "animal" ); + assertFalse( Hibernate.isInitialized( animal ) ); animal.setSex( "other" ); + assertTrue( Hibernate.isInitialized( animal ) ); animal.setAge( 4 ); - session.merge( animalUninitialized ); + final Animal animalMerged = (Animal) session.merge( animalUninitialized ); + assertSame( animal, animalMerged ); assertTrue( Hibernate.isInitialized( animal ) ); assertEquals( "other", animal.getSex() ); assertEquals( 4, animal.getAge() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/QueryScrollingWithInheritanceProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/QueryScrollingWithInheritanceProxyTest.java new file mode 100644 index 0000000000..680bf1b702 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/QueryScrollingWithInheritanceProxyTest.java @@ -0,0 +1,327 @@ +/* + * 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.LinkedHashSet; +import java.util.Set; +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.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.StatelessSession; +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.dialect.DB2Dialect; +import org.hibernate.query.Query; +import org.hibernate.stat.spi.StatisticsImplementor; + +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.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Andrea Boriero + */ +@RunWith(BytecodeEnhancerRunner.class) +public class QueryScrollingWithInheritanceProxyTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "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( EmployeeParent.class ); + sources.addAnnotatedClass( Employee.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void testScrollableWithStatelessSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + + try { + statelessSession.beginTransaction(); + Query query = statelessSession.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + if ( "test1".equals( otherEntity.id ) ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( true ) ); + assertThat( otherEntity.employee, is( employee ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( otherEntity.employeeParent, is( employee ) ); + } + else { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( true ) ); + assertThat( otherEntity.employee, is( employee ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( Hibernate.isInitialized( otherEntity.employeeParent ), is( false ) ); + } + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + statelessSession.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( statelessSession.getTransaction().isActive() ) { + statelessSession.getTransaction().rollback(); + } + statelessSession.close(); + } + } + + @Test + public void testScrollableWithSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final Session session = sessionFactory().openSession(); + + try { + session.beginTransaction(); + Query query = session.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + if ( "test1".equals( otherEntity.id ) ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( true ) ); + assertThat( otherEntity.employee, is( employee ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( otherEntity.employeeParent, is( employee ) ); + } + else { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( true ) ); + assertThat( otherEntity.employee, is( employee ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( Hibernate.isInitialized( otherEntity.employeeParent ), is( false ) ); + } + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + session.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( session.getTransaction().isActive() ) { + session.getTransaction().rollback(); + } + session.close(); + } + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + Employee e1 = new Employee( "ENG1" ); + Employee e2 = new Employee( "ENG2" ); + OtherEntity other1 = new OtherEntity( "test1" ); + OtherEntity other2 = new OtherEntity( "test2" ); + e1.getOtherEntities().add( other1 ); + e1.getOtherEntities().add( other2 ); + e1.getParentOtherEntities().add( other1 ); + e1.getParentOtherEntities().add( other2 ); + other1.employee = e1; + other2.employee = e1; + other1.employeeParent = e1; + other2.employeeParent = e2; + session.persist( other1 ); + session.persist( other2 ); + session.persist( e1 ); + session.persist( e2 ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from EmployeeParent" ).executeUpdate(); + } + ); + } + + @Entity(name = "EmployeeParent") + @Table(name = "EmployeeParent") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static abstract class EmployeeParent { + + @Id + private String dept; + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employeeParent", fetch = FetchType.LAZY) + protected Set parentOtherEntities = new HashSet<>(); + + public Set getParentOtherEntities() { + if ( parentOtherEntities == null ) { + parentOtherEntities = new LinkedHashSet(); + } + return parentOtherEntities; + } + + public void setOtherEntities(Set pParentOtherEntites) { + parentOtherEntities = pParentOtherEntites; + } + + public String getDept() { + return dept; + } + + protected void setDept(String dept) { + this.dept = dept; + } + + } + + @Entity(name = "Employee") + @Table(name = "Employee") + public static class Employee extends EmployeeParent { + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employee", fetch = FetchType.LAZY) + protected Set otherEntities = new HashSet<>(); + + public Employee(String dept) { + this(); + setDept( dept ); + } + + protected Employee() { + // this form used by Hibernate + } + + public Set getOtherEntities() { + if ( otherEntities == null ) { + otherEntities = new LinkedHashSet(); + } + return otherEntities; + } + + public void setOtherEntities(Set pOtherEntites) { + otherEntities = pOtherEntites; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "Employee_Id") + protected Employee employee = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "EmployeeParent_Id") + protected EmployeeParent employeeParent = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cfg/cache/DirectReferenceCacheEntriesTest.java b/hibernate-core/src/test/java/org/hibernate/test/cfg/cache/DirectReferenceCacheEntriesTest.java new file mode 100644 index 0000000000..fd91def312 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cfg/cache/DirectReferenceCacheEntriesTest.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.cfg.cache; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.Immutable; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13665") +public class DirectReferenceCacheEntriesTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { TheEntity.class }; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES, "true" ); + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + TheEntity theEntity = new TheEntity(); + theEntity.setId( 1L ); + session.persist( theEntity ); + } ); + } + + @Test + public void testSelectANonCachablenEntity() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "select t from TheEntity t", TheEntity.class ).getResultList(); + } ); + } + + @Entity(name = "TheEntity") + @Table(name = "THE_ENTITY") + @Immutable + public static class TheEntity { + @Id + public Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/flush/TestHibernateFlushModeOnThreadLocalInactiveTransaction.java b/hibernate-core/src/test/java/org/hibernate/test/flush/TestHibernateFlushModeOnThreadLocalInactiveTransaction.java new file mode 100644 index 0000000000..ea72b04d64 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/flush/TestHibernateFlushModeOnThreadLocalInactiveTransaction.java @@ -0,0 +1,38 @@ +/* + * 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.flush; + +import org.hibernate.FlushMode; +import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +/** + * Test for issue https://hibernate.atlassian.net/browse/HHH-13663 + * + * @author Luca Domenichini + */ +@TestForIssue(jiraKey = "HHH-13663") +public class TestHibernateFlushModeOnThreadLocalInactiveTransaction extends BaseCoreFunctionalTestCase { + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, "thread" ); + } + + @Test + public void testHibernateFlushModeOnInactiveTransaction() { + try ( Session s = sessionFactory().getCurrentSession() ) { + //s.setFlushMode( FlushMode.AUTO ); // this does not throw (API is deprecated) + s.setHibernateFlushMode( FlushMode.AUTO ); // this should not throw even within an inactive transaction + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/QueryScrollingWithInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/QueryScrollingWithInheritanceTest.java new file mode 100644 index 0000000000..1007a8c711 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/QueryScrollingWithInheritanceTest.java @@ -0,0 +1,318 @@ +/* + * 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.hqlfetchscroll; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +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.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.StatelessSession; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.query.Query; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Andrea Boriero + */ +public class QueryScrollingWithInheritanceTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + } + + @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( EmployeeParent.class ); + sources.addAnnotatedClass( Employee.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void testScrollableWithStatelessSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + + try { + statelessSession.beginTransaction(); + Query query = statelessSession.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + if ( "test1".equals( otherEntity.id ) ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( true ) ); + assertThat( otherEntity.employee, is( employee ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( otherEntity.employeeParent, is( employee ) ); + } + else { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( true ) ); + assertThat( otherEntity.employee, is( employee ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( Hibernate.isInitialized( otherEntity.employeeParent ), is( false ) ); + } + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + statelessSession.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( statelessSession.getTransaction().isActive() ) { + statelessSession.getTransaction().rollback(); + } + statelessSession.close(); + } + } + + @Test + public void testScrollableWithSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final Session session = sessionFactory().openSession(); + + try { + session.beginTransaction(); + Query query = session.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + if ( "test1".equals( otherEntity.id ) ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( true ) ); + assertThat( otherEntity.employee, is( employee ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( otherEntity.employeeParent, is( employee ) ); + } + else { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( true ) ); + assertThat( otherEntity.employee, is( employee ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( Hibernate.isInitialized( otherEntity.employeeParent ), is( false ) ); + } + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + session.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( session.getTransaction().isActive() ) { + session.getTransaction().rollback(); + } + session.close(); + } + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + Employee e1 = new Employee( "ENG1" ); + Employee e2 = new Employee( "ENG2" ); + OtherEntity other1 = new OtherEntity( "test1" ); + OtherEntity other2 = new OtherEntity( "test2" ); + e1.getOtherEntities().add( other1 ); + e1.getOtherEntities().add( other2 ); + e1.getParentOtherEntities().add( other1 ); + e1.getParentOtherEntities().add( other2 ); + other1.employee = e1; + other2.employee = e1; + other1.employeeParent = e1; + other2.employeeParent = e2; + session.persist( other1 ); + session.persist( other2 ); + session.persist( e1 ); + session.persist( e2 ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from EmployeeParent" ).executeUpdate(); + } + ); + } + + @Entity(name = "EmployeeParent") + @Table(name = "EmployeeParent") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static abstract class EmployeeParent { + + @Id + private String dept; + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employeeParent", fetch = FetchType.LAZY) + protected Set parentOtherEntities = new HashSet<>(); + + public Set getParentOtherEntities() { + if ( parentOtherEntities == null ) { + parentOtherEntities = new LinkedHashSet(); + } + return parentOtherEntities; + } + + public void setOtherEntities(Set pParentOtherEntites) { + parentOtherEntities = pParentOtherEntites; + } + + public String getDept() { + return dept; + } + + protected void setDept(String dept) { + this.dept = dept; + } + + } + + @Entity(name = "Employee") + @Table(name = "Employee") + public static class Employee extends EmployeeParent { + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employee", fetch = FetchType.LAZY) + protected Set otherEntities = new HashSet<>(); + + public Employee(String dept) { + this(); + setDept( dept ); + } + + protected Employee() { + // this form used by Hibernate + } + + public Set getOtherEntities() { + if ( otherEntities == null ) { + otherEntities = new LinkedHashSet(); + } + return otherEntities; + } + + public void setOtherEntities(Set pOtherEntites) { + otherEntities = pOtherEntites; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "Employee_Id") + protected Employee employee = null; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "EmployeeParent_Id") + protected EmployeeParent employeeParent = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/manytomany/mapkey/ManyToManyWithMaykeyAndSchemaDefinitionTest.java b/hibernate-core/src/test/java/org/hibernate/test/manytomany/mapkey/ManyToManyWithMaykeyAndSchemaDefinitionTest.java new file mode 100644 index 0000000000..55a2371d6a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/manytomany/mapkey/ManyToManyWithMaykeyAndSchemaDefinitionTest.java @@ -0,0 +1,135 @@ +/* + * 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.manytomany.mapkey; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKey; +import javax.persistence.Table; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-4235") +@RequiresDialectFeature(DialectChecks.SupportSchemaCreation.class) +public class ManyToManyWithMaykeyAndSchemaDefinitionTest extends BaseCoreFunctionalTestCase { + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.HBM2DDL_CREATE_SCHEMAS, "true" ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EntityA.class, EntityB.class }; + } + + @Before + public void setUp() { + inTransaction( + session -> { + EntityA entityA = new EntityA(); + entityA.setId( 1L ); + + EntityB entityB = new EntityB(); + entityB.setId( 1L ); + entityA.setEntityBs( "B", entityB ); + session.persist( entityB ); + session.persist( entityA ); + } + ); + } + + @Test + public void testRetrievingTheMapGeneratesACorrectlyQuery() { + + inTransaction( + session -> { + EntityA entityA = session.get( EntityA.class, 1L ); + Collection values = entityA.getEntityBMap().values(); + assertThat( values.size(), is( 1 ) ); + } + ); + + } + + + @Entity(name = "EntityA") + @Table(name = "entitya", schema = "myschema") + public static class EntityA { + + @Id + private Long id; + + private String name; + + @ManyToMany + @MapKey(name = "id") + @JoinTable(name = "entitya_entityb", schema = "myschema", joinColumns = @JoinColumn(name = "entitya_pk"), inverseJoinColumns = @JoinColumn(name = "entityb_pk")) + private Map entityBMap = new HashMap<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public void setEntityBs(String key, EntityB entityB) { + this.entityBMap.put( key, entityB ); + } + + public Map getEntityBMap() { + return entityBMap; + } + } + + @Entity(name = "EntityB") + @Table(name = "entityb", schema = "myschema") + public static class EntityB { + @Id + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + } +}