HHH-16322 Merge of entities having a collection with orphanRemoval true fails when bytecode enhancement is enabled

This commit is contained in:
Andrea Boriero 2023-03-16 16:41:36 +01:00 committed by Steve Ebersole
parent cdada7a916
commit bcb50d1052
2 changed files with 201 additions and 18 deletions

View File

@ -12,6 +12,7 @@ import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLaziness
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.CollectionEntry;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
@ -111,25 +112,27 @@ public class WrapVisitor extends ProxyVisitor {
} }
else if ( attributeInterceptor != null else if ( attributeInterceptor != null
&& ((LazyAttributeLoadingInterceptor)attributeInterceptor).isAttributeLoaded( persister.getAttributeMapping().getAttributeName() ) ) { && ((LazyAttributeLoadingInterceptor)attributeInterceptor).isAttributeLoaded( persister.getAttributeMapping().getAttributeName() ) ) {
// the collection has not been initialized and new collection values have been assigned, final EntityEntry entry = persistenceContext.getEntry( entity );
// we need to be sure to delete all the collection elements before inserting the new ones if ( entry.isExistsInDatabase() ) {
final AbstractEntityPersister entityDescriptor = (AbstractEntityPersister) mappingMetamodel.getEntityDescriptor( entity.getClass() ); // the collection has not been initialized and new collection values have been assigned,
final Object key = entityDescriptor.getCollectionKey( // we need to be sure to delete all the collection elements before inserting the new ones
persister, final AbstractEntityPersister entityDescriptor =
entity, (AbstractEntityPersister) persister.getOwnerEntityPersister();
persistenceContext.getEntry( entity ), final Object key = entityDescriptor.getCollectionKey(
session persister,
); entity,
final PersistentCollection<?> collectionInstance = persister.getCollectionSemantics().instantiateWrapper( entry,
key, session
persister, );
session final PersistentCollection<?> collectionInstance = persister.getCollectionSemantics()
); .instantiateWrapper( key, persister, session );
collectionInstance.setOwner( entity ); collectionInstance.setOwner( entity );
persistenceContext.addUninitializedCollection( persister, collectionInstance, key ); persistenceContext.addUninitializedCollection( persister, collectionInstance, key );
final CollectionEntry collectionEntry = persistenceContext.getCollectionEntry( collectionInstance ); final CollectionEntry collectionEntry = persistenceContext
collectionEntry.setDoremove( true ); .getCollectionEntry( collectionInstance );
collectionEntry.setDoremove( true );
}
} }
} }

View File

@ -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<Child> 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<Child> getChildren() {
return children;
}
public void setChildren(List<Child> 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;
}
}
}