diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java index 7b846dceab..3d9b91275a 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java @@ -20,6 +20,7 @@ import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker; import org.hibernate.bytecode.enhance.spi.CollectionTracker; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.engine.spi.CompositeTracker; import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; @@ -170,11 +171,16 @@ class CodeTemplates { @FieldValue Collection collection, @Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) { if ( !returned && $$_hibernate_collectionTracker != null ) { - if ( collection == null && $$_hibernate_collectionTracker.getSize( fieldName ) != -1 ) { + final int size = $$_hibernate_collectionTracker.getSize( fieldName ); + if ( collection == null && size != -1 ) { returned = true; } - else if ( collection != null && $$_hibernate_collectionTracker.getSize( fieldName ) != collection.size() ) { - returned = true; + else if ( collection != null ) { + // We only check sizes of non-persistent or initialized persistent collections + if ( ( !( collection instanceof PersistentCollection ) || ( (PersistentCollection) collection ).wasInitialized() ) + && size != collection.size() ) { + returned = true; + } } } } @@ -188,11 +194,16 @@ class CodeTemplates { @FieldValue Map map, @Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) { if ( !returned && $$_hibernate_collectionTracker != null ) { - if ( map == null && $$_hibernate_collectionTracker.getSize( fieldName ) != -1 ) { + final int size = $$_hibernate_collectionTracker.getSize( fieldName ); + if ( map == null && size != -1 ) { returned = true; } - else if ( map != null && $$_hibernate_collectionTracker.getSize( fieldName ) != map.size() ) { - returned = true; + else if ( map != null ) { + // We only check sizes of non-persistent or initialized persistent collections + if ( ( !( map instanceof PersistentCollection ) || ( (PersistentCollection) map ).wasInitialized() ) + && size != map.size() ) { + returned = true; + } } } } @@ -206,11 +217,16 @@ class CodeTemplates { @Advice.Argument(0) DirtyTracker tracker, @Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) { if ( $$_hibernate_collectionTracker != null ) { - if ( collection == null && $$_hibernate_collectionTracker.getSize( fieldName ) != -1 ) { + final int size = $$_hibernate_collectionTracker.getSize( fieldName ); + if ( collection == null && size != -1 ) { tracker.add( fieldName ); } - else if ( collection != null && $$_hibernate_collectionTracker.getSize( fieldName ) != collection.size() ) { - tracker.add( fieldName ); + else if ( collection != null ) { + // We only check sizes of non-persistent or initialized persistent collections + if ( ( !( collection instanceof PersistentCollection ) || ( (PersistentCollection) collection ).wasInitialized() ) + && size != collection.size() ) { + tracker.add( fieldName ); + } } } } @@ -224,11 +240,16 @@ class CodeTemplates { @Advice.Argument(0) DirtyTracker tracker, @Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) { if ( $$_hibernate_collectionTracker != null ) { - if ( map == null && $$_hibernate_collectionTracker.getSize( fieldName ) != -1 ) { + final int size = $$_hibernate_collectionTracker.getSize( fieldName ); + if ( map == null && size != -1 ) { tracker.add( fieldName ); } - else if ( map != null && $$_hibernate_collectionTracker.getSize( fieldName ) != map.size() ) { - tracker.add( fieldName ); + else if ( map != null ) { + // We only check sizes of non-persistent or initialized persistent collections + if ( ( !( map instanceof PersistentCollection ) || ( (PersistentCollection) map ).wasInitialized() ) + && size != map.size() ) { + tracker.add( fieldName ); + } } } } @@ -242,7 +263,8 @@ class CodeTemplates { @Advice.Argument(value = 0, readOnly = false) LazyAttributeLoadingInterceptor lazyInterceptor, @Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) { if ( lazyInterceptor == null || lazyInterceptor.isAttributeLoaded( fieldName ) ) { - if ( collection == null ) { + if ( collection == null || collection instanceof PersistentCollection && !( (PersistentCollection) collection ) + .wasInitialized() ) { $$_hibernate_collectionTracker.add( fieldName, -1 ); } else { @@ -260,7 +282,8 @@ class CodeTemplates { @Advice.Argument(value = 0, readOnly = false) LazyAttributeLoadingInterceptor lazyInterceptor, @Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) { if ( lazyInterceptor == null || lazyInterceptor.isAttributeLoaded( fieldName ) ) { - if ( map == null ) { + if ( map == null || map instanceof PersistentCollection && !( (PersistentCollection) map ) + .wasInitialized() ) { $$_hibernate_collectionTracker.add( fieldName, -1 ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java index f06b78f000..4def1c73e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java @@ -34,8 +34,7 @@ public class LazyAttributesMetadata implements Serializable { public static LazyAttributesMetadata from( PersistentClass mappedEntity, boolean isEnhanced, - boolean allowEnhancementAsProxy, - boolean collectionsInDefaultFetchGroupEnabled) { + boolean allowEnhancementAsProxy) { final Map lazyAttributeDescriptorMap = new LinkedHashMap<>(); final Map> fetchGroupToAttributesMap = new HashMap<>(); @@ -49,7 +48,7 @@ public class LazyAttributesMetadata implements Serializable { property, isEnhanced, allowEnhancementAsProxy, - collectionsInDefaultFetchGroupEnabled + false ); if ( lazy ) { final LazyAttributeDescriptor lazyAttributeDescriptor = LazyAttributeDescriptor.from( property, i, x++ ); diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java index 0e8ceeee65..de07cc6990 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java @@ -38,12 +38,11 @@ public final class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhanc PersistentClass persistentClass, Set identifierAttributeNames, CompositeType nonAggregatedCidMapper, - boolean allowEnhancementAsProxy, - boolean collectionsInDefaultFetchGroupEnabled) { + boolean allowEnhancementAsProxy) { final Class mappedClass = persistentClass.getMappedClass(); final boolean enhancedForLazyLoading = PersistentAttributeInterceptable.class.isAssignableFrom( mappedClass ); final LazyAttributesMetadata lazyAttributesMetadata = enhancedForLazyLoading - ? LazyAttributesMetadata.from( persistentClass, true, allowEnhancementAsProxy, collectionsInDefaultFetchGroupEnabled ) + ? LazyAttributesMetadata.from( persistentClass, true, allowEnhancementAsProxy ) : LazyAttributesMetadata.nonEnhanced( persistentClass.getEntityName() ); return new BytecodeEnhancementMetadataPojoImpl( diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 61564422c0..252d265925 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -169,8 +169,7 @@ public class EntityMetamodel implements Serializable { persistentClass, idAttributeNames, nonAggregatedCidMapper, - sessionFactoryOptions.isEnhancementAsProxyEnabled(), - sessionFactoryOptions.isCollectionsInDefaultFetchGroupEnabled() + sessionFactoryOptions.isEnhancementAsProxyEnabled() ); } else { diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingCollectionInDefaultFetchGroupTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingCollectionInDefaultFetchGroupTest.java new file mode 100644 index 0000000000..31e46e540d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingCollectionInDefaultFetchGroupTest.java @@ -0,0 +1,112 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.dirty; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.boot.internal.SessionFactoryBuilderImpl; +import org.hibernate.boot.internal.SessionFactoryOptionsBuilder; +import org.hibernate.boot.spi.SessionFactoryBuilderService; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.cfg.Configuration; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.SelfDirtinessTracker; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Christian Beikov + */ +@TestForIssue( jiraKey = "HHH-14348" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class DirtyTrackingCollectionInDefaultFetchGroupTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{StringsEntity.class}; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getStandardServiceRegistryBuilder().addService( + SessionFactoryBuilderService.class, + (SessionFactoryBuilderService) (metadata, bootstrapContext) -> { + SessionFactoryOptionsBuilder optionsBuilder = new SessionFactoryOptionsBuilder( + metadata.getMetadataBuildingOptions().getServiceRegistry(), + bootstrapContext + ); + optionsBuilder.enableCollectionInDefaultFetchGroup( true ); + return new SessionFactoryBuilderImpl( metadata, optionsBuilder ); + } + ); + } + + @Before + public void prepare() { + doInJPA( this::sessionFactory, em -> { + StringsEntity entity = new StringsEntity(); + entity.id = 1L; + entity.someStrings = new ArrayList<>( Arrays.asList( "a", "b", "c" ) ); + em.persist( entity ); + } ); + } + + @Test + public void test() { + doInJPA( this::sessionFactory, entityManager -> { + StringsEntity entity = entityManager.find( StringsEntity.class, 1L ); + entityManager.flush(); + BytecodeLazyAttributeInterceptor interceptor = (BytecodeLazyAttributeInterceptor) ( (PersistentAttributeInterceptable) entity ) + .$$_hibernate_getInterceptor(); + assertTrue( interceptor.hasAnyUninitializedAttributes() ); + assertFalse( interceptor.isAttributeLoaded( "someStrings" ) ); + assertFalse( interceptor.isAttributeLoaded( "someStringEntities" ) ); + } ); + } + + // --- // + + @Entity + @Table( name = "STRINGS_ENTITY" ) + private static class StringsEntity { + + @Id + Long id; + + @ElementCollection + List someStrings; + + @ManyToOne(fetch = FetchType.LAZY) + StringsEntity parent; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY) + Set someStringEntities; + } +} \ No newline at end of file