From 189bc54dbd777fe489079cbc4b33ae43986f9833 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 2 Dec 2021 14:11:08 +0100 Subject: [PATCH] Fix EntityEntry loaded state for persistent arrays --- .../persister/entity/EntityPersister.java | 2 +- .../sql/results/internal/ResultsHelper.java | 11 + .../LoadEntityWithElementCollectionTest.java | 220 ++++++++++++++++++ 3 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/collection/LoadEntityWithElementCollectionTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index 8c111d85d9..0e5186ef50 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -828,7 +828,7 @@ public interface EntityPersister Object[] getPropertyValues(Object object); default Object getValue(Object object, int i) { - return getValue( object, i ); + return getPropertyValue( object, i ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ResultsHelper.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ResultsHelper.java index 3f8457ba9a..515606140e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ResultsHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ResultsHelper.java @@ -18,9 +18,11 @@ import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.entry.CollectionCacheEntry; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.collection.internal.PersistentArrayHolder; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.BatchFetchQueue; import org.hibernate.engine.spi.CollectionEntry; +import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -28,6 +30,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.EntityPersister; @@ -145,6 +148,14 @@ public class ResultsHelper { } if ( collectionDescriptor.getCollectionType().hasHolder() ) { + // in case of PersistentArrayHolder we have to realign the EntityEntry loaded state with + // the entity values + final Object owner = collectionInstance.getOwner(); + final EntityEntry entry = persistenceContext.getEntry( owner ); + final PluralAttributeMapping mapping = collectionDescriptor.getAttributeMapping(); + final int propertyIndex = mapping.getStateArrayPosition(); + final Object[] loadedState = entry.getLoadedState(); + loadedState[propertyIndex] = mapping.getValue( owner ); persistenceContext.addCollectionHolder( collectionInstance ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/LoadEntityWithElementCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/LoadEntityWithElementCollectionTest.java new file mode 100644 index 0000000000..351f369013 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/LoadEntityWithElementCollectionTest.java @@ -0,0 +1,220 @@ +package org.hibernate.orm.test.collection; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.dialect.SybaseDialect; +import org.hibernate.event.spi.EventType; +import org.hibernate.event.spi.PostUpdateEvent; +import org.hibernate.event.spi.PostUpdateEventListener; +import org.hibernate.persister.entity.EntityPersister; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderColumn; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; + + +@DomainModel( + annotatedClasses = { + LoadEntityWithElementCollectionTest.IndexedEntity.class + } +) +@SessionFactory +@SkipForDialect(dialectClass = SybaseDialect.class, matchSubTypes = true) +public class LoadEntityWithElementCollectionTest { + + public static class PostUpdateTestListener implements PostUpdateEventListener { + + boolean isPostUpdateCalled; + + @Override + public void onPostUpdate(PostUpdateEvent event) { + isPostUpdateCalled = true; + } + + @Override + public boolean requiresPostCommitHanding(EntityPersister persister) { + return false; + } + + public boolean isPostUpdateCalled() { + return isPostUpdateCalled; + } + } + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + IndexedEntity entity = new IndexedEntity(); + entity.setId( 1L ); + entity.setSerializableArray( new boolean[] { true, false } ); + + session.persist( entity ); + + IndexedEntity entity2 = new IndexedEntity(); + entity2.setId( 2L ); + entity2.setElementCollectionArray( new boolean[] { true, false } ); + final HashSet indexedEntities = new HashSet<>(); + indexedEntities.add( entity ); + entity2.setIndexedEntities( indexedEntities ); + + session.persist( entity2 ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "delete from IndexedEntity" ).executeUpdate(); + } + ); + } + + @Test + public void onPostUpdateMethodShouldBeCalledTest(SessionFactoryScope scope) { + + final PostUpdateTestListener listener = new PostUpdateTestListener(); + scope.getSessionFactory().getEventEngine().getListenerRegistry().setListeners( + EventType.POST_UPDATE, + listener + ); + + scope.inTransaction( + session -> { + final IndexedEntity indexedEntity = session.get( IndexedEntity.class, 1L ); + assertNotNull( indexedEntity.getElementCollectionArray() ); + assertEquals( 0, indexedEntity.getElementCollectionArray().length ); + assertNotNull( indexedEntity.getSerializableArray() ); + indexedEntity.setSerializableArray( new boolean[] { false, true } ); + } + ); + + assertTrue( listener.isPostUpdateCalled() ); + + } + + @Test + public void onPostUpdateMethodShouldBeCalledTest2(SessionFactoryScope scope) { + + final PostUpdateTestListener listener = new PostUpdateTestListener(); + scope.getSessionFactory().getEventEngine().getListenerRegistry().setListeners( + EventType.POST_UPDATE, + listener + ); + + scope.inTransaction( + session -> { + final IndexedEntity indexedEntity = session.get( IndexedEntity.class, 1L ); + assertNotNull( indexedEntity.getElementCollectionArray() ); + assertEquals( 0, indexedEntity.getElementCollectionArray().length ); + final boolean[] serializableArray = indexedEntity.getSerializableArray(); + assertNotNull( serializableArray ); + serializableArray[0] = !serializableArray[0]; + serializableArray[1] = !serializableArray[1]; + } + ); + + assertTrue( listener.isPostUpdateCalled() ); + } + + @Test + public void onPostUpdateMethodShouldNotBeCalledTest(SessionFactoryScope scope) { + + final PostUpdateTestListener listener = new PostUpdateTestListener(); + scope.getSessionFactory().getEventEngine().getListenerRegistry().setListeners( + EventType.POST_UPDATE, + listener + ); + + scope.inTransaction( + session -> { + final IndexedEntity indexedEntity = session.get( IndexedEntity.class, 1L ); + assertNotNull( indexedEntity.getElementCollectionArray() ); + assertEquals( 0, indexedEntity.getElementCollectionArray().length ); + assertNotNull( indexedEntity.getSerializableArray() ); + } + ); + + assertFalse( listener.isPostUpdateCalled() ); + + scope.inTransaction( + session -> { + final IndexedEntity indexedEntity = session.get( IndexedEntity.class, 2L ); + assertNotNull( indexedEntity.getElementCollectionArray() ); + assertNull( indexedEntity.getSerializableArray() ); + } + ); + + assertFalse( listener.isPostUpdateCalled() ); + + } + + @Entity(name = "IndexedEntity") + public static class IndexedEntity { + @Id + public Long id; + + public boolean[] serializableArray; + + @ElementCollection + @OrderColumn + public boolean[] elementCollectionArray; + + @OneToMany(fetch = FetchType.EAGER) + public Set indexedEntities; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public boolean[] getSerializableArray() { + return serializableArray; + } + + public void setSerializableArray(boolean[] serializableArray) { + this.serializableArray = serializableArray; + } + + public boolean[] getElementCollectionArray() { + return elementCollectionArray; + } + + public void setElementCollectionArray(boolean[] elementCollectionArray) { + this.elementCollectionArray = elementCollectionArray; + } + + public Set getIndexedEntities() { + return indexedEntities; + } + + public void setIndexedEntities(Set indexedEntities) { + this.indexedEntities = indexedEntities; + } + + } + +} \ No newline at end of file