diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java index 3d29b406a4..ba8b8b4716 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java @@ -12,6 +12,7 @@ import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLaziness import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CollectionEntry; +import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionImplementor; @@ -111,25 +112,27 @@ public class WrapVisitor extends ProxyVisitor { } else if ( attributeInterceptor != null && ((LazyAttributeLoadingInterceptor)attributeInterceptor).isAttributeLoaded( persister.getAttributeMapping().getAttributeName() ) ) { - // the collection has not been initialized and new collection values have been assigned, - // we need to be sure to delete all the collection elements before inserting the new ones - final AbstractEntityPersister entityDescriptor = (AbstractEntityPersister) mappingMetamodel.getEntityDescriptor( entity.getClass() ); - final Object key = entityDescriptor.getCollectionKey( - persister, - entity, - persistenceContext.getEntry( entity ), - session - ); - final PersistentCollection collectionInstance = persister.getCollectionSemantics().instantiateWrapper( - key, - persister, - session - ); - collectionInstance.setOwner( entity ); - persistenceContext.addUninitializedCollection( persister, collectionInstance, key ); + final EntityEntry entry = persistenceContext.getEntry( entity ); + if ( entry.isExistsInDatabase() ) { + // the collection has not been initialized and new collection values have been assigned, + // we need to be sure to delete all the collection elements before inserting the new ones + final AbstractEntityPersister entityDescriptor = + (AbstractEntityPersister) persister.getOwnerEntityPersister(); + final Object key = entityDescriptor.getCollectionKey( + persister, + entity, + entry, + session + ); + final PersistentCollection collectionInstance = persister.getCollectionSemantics() + .instantiateWrapper( key, persister, session ); + collectionInstance.setOwner( entity ); + persistenceContext.addUninitializedCollection( persister, collectionInstance, key ); - final CollectionEntry collectionEntry = persistenceContext.getCollectionEntry( collectionInstance ); - collectionEntry.setDoremove( true ); + final CollectionEntry collectionEntry = persistenceContext + .getCollectionEntry( collectionInstance ); + collectionEntry.setDoremove( true ); + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeUnsavedEntitiesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeUnsavedEntitiesTest.java new file mode 100644 index 0000000000..2dd342bd1f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeUnsavedEntitiesTest.java @@ -0,0 +1,180 @@ +package org.hibernate.orm.test.bytecode.enhancement.merge; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.runner.RunWith; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static jakarta.persistence.CascadeType.MERGE; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@RunWith(BytecodeEnhancerRunner.class) +@TestForIssue( jiraKey = "HHH-16322") +public class MergeUnsavedEntitiesTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @AfterEach + public void tearDown() { + inTransaction( + session -> { + session.createMutationQuery( "delete from Child" ).executeUpdate(); + session.createMutationQuery( "delete from Parent" ).executeUpdate(); + } + ); + } + + @Test + public void testMerge() { + inTransaction( + session -> { + Parent parent = new Parent( 1l, 2l ); + parent = session.merge( parent ); + Child child = new Child( 2l, "first child" ); + child = session.merge( child ); + parent.addChild( child ); + parent.getId(); + } + ); + + inTransaction( + session -> { + Parent parent = session.find( Parent.class, 1l ); + assertThat( parent.getChildren().size() ).isEqualTo( 1 ); + } + ); + + inTransaction( + session -> { + Parent parent = session.find( Parent.class, 1l ); + session.merge( parent ); + } + ); + + inTransaction( + session -> { + Parent parent = session.find( Parent.class, 1l ); + parent.setChildren( new ArrayList<>() ); + session.merge( parent ); + } + ); + + inTransaction( + session -> { + Parent parent = session.find( Parent.class, 1l ); + assertThat( parent.getChildren().size() ).isEqualTo( 0 ); + } + ); + } + + @Entity(name = "parent") + @Table(name = "parent") + public static class Parent { + @Id + private Long id; + + private Long version; + + @OneToMany(mappedBy = "parent", cascade = { MERGE }, orphanRemoval = true, fetch = FetchType.LAZY) + private List children = new ArrayList<>(); + + public Parent() { + } + + public Parent(Long id, Long version) { + this.id = id; + this.version = version; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public void addChild(Child child) { + children.add( child ); + child.setParent( this ); + } + + public void removeChild(Child child) { + children.remove( child ); + child.setParent( null ); + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + } + + @Entity + @Table(name = "child") + public static class Child { + + @Id + private Long id; + + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + private Parent parent; + + public Child() { + } + + public Child(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return this.id; + } + + public Parent getParent() { + return parent; + } + + public void setId(Long id) { + this.id = id; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + } + +}