From 0118376f8efd9b35d8be815c892f3ac7f9b6048f Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 7 Apr 2014 16:01:16 -0700 Subject: [PATCH] HHH-9106 : Multiple detached representations of the same entity cannot be merged --- .../test/ops/AbstractOperationTestCase.java | 9 +- .../java/org/hibernate/test/ops/Category.java | 57 +++++++++ .../org/hibernate/test/ops/Hoarder.hbm.xml | 42 +++++++ .../java/org/hibernate/test/ops/Hoarder.java | 69 ++++++++++ .../java/org/hibernate/test/ops/Item.java | 89 +++++++++++++ .../org/hibernate/test/ops/MergeTest.java | 118 ++++++++++++++++-- 6 files changed, 370 insertions(+), 14 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/ops/Category.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/ops/Hoarder.hbm.xml create mode 100644 hibernate-core/src/test/java/org/hibernate/test/ops/Hoarder.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/ops/Item.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/ops/AbstractOperationTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/ops/AbstractOperationTestCase.java index 51523353c2..44a829e05c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/ops/AbstractOperationTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/test/ops/AbstractOperationTestCase.java @@ -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() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/ops/Category.java b/hibernate-core/src/test/java/org/hibernate/test/ops/Category.java new file mode 100644 index 0000000000..adee4d7971 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/ops/Category.java @@ -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; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/ops/Hoarder.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/ops/Hoarder.hbm.xml new file mode 100644 index 0000000000..79f4dd6bcf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/ops/Hoarder.hbm.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/ops/Hoarder.java b/hibernate-core/src/test/java/org/hibernate/test/ops/Hoarder.java new file mode 100644 index 0000000000..4c03ae76c9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/ops/Hoarder.java @@ -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 items = new HashSet(); + + 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 getItems() { + return items; + } + + public void setItems(Set items) { + this.items = items; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/ops/Item.java b/hibernate-core/src/test/java/org/hibernate/test/ops/Item.java new file mode 100644 index 0000000000..184b86b6c9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/ops/Item.java @@ -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(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/ops/MergeTest.java b/hibernate-core/src/test/java/org/hibernate/test/ops/MergeTest.java index 00b5dba4fb..b06fcfba2b 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/ops/MergeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/ops/MergeTest.java @@ -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();