diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java
index a719ed5ed4..4473d8c5f3 100644
--- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java
+++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java
@@ -14,6 +14,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
+import java.util.Map;
import javax.naming.NamingException;
@@ -474,6 +475,20 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers
dirty = true;
}
+ /**
+ * Replace entity instances with copy in {@code copyCache}/.
+ *
+ * @param copyCache - mapping from entity in the process of being
+ * merged to managed copy.
+ */
+ public final void replaceQueuedOperationValues(CollectionPersister persister, Map copyCache) {
+ for ( DelayedOperation operation : operationQueue ) {
+ if ( ValueDelayedOperation.class.isInstance( operation ) ) {
+ ( (ValueDelayedOperation) operation ).replace( persister, copyCache );
+ }
+ }
+ }
+
/**
* After reading all existing elements from the database,
* add the queued elements to the underlying collection.
@@ -1133,6 +1148,40 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers
public Object getOrphan();
}
+ protected interface ValueDelayedOperation extends DelayedOperation {
+ void replace(CollectionPersister collectionPersister, Map copyCache);
+ }
+
+ protected abstract class AbstractValueDelayedOperation implements ValueDelayedOperation {
+ private Object addedValue;
+ private Object orphan;
+
+ protected AbstractValueDelayedOperation(Object addedValue, Object orphan) {
+ this.addedValue = addedValue;
+ this.orphan = orphan;
+ }
+
+ public void replace(CollectionPersister persister, Map copyCache) {
+ if ( addedValue != null ) {
+ addedValue = getReplacement( persister.getElementType(), addedValue, copyCache );
+ }
+ }
+
+ protected final Object getReplacement(Type type, Object current, Map copyCache) {
+ return type.replace( current, null, session, owner, copyCache );
+ }
+
+ @Override
+ public final Object getAddedInstance() {
+ return addedValue;
+ }
+
+ @Override
+ public final Object getOrphan() {
+ return orphan;
+ }
+ }
+
/**
* Given a collection of entity instances that used to
* belong to the collection, and a collection of instances
diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java
index 1409be8d79..b131fa85e8 100644
--- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java
+++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java
@@ -546,28 +546,16 @@ public class PersistentBag extends AbstractPersistentCollection implements List
}
}
- final class SimpleAdd implements DelayedOperation {
- private Object value;
+ final class SimpleAdd extends AbstractValueDelayedOperation {
- public SimpleAdd(Object value) {
- this.value = value;
+ public SimpleAdd(Object addedValue) {
+ super( addedValue, null );
}
@Override
@SuppressWarnings("unchecked")
public void operate() {
- bag.add( value );
- }
-
- @Override
- public Object getAddedInstance() {
- return value;
- }
-
- @Override
- public Object getOrphan() {
- return null;
+ bag.add( getAddedInstance() );
}
}
-
}
diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java
index 66cabbc604..599b312610 100644
--- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java
+++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java
@@ -14,7 +14,6 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
-import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
@@ -511,131 +510,81 @@ public class PersistentList extends AbstractPersistentCollection implements List
}
}
- final class SimpleAdd implements DelayedOperation {
- private Object value;
+ final class SimpleAdd extends AbstractValueDelayedOperation {
- public SimpleAdd(Object value) {
- this.value = value;
+ public SimpleAdd(Object addedValue) {
+ super( addedValue, null );
}
@Override
@SuppressWarnings("unchecked")
public void operate() {
- list.add( value );
- }
-
- @Override
- public Object getAddedInstance() {
- return value;
- }
-
- @Override
- public Object getOrphan() {
- return null;
+ list.add( getAddedInstance() );
}
}
- final class Add implements DelayedOperation {
+ abstract class AbstractListValueDelayedOperation extends AbstractValueDelayedOperation {
private int index;
- private Object value;
- public Add(int index, Object value) {
+ AbstractListValueDelayedOperation(Integer index, Object addedValue, Object orphan) {
+ super( addedValue, orphan );
this.index = index;
- this.value = value;
}
- @Override
- @SuppressWarnings("unchecked")
- public void operate() {
- list.add( index, value );
- }
-
- @Override
- public Object getAddedInstance() {
- return value;
- }
-
- @Override
- public Object getOrphan() {
- return null;
+ protected final int getIndex() {
+ return index;
}
}
- final class Set implements DelayedOperation {
- private int index;
- private Object value;
- private Object old;
+ final class Add extends AbstractListValueDelayedOperation {
- public Set(int index, Object value, Object old) {
- this.index = index;
- this.value = value;
- this.old = old;
+ public Add(int index, Object addedValue) {
+ super( index, addedValue, null );
}
@Override
@SuppressWarnings("unchecked")
public void operate() {
- list.set( index, value );
- }
-
- @Override
- public Object getAddedInstance() {
- return value;
- }
-
- @Override
- public Object getOrphan() {
- return old;
+ list.add( getIndex(), getAddedInstance() );
}
}
- final class Remove implements DelayedOperation {
- private int index;
- private Object old;
+ final class Set extends AbstractListValueDelayedOperation {
- public Remove(int index, Object old) {
- this.index = index;
- this.old = old;
+ public Set(int index, Object addedValue, Object orphan) {
+ super( index, addedValue, orphan );
}
@Override
@SuppressWarnings("unchecked")
public void operate() {
- list.remove( index );
- }
-
- @Override
- public Object getAddedInstance() {
- return null;
- }
-
- @Override
- public Object getOrphan() {
- return old;
+ list.set( getIndex(), getAddedInstance() );
}
}
- final class SimpleRemove implements DelayedOperation {
- private Object value;
+ final class Remove extends AbstractListValueDelayedOperation {
- public SimpleRemove(Object value) {
- this.value = value;
+ public Remove(int index, Object orphan) {
+ super( index, null, orphan );
}
@Override
@SuppressWarnings("unchecked")
public void operate() {
- list.remove( value );
+ list.remove( getIndex() );
+ }
+ }
+
+ final class SimpleRemove extends AbstractValueDelayedOperation {
+
+ public SimpleRemove(Object orphan) {
+ super( null, orphan );
}
@Override
- public Object getAddedInstance() {
- return null;
- }
-
- @Override
- public Object getOrphan() {
- return value;
+ @SuppressWarnings("unchecked")
+ public void operate() {
+ list.remove( getOrphan() );
}
}
}
diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java
index ab85032c5c..8496697fb7 100644
--- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java
+++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java
@@ -570,59 +570,42 @@ public class PersistentMap extends AbstractPersistentCollection implements Map {
}
}
- final class Put implements DelayedOperation {
+ abstract class AbstractMapValueDelayedOperation extends AbstractValueDelayedOperation {
private Object index;
- private Object value;
- private Object old;
-
- public Put(Object index, Object value, Object old) {
+
+ protected AbstractMapValueDelayedOperation(Object index, Object addedValue, Object orphan) {
+ super( addedValue, orphan );
this.index = index;
- this.value = value;
- this.old = old;
}
- @Override
- @SuppressWarnings("unchecked")
- public void operate() {
- map.put( index, value );
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public Object getAddedInstance() {
- return value;
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public Object getOrphan() {
- return old;
+ protected final Object getIndex() {
+ return index;
}
}
- final class Remove implements DelayedOperation {
- private Object index;
- private Object old;
-
- public Remove(Object index, Object old) {
- this.index = index;
- this.old = old;
+ final class Put extends AbstractMapValueDelayedOperation {
+
+ public Put(Object index, Object addedValue, Object orphan) {
+ super( index, addedValue, orphan );
}
@Override
@SuppressWarnings("unchecked")
public void operate() {
- map.remove( index );
+ map.put( getIndex(), getAddedInstance() );
+ }
+ }
+
+ final class Remove extends AbstractMapValueDelayedOperation {
+
+ public Remove(Object index, Object orphan) {
+ super( index, null, orphan );
}
@Override
- public Object getAddedInstance() {
- return null;
- }
-
- @Override
- public Object getOrphan() {
- return old;
+ @SuppressWarnings("unchecked")
+ public void operate() {
+ map.remove( getIndex() );
}
}
}
diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java
index daf903825f..cc56ae6e80 100644
--- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java
+++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java
@@ -14,6 +14,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.hibernate.HibernateException;
@@ -460,51 +461,29 @@ public class PersistentSet extends AbstractPersistentCollection implements java.
}
}
- final class SimpleAdd implements DelayedOperation {
- private Object value;
-
- public SimpleAdd(Object value) {
- this.value = value;
+ final class SimpleAdd extends AbstractValueDelayedOperation {
+
+ public SimpleAdd(Object addedValue) {
+ super( addedValue, null );
}
@Override
@SuppressWarnings("unchecked")
public void operate() {
- set.add( value );
- }
-
- @Override
- public Object getAddedInstance() {
- return value;
- }
-
- @Override
- public Object getOrphan() {
- return null;
+ set.add( getAddedInstance() );
}
}
- final class SimpleRemove implements DelayedOperation {
- private Object value;
-
- public SimpleRemove(Object value) {
- this.value = value;
+ final class SimpleRemove extends AbstractValueDelayedOperation {
+
+ public SimpleRemove(Object orphan) {
+ super( null, orphan );
}
@Override
@SuppressWarnings("unchecked")
public void operate() {
- set.remove( value );
- }
-
- @Override
- public Object getAddedInstance() {
- return null;
- }
-
- @Override
- public Object getOrphan() {
- return value;
+ set.remove( getOrphan() );
}
}
}
diff --git a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java
index 818e321ca5..079fd9cca2 100644
--- a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java
+++ b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java
@@ -24,6 +24,7 @@ import org.hibernate.EntityMode;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
+import org.hibernate.collection.internal.AbstractPersistentCollection;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.CollectionEntry;
@@ -47,9 +48,6 @@ import org.hibernate.proxy.LazyInitializer;
import org.jboss.logging.Logger;
-import org.dom4j.Element;
-import org.dom4j.Node;
-
/**
* A type that handles Hibernate PersistentCollections (including arrays).
*
@@ -135,7 +133,7 @@ public abstract class CollectionType extends AbstractType implements Association
@Override
public Object nullSafeGet(ResultSet rs, String name, SessionImplementor session, Object owner) throws SQLException {
- return nullSafeGet( rs, new String[] {name}, session, owner );
+ return nullSafeGet( rs, new String[] { name }, session, owner );
}
@Override
@@ -649,6 +647,10 @@ public abstract class CollectionType extends AbstractType implements Association
return null;
}
if ( !Hibernate.isInitialized( original ) ) {
+ if ( ( (PersistentCollection) original ).hasQueuedOperations() ) {
+ final AbstractPersistentCollection pc = (AbstractPersistentCollection) original;
+ pc.replaceQueuedOperationValues( getPersister( session ), copyCache );
+ }
return target;
}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java
index 0c99517b3a..8b22d32c70 100644
--- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java
+++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java
@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import org.hibernate.Criteria;
+import org.hibernate.Hibernate;
import org.hibernate.NullPrecedence;
import org.hibernate.Session;
import org.hibernate.dialect.H2Dialect;
@@ -406,8 +407,12 @@ public class OrderByTest extends BaseCoreFunctionalTestCase {
assertEquals( 2, forum.getPosts().size() );
assertEquals( "post1", forum.getPosts().get( 0 ).getName() );
assertEquals( "post2", forum.getPosts().get( 1 ).getName() );
+ Hibernate.initialize( forum.getPosts() );
+ assertEquals( 2, forum.getPosts().size() );
assertEquals( 1, forum.getUsers().size() );
assertEquals( "john", forum.getUsers().get( 0 ).getName() );
+ Hibernate.initialize( forum.getUsers() );
+ assertEquals( 1, forum.getUsers().size() );
}
@Test
diff --git a/hibernate-core/src/test/java/org/hibernate/test/cascade/MergeTest.java b/hibernate-core/src/test/java/org/hibernate/test/cascade/MergeTest.java
new file mode 100644
index 0000000000..f17aabd3ba
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/cascade/MergeTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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 .
+ */
+package org.hibernate.test.cascade;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+
+import org.junit.Test;
+
+import org.hibernate.Hibernate;
+import org.hibernate.Session;
+import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+public class MergeTest extends BaseCoreFunctionalTestCase {
+
+ @Test
+ public void testMergeDetachedEntityWithNewOneToManyElements() {
+ Order order = new Order();
+
+ Session s = openSession();
+ s.getTransaction().begin();
+ s.persist( order );
+ s.getTransaction().commit();
+ s.close();
+
+ Item item1 = new Item();
+ item1.name = "i1";
+
+ Item item2 = new Item();
+ item2.name = "i2";
+
+ order.addItem( item1 );
+ order.addItem( item2 );
+
+ s = openSession();
+ s.getTransaction().begin();
+ order = (Order) s.merge( order );
+ s.flush();
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ order = s.get( Order.class, order.id );
+ assertEquals( 2, order.items.size() );
+ s.delete( order );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Test
+ public void testMergeEntityWithNewOneToManyElements() {
+ Order order = new Order();
+
+ Session s = openSession();
+ s.getTransaction().begin();
+ s.persist( order );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ order = s.get( Order.class, order.id );
+ Item item1 = new Item();
+ item1.name = "i1";
+ Item item2 = new Item();
+ item2.name = "i2";
+ order.addItem( item1 );
+ order.addItem( item2 );
+ assertFalse( Hibernate.isInitialized( order.items ) );
+ order = (Order) s.merge( order );
+ //s.flush();
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ order = s.get( Order.class, order.id );
+ assertEquals( 2, order.items.size() );
+ s.delete( order );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ protected Class[] getAnnotatedClasses() {
+ return new Class[] {
+ Order.class,
+ Item.class
+ };
+ }
+
+ @Entity
+ private static class Order {
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "order", orphanRemoval = true)
+ private List- items = new ArrayList
- ();
+
+ public Order() {
+ }
+
+ public void addItem(Item item) {
+ items.add( item );
+ item.order = this;
+ }
+ }
+
+ @Entity
+ private static class Item {
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ private String name;
+
+ @ManyToOne
+ private Order order;
+ }
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/bag/BagDuplicatesTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/BagDuplicatesTest.java
new file mode 100644
index 0000000000..d3dcb34ec0
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/BagDuplicatesTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2014 JBoss Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hibernate.test.collection.bag;
+
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+import org.hibernate.cfg.AvailableSettings;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
+import org.junit.Test;
+
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * This template demonstrates how to develop a test case for Hibernate ORM, using its built-in unit test framework.
+ * Although ORMStandaloneTestCase is perfectly acceptable as a reproducer, usage of this class is much preferred.
+ * Since we nearly always include a regression test with bug fixes, providing your reproducer using this method
+ * simplifies the process.
+ *
+ * What's even better? Fork hibernate-orm itself, add your test case directly to a module's unit tests, then
+ * submit it as a PR!
+ */
+public class BagDuplicatesTest extends BaseCoreFunctionalTestCase {
+
+ // Add your entities here.
+ @Override
+ protected Class[] getAnnotatedClasses() {
+ return new Class[] {
+ Parent.class,
+ Child.class
+ };
+ }
+
+ // If you use *.hbm.xml mappings, instead of annotations, add the mappings here.
+ @Override
+ protected String[] getMappings() {
+ return new String[] {
+// "Foo.hbm.xml",
+// "Bar.hbm.xml"
+ };
+ }
+ // If those mappings reside somewhere other than resources/org/hibernate/test, change this.
+ @Override
+ protected String getBaseForMappings() {
+ return "org/hibernate/test/";
+ }
+
+ // Add in any settings that are specific to your test. See resources/hibernate.properties for the defaults.
+ @Override
+ protected void configure(Configuration configuration) {
+ super.configure( configuration );
+
+ configuration.setProperty( AvailableSettings.SHOW_SQL, "true" );
+ }
+
+ // Add your tests, using standard JUnit.
+ @Test
+ public void HHH10385Test() throws Exception {
+ // BaseCoreFunctionalTestCase automatically creates the SessionFactory and provides the Session.
+ Session session = null;
+ Transaction transaction = null;
+
+ Long parentId = null;
+
+ try {
+ session = openSession();
+ transaction = session.beginTransaction();
+
+ Parent parent = new Parent();
+ session.persist(parent);
+ session.flush();
+ parentId = parent.getId();
+
+ transaction.commit();
+ } catch (HibernateException e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ fail(e.getMessage());
+ } finally {
+ if (session != null) {
+ session.close();
+ }
+ }
+
+ try {
+ session = openSession();
+ transaction = session.beginTransaction();
+
+ Parent parent = session.get(Parent.class, parentId);
+ Child child1 = new Child();
+ child1.setName("child1");
+ child1.setParent(parent);
+ parent.addChild(child1);
+ parent = (Parent) session.merge(parent);
+ session.flush();
+ //assertEquals(1, parent.getChildren().size());
+
+ transaction.commit();
+ } catch (HibernateException e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ fail(e.getMessage());
+ } finally {
+ if (session != null) {
+ session.close();
+ }
+ }
+
+ try {
+ session = openSession();
+ transaction = session.beginTransaction();
+
+ Parent parent = session.get(Parent.class, parentId);
+ assertEquals(1, parent.getChildren().size());
+
+ transaction.commit();
+ } catch (HibernateException e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ fail(e.getMessage());
+ } finally {
+ if (session != null) {
+ session.close();
+ }
+ }
+ }
+
+ @Entity(name = "Parent")
+ public static class Parent {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
+ private List children = new ArrayList();
+
+ public Parent() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+
+ public void addChild(Child child) {
+ children.add(child);
+ child.setParent(this);
+ }
+
+ public void removeChild(Child child) {
+ children.remove(child);
+ child.setParent(null);
+ }
+ }
+
+ @Entity(name = "Child")
+ public static class Child {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ private String name;
+
+ @ManyToOne
+ private Parent parent;
+
+ public Child() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Parent getParent() {
+ return parent;
+ }
+
+ public void setParent(Parent parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public String toString() {
+ return "Child{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ '}';
+ }
+ }
+
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/bag/Item.java b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/Item.java
new file mode 100644
index 0000000000..3429c588aa
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/Item.java
@@ -0,0 +1,37 @@
+/*
+ * 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 .
+ */
+package org.hibernate.test.collection.bag;
+
+/**
+ * @author Gail Badner
+ */
+public class Item {
+ private Long id;
+ private String name;
+ private Order order;
+
+ public Long getId() {
+ return id;
+ }
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Order getOrder() {
+ return order;
+ }
+ public void setOrder(Order order) {
+ this.order = order;
+ }
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/bag/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/Mappings.hbm.xml
index a412e36f77..134f3018fe 100644
--- a/hibernate-core/src/test/java/org/hibernate/test/collection/bag/Mappings.hbm.xml
+++ b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/Mappings.hbm.xml
@@ -23,4 +23,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/bag/Order.java b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/Order.java
new file mode 100644
index 0000000000..116390a927
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/Order.java
@@ -0,0 +1,41 @@
+/*
+ * 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 .
+ */
+package org.hibernate.test.collection.bag;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Gail Badner
+ */
+public class Order {
+ private Long id;
+
+ private List
- items = new ArrayList
- ();
+
+ public Order() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public List
- getItems() {
+ return items;
+ }
+ public void setItems(List
- items) {
+ this.items = items;
+ }
+
+ public void addItem(Item item) {
+ items.add( item );
+ item.setOrder( this );
+ }
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/bag/PersistentBagTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/PersistentBagTest.java
index 31c015dbdc..f41a241399 100644
--- a/hibernate-core/src/test/java/org/hibernate/test/collection/bag/PersistentBagTest.java
+++ b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/PersistentBagTest.java
@@ -14,6 +14,7 @@ import org.hibernate.collection.internal.PersistentBag;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -69,4 +70,37 @@ public class PersistentBagTest extends BaseCoreFunctionalTestCase {
session.getTransaction().commit();
session.close();
}
+
+ @Test
+ public void testMergePersistentEntityWithNewOneToManyElements() {
+ Order order = new Order();
+
+ Session s = openSession();
+ s.getTransaction().begin();
+ s.persist( order );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ order = s.get( Order.class, order.getId() );
+ Item item1 = new Item();
+ item1.setName( "i1" );
+ Item item2 = new Item();
+ item2.setName( "i2" );
+ order.addItem( item1 );
+ order.addItem( item2 );
+ order = (Order) s.merge( order );
+ //s.flush();
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ order = s.get( Order.class, order.getId() );
+ assertEquals( 2, order.getItems().size() );
+ s.delete( order );
+ s.getTransaction().commit();
+ s.close();
+ }
}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationTest.java
new file mode 100644
index 0000000000..0a18eac435
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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 .
+ */
+
+package org.hibernate.test.collection.delayedOperation;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.hibernate.Hibernate;
+import org.hibernate.Session;
+import org.hibernate.testing.TestForIssue;
+import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Tests delayed operations that are queued for a PersistentBag. The Bag does not have
+ * to be extra-lazy to queue the operations.
+ * @author Gail Badner
+ */
+public class BagDelayedOperationTest extends BaseCoreFunctionalTestCase {
+ private Long parentId;
+
+ @Override
+ protected Class[] getAnnotatedClasses() {
+ return new Class[] {
+ Parent.class,
+ Child.class
+ };
+ }
+
+ @Before
+ public void setup() {
+ // start by cleaning up in case a test fails
+ if ( parentId != null ) {
+ cleanup();
+ }
+
+ Parent parent = new Parent();
+ Child child1 = new Child( "Sherman" );
+ Child child2 = new Child( "Yogi" );
+ parent.addChild( child1 );
+ parent.addChild( child2 );
+
+ Session s = openSession();
+ s.getTransaction().begin();
+ s.persist( parent );
+ s.getTransaction().commit();
+ s.close();
+
+ parentId = parent.getId();
+ }
+
+ @After
+ public void cleanup() {
+ Session s = openSession();
+ s.getTransaction().begin();
+ Parent parent = s.get( Parent.class, parentId );
+ parent.getChildren().clear();
+ s.delete( parent );
+ s.getTransaction().commit();
+ s.close();
+
+ parentId = null;
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleAddDetached() {
+ // Create 2 detached Child objects.
+ Session s = openSession();
+ s.getTransaction().begin();
+ Child c1 = new Child( "Darwin" );
+ s.persist( c1 );
+ Child c2 = new Child( "Comet" );
+ s.persist( c2 );
+ s.getTransaction().commit();
+ s.close();
+
+ // Now Child c is detached.
+
+ s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // add detached Child c
+ p.addChild( c1 );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add a detached Child and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 3, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add another detached Child, merge, and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ p.addChild( c2 );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 4, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleAddTransient() {
+ // Add a transient Child and commit.
+ Session s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // add transient Child
+ p.addChild( new Child( "Darwin" ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 3, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add another transient Child and commit again.
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // add transient Child
+ p.addChild( new Child( "Comet" ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 4, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleAddManaged() {
+ // Add 2 Child entities
+ Session s = openSession();
+ s.getTransaction().begin();
+ Child c1 = new Child( "Darwin" );
+ s.persist( c1 );
+ Child c2 = new Child( "Comet" );
+ s.persist( c2 );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add a managed Child and commit
+ s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // get the first Child so it is managed; add to collection
+ p.addChild( s.get( Child.class, c1.getId() ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 3, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add the other managed Child, merge and commit.
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // get the second Child so it is managed; add to collection
+ p.addChild( s.get( Child.class, c2.getId() ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 4, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Entity(name = "Parent")
+ public static class Parent {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ // Don't need extra-lazy to delay add operations to a bag.
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
+ private List children = new ArrayList();
+
+ public Parent() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+
+ public void addChild(Child child) {
+ children.add(child);
+ child.setParent(this);
+ }
+ }
+
+ @Entity(name = "Child")
+ public static class Child {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @Column(nullable = false)
+ private String name;
+
+ @ManyToOne
+ private Parent parent;
+
+ public Child() {
+ }
+
+ public Child(String name) {
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Parent getParent() {
+ return parent;
+ }
+
+ public void setParent(Parent parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public String toString() {
+ return "Child{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) {
+ return true;
+ }
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+
+ Child child = (Child) o;
+
+ return name.equals( child.name );
+
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+ }
+
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/ListDelayedOperationTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/ListDelayedOperationTest.java
new file mode 100644
index 0000000000..ff768dd5da
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/ListDelayedOperationTest.java
@@ -0,0 +1,465 @@
+/*
+ * 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 .
+ */
+
+package org.hibernate.test.collection.delayedOperation;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.OrderColumn;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.hibernate.Hibernate;
+import org.hibernate.Session;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.hibernate.testing.TestForIssue;
+import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests delayed operations that are queued for a PersistentSet. remove( Object )
+ * requires extra lazy to queue the operations.
+ * @author Gail Badner
+ */
+public class ListDelayedOperationTest extends BaseCoreFunctionalTestCase {
+ private Long parentId;
+ private Long childId1;
+ private Long childId2;
+
+ @Override
+ protected Class[] getAnnotatedClasses() {
+ return new Class[] {
+ Parent.class,
+ Child.class
+ };
+ }
+
+ @Before
+ public void setup() {
+ // start by cleaning up in case a test fails
+ if ( parentId != null ) {
+ cleanup();
+ }
+
+ Parent parent = new Parent();
+ Child child1 = new Child( "Sherman" );
+ Child child2 = new Child( "Yogi" );
+ parent.addChild( child1 );
+ parent.addChild( child2 );
+
+ Session s = openSession();
+ s.getTransaction().begin();
+ s.persist( parent );
+ s.getTransaction().commit();
+ s.close();
+
+ parentId = parent.getId();
+ childId1 = child1.getId();
+ childId2 = child2.getId();
+ }
+
+ @After
+ public void cleanup() {
+ Session s = openSession();
+ s.getTransaction().begin();
+ Parent parent = s.get( Parent.class, parentId );
+ parent.getChildren().clear();
+ s.delete( parent );
+ s.getTransaction().commit();
+ s.close();
+
+ parentId = null;
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleAddDetached() {
+ // Create 2 detached Child objects.
+ Session s = openSession();
+ s.getTransaction().begin();
+ Child c1 = new Child( "Darwin" );
+ s.persist( c1 );
+ Child c2 = new Child( "Comet" );
+ s.persist( c2 );
+ s.getTransaction().commit();
+ s.close();
+
+ // Now Child c is detached.
+
+ s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // add detached Child c
+ p.addChild( c1 );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add a detached Child and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 3, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add another detached Child, merge, and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ p.addChild( c2 );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 4, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleAddTransient() {
+ // Add a transient Child and commit.
+ Session s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // add transient Child
+ p.addChild( new Child( "Darwin" ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 3, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add another transient Child and commit again.
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // add transient Child
+ p.addChild( new Child( "Comet" ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 4, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleAddManaged() {
+ // Add 2 Child entities
+ Session s = openSession();
+ s.getTransaction().begin();
+ Child c1 = new Child( "Darwin" );
+ s.persist( c1 );
+ Child c2 = new Child( "Comet" );
+ s.persist( c2 );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add a managed Child and commit
+ s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // get the first Child so it is managed; add to collection
+ p.addChild( s.get( Child.class, c1.getId() ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 3, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add the other managed Child, merge and commit.
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // get the second Child so it is managed; add to collection
+ p.addChild( s.get( Child.class, c2.getId() ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 4, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleRemoveDetached() {
+ // Get the 2 Child entities and detach.
+ Session s = openSession();
+ s.getTransaction().begin();
+ Child c1 = s.get( Child.class, childId1 );
+ Child c2 = s.get( Child.class, childId2 );
+ s.getTransaction().commit();
+ s.close();
+
+ // Remove a detached entity element and commit
+ s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // remove a detached element and commit
+ Hibernate.initialize( p.getChildren() );
+ p.removeChild( c1 );
+ for ( Child c : p.getChildren() ) {
+ if ( c.equals( c1 ) ) {
+ s.evict( c );
+ }
+ }
+ assertTrue( Hibernate.isInitialized( p.getChildren() ) );
+ //s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ // Remove a detached entity element, merge, and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ Hibernate.initialize( p.getChildren() );
+ assertEquals( 1, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Remove a detached entity element, merge, and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // remove a detached element and commit
+ p.removeChild( c2 );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ p = (Parent) s.merge( p );
+ Hibernate.initialize( p );
+ s.getTransaction().commit();
+ s.close();
+
+ // Remove a detached entity element, merge, and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 0, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+/* STILL WORKING ON THIS ONE...
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleRemoveManaged() {
+ // Remove a managed entity element and commit
+ Session s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // get c1 so it is managed, then remove and commit
+ p.removeChild( s.get( Child.class, childId1 ) );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 1, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // get c1 so it is managed, then remove, merge and commit
+ p.removeChild( s.get( Child.class, childId2 ) );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 0, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+*/
+
+ @Entity(name = "Parent")
+ public static class Parent {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
+ @LazyCollection(LazyCollectionOption.EXTRA )
+ @OrderColumn
+ private List children = new ArrayList();
+
+ public Parent() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+
+ public void addChild(Child child) {
+ children.add(child);
+ child.setParent(this);
+ }
+
+ public void addChild(Child child, int i) {
+ children.add(i, child );
+ child.setParent(this);
+ }
+
+ public void removeChild(Child child) {
+ children.remove(child);
+ child.setParent(null);
+ }
+ }
+
+ @Entity(name = "Child")
+ public static class Child {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @Column(nullable = false)
+ private String name;
+
+ @ManyToOne
+ private Parent parent;
+
+ public Child() {
+ }
+
+ public Child(String name) {
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Parent getParent() {
+ return parent;
+ }
+
+ public void setParent(Parent parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public String toString() {
+ return "Child{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) {
+ return true;
+ }
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+
+ Child child = (Child) o;
+
+ return name.equals( child.name );
+
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+ }
+
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/SetDelayedOperationTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/SetDelayedOperationTest.java
new file mode 100644
index 0000000000..4c7dbe5644
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/SetDelayedOperationTest.java
@@ -0,0 +1,448 @@
+/*
+ * 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 .
+ */
+
+package org.hibernate.test.collection.delayedOperation;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.hibernate.Hibernate;
+import org.hibernate.Session;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.hibernate.testing.TestForIssue;
+import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Tests delayed operations that are queued for a PersistentSet. The Set must be
+ * extra lazy to queue the operations.
+ * @author Gail Badner
+ */
+public class SetDelayedOperationTest extends BaseCoreFunctionalTestCase {
+ private Long parentId;
+ private Long childId1;
+ private Long childId2;
+
+ @Override
+ protected Class[] getAnnotatedClasses() {
+ return new Class[] {
+ Parent.class,
+ Child.class
+ };
+ }
+
+ @Before
+ public void setup() {
+ // start by cleaning up in case a test fails
+ if ( parentId != null ) {
+ cleanup();
+ }
+
+ Parent parent = new Parent();
+ Child child1 = new Child( "Sherman" );
+ Child child2 = new Child( "Yogi" );
+ parent.addChild( child1 );
+ parent.addChild( child2 );
+
+ Session s = openSession();
+ s.getTransaction().begin();
+ s.persist( parent );
+ s.getTransaction().commit();
+ s.close();
+
+ parentId = parent.getId();
+ childId1 = child1.getId();
+ childId2 = child2.getId();
+ }
+
+ @After
+ public void cleanup() {
+ Session s = openSession();
+ s.getTransaction().begin();
+ Parent parent = s.get( Parent.class, parentId );
+ parent.getChildren().clear();
+ s.delete( parent );
+ s.getTransaction().commit();
+ s.close();
+
+ parentId = null;
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleAddDetached() {
+ // Create 2 detached Child objects.
+ Session s = openSession();
+ s.getTransaction().begin();
+ Child c1 = new Child( "Darwin" );
+ s.persist( c1 );
+ Child c2 = new Child( "Comet" );
+ s.persist( c2 );
+ s.getTransaction().commit();
+ s.close();
+
+ // Now Child c is detached.
+
+ s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // add detached Child c
+ p.addChild( c1 );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add a detached Child and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 3, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add another detached Child, merge, and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ p.addChild( c2 );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 4, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleAddTransient() {
+ // Add a transient Child and commit.
+ Session s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // add transient Child
+ p.addChild( new Child( "Darwin" ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 3, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add another transient Child and commit again.
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // add transient Child
+ p.addChild( new Child( "Comet" ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 4, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleAddManaged() {
+ // Add 2 Child entities
+ Session s = openSession();
+ s.getTransaction().begin();
+ Child c1 = new Child( "Darwin" );
+ s.persist( c1 );
+ Child c2 = new Child( "Comet" );
+ s.persist( c2 );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add a managed Child and commit
+ s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // get the first Child so it is managed; add to collection
+ p.addChild( s.get( Child.class, c1.getId() ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 3, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Add the other managed Child, merge and commit.
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // get the second Child so it is managed; add to collection
+ p.addChild( s.get( Child.class, c2.getId() ) );
+ // collection should still be uninitialized
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 4, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleRemoveDetached() {
+ // Get the 2 Child entities and detach.
+ Session s = openSession();
+ s.getTransaction().begin();
+ Child c1 = s.get( Child.class, childId1 );
+ Child c2 = s.get( Child.class, childId2 );
+ s.getTransaction().commit();
+ s.close();
+
+ // Remove a detached entity element and commit
+ s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // remove a detached element and commit
+ p.removeChild( c1 );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ // Remove a detached entity element, merge, and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 1, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ // Remove a detached entity element, merge, and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // remove a detached element and commit
+ p.removeChild( c2 );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ p = (Parent) s.merge( p );
+ Hibernate.initialize( p );
+ s.getTransaction().commit();
+ s.close();
+
+ // Remove a detached entity element, merge, and commit
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 0, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-5855")
+ public void testSimpleRemoveManaged() {
+ // Remove a managed entity element and commit
+ Session s = openSession();
+ s.getTransaction().begin();
+ Parent p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // get c1 so it is managed, then remove and commit
+ p.removeChild( s.get( Child.class, childId1 ) );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 1, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ // get c1 so it is managed, then remove, merge and commit
+ p.removeChild( s.get( Child.class, childId2 ) );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ s.merge( p );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.getTransaction().begin();
+ p = s.get( Parent.class, parentId );
+ assertFalse( Hibernate.isInitialized( p.getChildren() ) );
+ assertEquals( 0, p.getChildren().size() );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ @Entity(name = "Parent")
+ public static class Parent {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
+ @LazyCollection( value = LazyCollectionOption.EXTRA)
+ private Set children = new HashSet();
+
+ public Parent() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Set getChildren() {
+ return children;
+ }
+
+ public void addChild(Child child) {
+ children.add(child);
+ child.setParent(this);
+ }
+
+ public void removeChild(Child child) {
+ children.remove(child);
+ child.setParent(null);
+ }
+ }
+
+ @Entity(name = "Child")
+ public static class Child {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @Column(nullable = false)
+ private String name;
+
+ @ManyToOne
+ private Parent parent;
+
+ public Child() {
+ }
+
+ public Child(String name) {
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Parent getParent() {
+ return parent;
+ }
+
+ public void setParent(Parent parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public String toString() {
+ return "Child{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) {
+ return true;
+ }
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+
+ Child child = (Child) o;
+
+ return name.equals( child.name );
+
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+ }
+
+}
diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/cascade/MergeTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/cascade/MergeTest.java
new file mode 100644
index 0000000000..de99bbd1e0
--- /dev/null
+++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/cascade/MergeTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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 .
+ */
+package org.hibernate.jpa.test.cascade;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.EntityManager;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+
+import org.junit.Test;
+
+import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
+
+import static org.junit.Assert.assertEquals;
+
+public class MergeTest extends BaseEntityManagerFunctionalTestCase {
+
+ @Test
+ public void testMergeDetachedEntityWithNewOneToManyElements() {
+ Order order = new Order();
+
+ EntityManager em = getOrCreateEntityManager();
+ em.getTransaction().begin();
+ em.persist( order );
+ em.getTransaction().commit();
+ em.close();
+
+ Item item1 = new Item();
+ item1.name = "i1";
+
+ Item item2 = new Item();
+ item2.name = "i2";
+
+ order.addItem( item1 );
+ order.addItem( item2 );
+
+ em = getOrCreateEntityManager();
+ em.getTransaction().begin();
+ order = em.merge( order );
+ em.flush();
+ em.getTransaction().commit();
+ em.close();
+
+ em = getOrCreateEntityManager();
+ em.getTransaction().begin();
+ order = em.find( Order.class, order.id );
+ assertEquals( 2, order.items.size() );
+ em.remove( order );
+ em.getTransaction().commit();
+ em.close();
+ }
+
+ @Test
+ public void testMergeLoadedEntityWithNewOneToManyElements() {
+ Order order = new Order();
+
+ EntityManager em = getOrCreateEntityManager();
+ em.getTransaction().begin();
+ em.persist( order );
+ em.getTransaction().commit();
+ em.close();
+
+ em = getOrCreateEntityManager();
+ em.getTransaction().begin();
+ order = em.find( Order.class, order.id );
+ Item item1 = new Item();
+ item1.name = "i1";
+ Item item2 = new Item();
+ item2.name = "i2";
+ order.addItem( item1 );
+ order.addItem( item2 );
+ order = em.merge( order );
+ em.flush();
+ em.getTransaction().commit();
+ em.close();
+
+ em = getOrCreateEntityManager();
+ em.getTransaction().begin();
+ order = em.find( Order.class, order.id );
+ assertEquals( 2, order.items.size() );
+ em.remove( order );
+ em.getTransaction().commit();
+ em.close();
+ }
+
+ protected Class[] getAnnotatedClasses() {
+ return new Class[] {
+ Order.class,
+ Item.class
+ };
+ }
+
+ @Entity
+ private static class Order {
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "order", orphanRemoval = true)
+ private List
- items = new ArrayList
- ();
+
+ public Order() {
+ }
+
+ public void addItem(Item item) {
+ items.add( item );
+ item.order = this;
+ }
+ }
+
+ @Entity
+ private static class Item {
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ private String name;
+
+ @ManyToOne
+ private Order order;
+ }
+}