HHH-12439 : Merging of new entities can fail depending on cascade order
This commit is contained in:
parent
9d958291d8
commit
c10dbe9d9c
|
@ -231,17 +231,6 @@ class MergeContext implements Map {
|
|||
throw new NullPointerException( "null merge and managed entities are not supported by " + getClass().getName() );
|
||||
}
|
||||
|
||||
// Detect invalid 'managed entity' -> 'managed entity' mappings where key != value
|
||||
if ( managedToMergeEntityXref.containsKey( mergeEntity ) ) {
|
||||
if ( managedToMergeEntityXref.get( mergeEntity ) != mergeEntity ) {
|
||||
throw new IllegalStateException(
|
||||
"MergeContext#attempt to create managed -> managed mapping with different entities: "
|
||||
+ printEntity( mergeEntity ) + "; " + printEntity(
|
||||
managedEntity )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Object oldManagedEntity = mergeToManagedEntityXref.put( mergeEntity, managedEntity );
|
||||
Boolean oldOperatedOn = mergeEntityToOperatedOnFlagMap.put( mergeEntity, isOperatedOn );
|
||||
// If managedEntity already corresponds with a different merge entity, that means
|
||||
|
|
|
@ -322,13 +322,6 @@ public abstract class EntityType extends AbstractType implements AssociationType
|
|||
return null;
|
||||
}
|
||||
Object cached = copyCache.get( original );
|
||||
if ( cached == null ) {
|
||||
// Avoid creation of invalid managed -> managed mapping in copyCache when traversing
|
||||
// cascade loop (@OneToMany(cascade=ALL) with associated @ManyToOne(cascade=ALL)) in entity graph
|
||||
if ( copyCache.containsValue( original ) ) {
|
||||
cached = original;
|
||||
}
|
||||
}
|
||||
if ( cached != null ) {
|
||||
return cached;
|
||||
}
|
||||
|
@ -338,10 +331,19 @@ public abstract class EntityType extends AbstractType implements AssociationType
|
|||
}
|
||||
if ( session.getContextEntityIdentifier( original ) == null &&
|
||||
ForeignKeys.isTransient( associatedEntityName, original, Boolean.FALSE, session ) ) {
|
||||
final Object copy = session.getEntityPersister( associatedEntityName, original )
|
||||
.instantiate( null, session );
|
||||
copyCache.put( original, copy );
|
||||
return copy;
|
||||
// original is transient; it is possible that original is a "managed" entity that has
|
||||
// not been made persistent yet, so check if copyCache contains original as a "managed" value
|
||||
// that corresponds with some "merge" value.
|
||||
if ( copyCache.containsValue( original ) ) {
|
||||
return original;
|
||||
}
|
||||
else {
|
||||
// the transient entity is not "managed"; add the merge/managed pair to copyCache
|
||||
final Object copy = session.getEntityPersister( associatedEntityName, original )
|
||||
.instantiate( null, session );
|
||||
copyCache.put( original, copy );
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Object id = getIdentifier( original, session );
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* 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.ops;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
import org.hibernate.cfg.Configuration;
|
||||
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
/**
|
||||
* A 1 ------------> 1 B 1 ----------> 1 C
|
||||
* 1 1
|
||||
* | |
|
||||
* | |
|
||||
* V V
|
||||
* 1 N
|
||||
* D 1------------>N E
|
||||
*
|
||||
*
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class MergeManagedAndCopiesAllowedTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
protected Class[] getAnnotatedClasses() {
|
||||
return new Class[]{
|
||||
A.class,
|
||||
B.class,
|
||||
C.class,
|
||||
D.class,
|
||||
E.class
|
||||
};
|
||||
}
|
||||
|
||||
protected void configure(Configuration cfg) {
|
||||
super.configure( cfg );
|
||||
cfg.setProperty( "hibernate.event.merge.entity_copy_observer", "allow" );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIt() {
|
||||
A a = new A();
|
||||
a.b = new B();
|
||||
a.b.d = new D();
|
||||
a.b.d.dEs.add( new E() );
|
||||
|
||||
doInHibernate(
|
||||
this::sessionFactory, session -> {
|
||||
session.persist( a );
|
||||
}
|
||||
);
|
||||
|
||||
doInHibernate(
|
||||
this::sessionFactory, session -> {
|
||||
A aGet= session.get( A.class, a.id );
|
||||
aGet.b.c = new C();
|
||||
Set<E> copies = new HashSet<>();
|
||||
for ( E e : aGet.b.d.dEs ) {
|
||||
copies.add ( new E( e.id, "description" ) );
|
||||
}
|
||||
aGet.b.c.cEs.addAll( copies );
|
||||
session.merge( aGet );
|
||||
}
|
||||
);
|
||||
|
||||
doInHibernate(
|
||||
this::sessionFactory, session -> {
|
||||
A aGet= session.get( A.class, a.id );
|
||||
E e = aGet.b.c.cEs.iterator().next();
|
||||
assertSame( e, aGet.b.d.dEs.iterator().next() );
|
||||
assertEquals( "description", e.description );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity(name = "A")
|
||||
public static class A {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private int id;
|
||||
|
||||
@OneToOne(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
|
||||
private B b;
|
||||
}
|
||||
|
||||
@Entity(name = "B")
|
||||
public static class B {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private int id;
|
||||
|
||||
@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
||||
private C c;
|
||||
|
||||
@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
||||
private D d;
|
||||
}
|
||||
|
||||
@Entity(name = "C")
|
||||
public static class C {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private int id;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
@JoinColumn(nullable = true)
|
||||
private Set<E> cEs = new HashSet<>();
|
||||
}
|
||||
|
||||
|
||||
@Entity(name = "D")
|
||||
public static class D {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private int id;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
@JoinColumn(nullable = false)
|
||||
private Set<E> dEs = new HashSet<>();
|
||||
}
|
||||
|
||||
@Entity(name = "E")
|
||||
public static class E {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private int id;
|
||||
|
||||
private String description;
|
||||
|
||||
E() {}
|
||||
|
||||
E(int id, String description) {
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue