From c1934b72edb4f781520937618b3b750bebb84576 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 8 Sep 2015 18:53:18 -0700 Subject: [PATCH] HHH-5855 : Merge causes a duplicated "insert" of a child entity in lazy collection (cherry picked from commit efa72a83336430b99c0f03a62294fae816485c04) --- .../AbstractPersistentCollection.java | 49 ++ .../collection/internal/PersistentBag.java | 20 +- .../collection/internal/PersistentList.java | 113 ++--- .../collection/internal/PersistentMap.java | 59 +-- .../collection/internal/PersistentSet.java | 43 +- .../org/hibernate/type/CollectionType.java | 10 +- .../annotations/onetomany/OrderByTest.java | 5 + .../org/hibernate/test/cascade/MergeTest.java | 134 +++++ .../collection/bag/BagDuplicatesTest.java | 230 +++++++++ .../hibernate/test/collection/bag/Item.java | 37 ++ .../test/collection/bag/Mappings.hbm.xml | 18 + .../hibernate/test/collection/bag/Order.java | 41 ++ .../collection/bag/PersistentBagTest.java | 34 ++ .../BagDelayedOperationTest.java | 340 +++++++++++++ .../ListDelayedOperationTest.java | 465 ++++++++++++++++++ .../SetDelayedOperationTest.java | 448 +++++++++++++++++ .../hibernate/jpa/test/cascade/MergeTest.java | 131 +++++ 17 files changed, 2005 insertions(+), 172 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/cascade/MergeTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/collection/bag/BagDuplicatesTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/collection/bag/Item.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/collection/bag/Order.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/ListDelayedOperationTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/SetDelayedOperationTest.java create mode 100644 hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/cascade/MergeTest.java 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; + } +}