HHH-9106 : Multiple detached representations of the same entity cannot be merged

This commit is contained in:
Gail Badner 2014-04-07 16:01:16 -07:00
parent a65ca8044a
commit 0118376f8e
6 changed files with 370 additions and 14 deletions

View File

@ -39,7 +39,14 @@ public abstract class AbstractOperationTestCase extends BaseCoreFunctionalTestCa
}
public String[] getMappings() {
return new String[] { "ops/Node.hbm.xml", "ops/Employer.hbm.xml", "ops/OptLockEntity.hbm.xml", "ops/OneToOne.hbm.xml", "ops/Competition.hbm.xml" };
return new String[] {
"ops/Node.hbm.xml",
"ops/Employer.hbm.xml",
"ops/OptLockEntity.hbm.xml",
"ops/OneToOne.hbm.xml",
"ops/Competition.hbm.xml",
"ops/Hoarder.hbm.xml"
};
}
public String getCacheConcurrencyStrategy() {

View File

@ -0,0 +1,57 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.ops;
/**
* @author Gail Badner
*/
public class Category {
private Long id;
private String name;
private Item exampleItem;
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 Item getExampleItem() {
return exampleItem;
}
public void setExampleItem(Item exampleItem) {
this.exampleItem = exampleItem;
}
}

View File

@ -0,0 +1,42 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--
-->
<hibernate-mapping package="org.hibernate.test.ops">
<class name="Hoarder">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<many-to-one name="favoriteItem" cascade="persist,merge,save-update" />
<set name="items" cascade="persist,merge,save-update">
<key column="HOARDER_ID"/>
<one-to-many class="Item" />
</set>
</class>
<class name="Item">
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" />
<property name="description" />
<many-to-one name="category" cascade="persist,merge,save-update"/>
</class>
<class name="Category">
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" />
<many-to-one name="exampleItem" cascade="persist,merge,save-update" />
</class>
</hibernate-mapping>

View File

@ -0,0 +1,69 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.ops;
import java.util.HashSet;
import java.util.Set;
/**
* @author Gail Badner
*/
public class Hoarder {
private Long id;
private String name;
private Item favoriteItem;
private Set<Item> items = new HashSet<Item>();
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 Item getFavoriteItem() {
return favoriteItem;
}
public void setFavoriteItem(Item favoriteItem) {
this.favoriteItem = favoriteItem;
}
public Set<Item> getItems() {
return items;
}
public void setItems(Set<Item> items) {
this.items = items;
}
}

View File

@ -0,0 +1,89 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.ops;
/**
* @author Gail Badner
*/
public class Item {
private Long id;
private String name;
private String description;
private Category category;
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 String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Item item = (Item) o;
if ( !name.equals( item.name ) ) {
return false;
}
return true;
}
@Override
public int hashCode() {
return name.hashCode();
}
}

View File

