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() );
|
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 );
|
Object oldManagedEntity = mergeToManagedEntityXref.put( mergeEntity, managedEntity );
|
||||||
Boolean oldOperatedOn = mergeEntityToOperatedOnFlagMap.put( mergeEntity, isOperatedOn );
|
Boolean oldOperatedOn = mergeEntityToOperatedOnFlagMap.put( mergeEntity, isOperatedOn );
|
||||||
// If managedEntity already corresponds with a different merge entity, that means
|
// 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;
|
return null;
|
||||||
}
|
}
|
||||||
Object cached = copyCache.get( original );
|
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 ) {
|
if ( cached != null ) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
@ -338,10 +331,19 @@ public abstract class EntityType extends AbstractType implements AssociationType
|
||||||
}
|
}
|
||||||
if ( session.getContextEntityIdentifier( original ) == null &&
|
if ( session.getContextEntityIdentifier( original ) == null &&
|
||||||
ForeignKeys.isTransient( associatedEntityName, original, Boolean.FALSE, session ) ) {
|
ForeignKeys.isTransient( associatedEntityName, original, Boolean.FALSE, session ) ) {
|
||||||
final Object copy = session.getEntityPersister( associatedEntityName, original )
|
// original is transient; it is possible that original is a "managed" entity that has
|
||||||
.instantiate( null, session );
|
// not been made persistent yet, so check if copyCache contains original as a "managed" value
|
||||||
copyCache.put( original, copy );
|
// that corresponds with some "merge" value.
|
||||||
return copy;
|
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 {
|
else {
|
||||||
Object id = getIdentifier( original, session );
|
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