HHH-5855 : Merge causes a duplicated "insert" of a child entity in lazy collection

This commit is contained in:
Gail Badner 2015-09-08 18:53:18 -07:00
parent 6d08b3526c
commit efa72a8333
17 changed files with 2005 additions and 172 deletions

View File

@ -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 org.hibernate.AssertionFailure;
import org.hibernate.FlushMode;
@ -471,6 +472,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.
@ -1125,6 +1140,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

View File

@ -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() );
}
}
}

View File

@ -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() );
}
}
}

View File

@ -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() );
}
}
}

View File

@ -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() );
}
}
}

View File

@ -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 <tt>PersistentCollection</tt>s (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;
}

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Item> items = new ArrayList<Item>();
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;
}
}

View File

@ -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<Child> children = new ArrayList<Child>();
public Parent() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Child> 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 + '\'' +
'}';
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}

View File

@ -23,4 +23,22 @@
</bag>
</class>
<class name="Order" table="TAuction">
<id name="id">
<generator class="native"/>
</id>
<bag name="items" inverse="true"
cascade="all-delete-orphan">
<key column="orderId"/>
<one-to-many class="Item"/>
</bag>
</class>
<class name="Item" table="TBid">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<many-to-one name="order" column="orderId"/>
</class>
</hibernate-mapping>

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Item> items = new ArrayList<Item>();
public Order() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Item> getItems() {
return items;
}
public void setItems(List<Item> items) {
this.items = items;
}
public void addItem(Item item) {
items.add( item );
item.setOrder( this );
}
}

View File

@ -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();
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Child> children = new ArrayList<Child>();
public Parent() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Child> 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();
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Child> children = new ArrayList<Child>();
public Parent() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Child> 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();
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Child> children = new HashSet<Child>();
public Parent() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<Child> 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();
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Item> items = new ArrayList<Item>();
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;
}
}