@ -35,6 +35,7 @@ import org.hibernate.Session;
import org.hibernate.StaleObjectStateException;
import org.hibernate.Transaction;
import org.hibernate.criterion.Projections;
import org.hibernate.testing.FailureExpected;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -493,7 +494,7 @@ public class MergeTest extends AbstractOperationTestCase {
child = (NumberedNode) root.getChildren().iterator().next();
grandchild = (NumberedNode) child.getChildren().iterator().next();
grandchild.setDescription("the grand child");
grandchild.setDescription( "the grand child" );
NumberedNode grandchild2 = new NumberedNode("grandchild2");
child.addChild( grandchild2 );
@ -503,7 +504,7 @@ public class MergeTest extends AbstractOperationTestCase {
tx.commit();
s.close();
assertInsertCount(1);
assertInsertCount( 1 );
assertUpdateCount(1);
clearCounts();
@ -512,7 +513,7 @@ public class MergeTest extends AbstractOperationTestCase {
NumberedNode child2 = new NumberedNode("child2");
NumberedNode grandchild3 = new NumberedNode("grandchild3");
child2.addChild( grandchild3 );
root.addChild(child2);
root.addChild( child2 );
s = openSession();
tx = s.beginTransaction();
@ -615,19 +616,19 @@ public class MergeTest extends AbstractOperationTestCase {
tx = s.beginTransaction();
NumberedNode child = new NumberedNode("child");
root.addChild(child);
assertSame( root, s.merge(root) );
root.addChild( child );
assertSame( root, s.merge( root ) );
Object mergedChild = root.getChildren().iterator().next();
assertNotSame( mergedChild, child );
assertTrue( s.contains(mergedChild) );
assertTrue( s.contains( mergedChild ) );
assertFalse( s.contains(child) );
assertEquals( root.getChildren().size(), 1 );
assertTrue( root.getChildren().contains(mergedChild) );
//assertNotSame( mergedChild, s.merge(child) ); //yucky :(
tx.commit();
assertInsertCount(1);
assertUpdateCount(0);
assertInsertCount( 1 );
assertUpdateCount( 0 );
assertEquals( root.getChildren().size(), 1 );
assertTrue( root.getChildren().contains(mergedChild) );
@ -651,7 +652,7 @@ public class MergeTest extends AbstractOperationTestCase {
Transaction tx = s.beginTransaction();
NumberedNode root = new NumberedNode( "root" );
root.addChild( new NumberedNode( "child" ) );
s.persist(root);
s.persist( root );
tx.commit();
s.close();
@ -671,14 +672,14 @@ public class MergeTest extends AbstractOperationTestCase {
assertFalse( Hibernate.isInitialized( managedChildren ) );
tx.commit();
assertInsertCount(0);
assertUpdateCount(0);
assertDeleteCount(0);
assertInsertCount( 0 );
assertUpdateCount( 0 );
assertDeleteCount( 0 );
tx = s.beginTransaction();
assertEquals(
Long.valueOf( 2 ),
s.createCriteria(NumberedNode.class)
s.createCriteria( NumberedNode.class )
.setProjection( Projections.rowCount() )
.uniqueResult()
);
@ -825,6 +826,97 @@ public class MergeTest extends AbstractOperationTestCase {
cleanup();
}
@Test
@FailureExpected( jiraKey = "HHH-9106")
public void testMergeTransientWithMultipleDetachedRepresentationOfSameEntity() {
Item item1 = new Item();
item1.setName( "item1" );
item1.setDescription( "item1 description" );
Session s = openSession();
Transaction tx = session.beginTransaction();
s.persist( item1 );
tx.commit();
s.close();
// Get 2 representations of the same Item from 3 different sessions.
s = openSession();
Item item1_1 = (Item) s.get( Item.class, item1.getId() );
s.close();
s = openSession();
Item item1_2 = (Item) s.get( Item.class, item1.getId() );
s.close();
// item1_1 and item1_2 are unmodified representations of the same persistent entity.
assertFalse( item1_1 == item1_2 );
assertTrue( item1_1.equals( item1_2 ) );
// Create a transient entity that references both representations.
Hoarder hoarder = new Hoarder();
hoarder.setName( "joe" );
hoarder.getItems().add( item1_1 );
hoarder.setFavoriteItem( item1_2 );
s = openSession();
tx = s.beginTransaction();
hoarder = (Hoarder) s.merge( hoarder );
assertSame( hoarder.getFavoriteItem(), hoarder.getItems().iterator().next() );
assertEquals( item1.getId(), hoarder.getFavoriteItem().getId() );
assertEquals( item1.getDescription(), hoarder.getFavoriteItem().getDescription() );
assertEquals( item1.getCategory(), hoarder.getFavoriteItem().getCategory() );
tx.commit();
s.close();
}
@Test
@FailureExpected( jiraKey = "HHH-9106")
public void testMergeDetachedWithDetachedRepresentationOfSameEntity() {
Item item1 = new Item();
item1.setName( "item1" );
item1.setDescription( "item1 description" );
Hoarder hoarder = new Hoarder();
hoarder.setName( "joe" );
Session s = openSession();
Transaction tx = session.beginTransaction();
s.persist( item1 );
s.persist( hoarder );
tx.commit();
s.close();
// Get 2 representations of the same Item from 3 different sessions.
s = openSession();
Item item1_1 = (Item) s.get( Item.class, item1.getId() );
s.close();
s = openSession();
Item item1_2 = (Item) s.get( Item.class, item1.getId() );
s.close();
// item1_1 and item1_2 are unmodified representations of the same persistent entity.
assertFalse( item1_1 == item1_2 );
assertTrue( item1_1.equals( item1_2 ) );
// Update hoarder (detached) to references both representations.
hoarder.getItems().add( item1_1 );
hoarder.setFavoriteItem( item1_2 );
s = openSession();
tx = s.beginTransaction();
hoarder = (Hoarder) s.merge( hoarder );
assertSame( hoarder.getFavoriteItem(), hoarder.getItems().iterator().next() );
assertEquals( item1.getId(), hoarder.getFavoriteItem().getId() );
assertEquals( item1.getDescription(), hoarder.getFavoriteItem().getDescription() );
assertEquals( item1.getCategory(), hoarder.getFavoriteItem().getCategory() );
tx.commit();
s.close();
}
@SuppressWarnings( {"unchecked"})
private void cleanup() {
Session s = openSession();