diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/Category.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/Category.java new file mode 100644 index 0000000000..1797ee16dc --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/Category.java @@ -0,0 +1,109 @@ +/* + * 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.jpa.test.emops; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +/** + * @author Gail Badner + */ +@Entity +public class Category { + @Id + @GeneratedValue + private Long id; + private String name; + + @ManyToOne( cascade = { CascadeType.PERSIST, CascadeType.MERGE } ) + private Item exampleItem; + private int version; + + 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; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + @Override + public String toString() { + return "Category{" + + "id=" + id + + ", name='" + name + '\'' + + ", version=" + version + + '}'; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + Category category = (Category) o; + + if ( name != null ? !name.equals( category.name ) : category.name != null ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return name != null ? name.hashCode() : 0; + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/Hoarder.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/Hoarder.java new file mode 100644 index 0000000000..3ffab53036 --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/Hoarder.java @@ -0,0 +1,93 @@ +/* + * 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.jpa.test.emops; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +/** + * @author Gail Badner + */ +@Entity +public class Hoarder { + @Id + @GeneratedValue + private Long id; + private String name; + + @ManyToOne( cascade = { CascadeType.PERSIST, CascadeType.MERGE } ) + private Item favoriteItem; + + @OneToMany( cascade = { CascadeType.PERSIST, CascadeType.MERGE } ) + @JoinColumn + 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; + } + + @Override + public String toString() { + return "Hoarder{" + + "id=" + id + + ", name='" + name + '\'' + + ", favoriteItem=" + favoriteItem + + '}'; + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/Item.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/Item.java new file mode 100644 index 0000000000..f92ac25436 --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/Item.java @@ -0,0 +1,111 @@ +/* + * 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.jpa.test.emops; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +/** + * @author Gail Badner + */ +@Entity +public class Item { + @Id + @GeneratedValue + private Long id; + private int version; + private String name; + + @ManyToOne( cascade = { CascadeType.PERSIST, CascadeType.MERGE } ) + private Category category; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + 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(); + } + + @Override + public String toString() { + return "Item{" + + "id=" + id + + ", version=" + version + + ", name='" + name + '\'' + + //", category=" + category + + //", subItems=" + subItems + + '}'; + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/MergeMultipleEntityRepresentationsAllowedTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/MergeMultipleEntityRepresentationsAllowedTest.java new file mode 100644 index 0000000000..95e0901c7a --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/MergeMultipleEntityRepresentationsAllowedTest.java @@ -0,0 +1,197 @@ +/* + * 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.jpa.test.emops; + +import java.util.List; +import javax.persistence.EntityManager; + +import org.junit.Test; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.spi.EventType; +import org.hibernate.jpa.event.internal.core.JpaEntityCopyAllowedMergeEventListener; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * Tests merging multiple detached representations of the same entity using + * {@link org.hibernate.jpa.event.internal.core.JpaEntityCopyAllowedMergeEventListener}. + * + * @author Gail Badner + */ +@TestForIssue( jiraKey = "HHH-9106") +public class MergeMultipleEntityRepresentationsAllowedTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected void afterEntityManagerFactoryBuilt() { + super.afterEntityManagerFactoryBuilt(); + + SessionFactoryImplementor sfi = entityManagerFactory().unwrap( SessionFactoryImplementor.class ); + EventListenerRegistry registry = sfi.getServiceRegistry().getService( EventListenerRegistry.class ); + registry.setListeners( EventType.MERGE, new JpaEntityCopyAllowedMergeEventListener() ); + } + + @Test + public void testCascadeFromDetachedToNonDirtyRepresentations() { + Item item1 = new Item(); + item1.setName( "item1" ); + + Hoarder hoarder = new Hoarder(); + hoarder.setName( "joe" ); + + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.persist( item1 ); + em.persist( hoarder ); + em.getTransaction().commit(); + em.close(); + + // Get another representation of the same Item from a different EntityManager. + + em = getOrCreateEntityManager(); + Item item1_1 = em.find( Item.class, item1.getId() ); + em.close(); + + // item1_1 and item1_2 are unmodified representations of the same persistent entity. + assertFalse( item1 == item1_1 ); + assertTrue( item1.equals( item1_1 ) ); + + // Update hoarder (detached) to references both representations. + hoarder.getItems().add( item1 ); + hoarder.setFavoriteItem( item1_1 ); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + hoarder = em.merge( hoarder ); + assertEquals( 1, hoarder.getItems().size() ); + assertSame( hoarder.getFavoriteItem(), hoarder.getItems().iterator().next() ); + assertEquals( item1.getId(), hoarder.getFavoriteItem().getId() ); + assertEquals( item1.getCategory(), hoarder.getFavoriteItem().getCategory() ); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + hoarder = em.merge( hoarder ); + assertEquals( 1, hoarder.getItems().size() ); + assertSame( hoarder.getFavoriteItem(), hoarder.getItems().iterator().next() ); + assertEquals( item1.getId(), hoarder.getFavoriteItem().getId() ); + assertEquals( item1.getCategory(), hoarder.getFavoriteItem().getCategory() ); + em.getTransaction().commit(); + em.close(); + + cleanup(); + } + + + @Test + public void testTopLevelManyToOneManagedNestedIsDetached() { + Item item1 = new Item(); + item1.setName( "item1 name" ); + Category category = new Category(); + category.setName( "category" ); + item1.setCategory( category ); + category.setExampleItem( item1 ); + + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.persist( item1 ); + em.getTransaction().commit(); + em.close(); + + // get another representation of item1 + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Item item1_1 = em.find( Item.class, item1.getId() ); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Item item1Merged = em.merge( item1 ); + + item1Merged.setCategory( category ); + category.setExampleItem( item1_1 ); + + // now item1Merged is managed and it has a nested detached item + em.merge( item1Merged ); + assertEquals( category.getName(), item1Merged.getCategory().getName() ); + assertSame( item1Merged, item1Merged.getCategory().getExampleItem() ); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + item1 = em.find( Item.class, item1.getId() ); + assertEquals( category.getName(), item1.getCategory().getName() ); + assertSame( item1, item1.getCategory().getExampleItem() ); + em.getTransaction().commit(); + em.close(); + + cleanup(); + } + + @SuppressWarnings( {"unchecked"}) + private void cleanup() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + + for ( Hoarder hoarder : (List) em.createQuery( "from Hoarder" ).getResultList() ) { + hoarder.getItems().clear(); + em.remove( hoarder ); + } + + for ( Category category : (List) em.createQuery( "from Category" ).getResultList() ) { + if ( category.getExampleItem() != null ) { + category.setExampleItem( null ); + em.remove( category ); + } + } + + for ( Item item : (List) em.createQuery( "from Item" ).getResultList() ) { + item.setCategory( null ); + em.remove( item ); + } + + em.createQuery( "delete from Item" ).executeUpdate(); + + em.getTransaction().commit(); + em.close(); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Category.class, + Hoarder.class, + Item.class + }; + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/MergeMultipleEntityRepresentationsNotAllowedTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/MergeMultipleEntityRepresentationsNotAllowedTest.java new file mode 100644 index 0000000000..cc6560dbb4 --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/emops/MergeMultipleEntityRepresentationsNotAllowedTest.java @@ -0,0 +1,175 @@ +/* + * 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.jpa.test.emops; + +import java.util.List; + +import javax.persistence.EntityManager; + +import org.junit.Test; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; + +import static junit.framework.TestCase.fail; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests merging multiple detached representations of the same entity using + * a the default MergeEventListener (that does not allow this). + * + * @author Gail Badner + */ +@TestForIssue( jiraKey = "HHH-9106") +public class MergeMultipleEntityRepresentationsNotAllowedTest extends BaseEntityManagerFunctionalTestCase { + + @Test + public void testCascadeFromDetachedToNonDirtyRepresentations() { + Item item1 = new Item(); + item1.setName( "item1" ); + + Hoarder hoarder = new Hoarder(); + hoarder.setName( "joe" ); + + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.persist( item1 ); + em.persist( hoarder ); + em.getTransaction().commit(); + em.close(); + + // Get another representation of the same Item from a different session. + + em = getOrCreateEntityManager(); + Item item1_1 = em.find( Item.class, item1.getId() ); + em.close(); + + // item1_1 and item1_2 are unmodified representations of the same persistent entity. + assertFalse( item1 == item1_1 ); + assertTrue( item1.equals( item1_1 ) ); + + // Update hoarder (detached) to references both representations. + hoarder.getItems().add( item1 ); + hoarder.setFavoriteItem( item1_1 ); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + try { + em.merge( hoarder ); + fail( "should have failed due IllegalStateException"); + } + catch (IllegalStateException ex) { + //expected + } + finally { + em.getTransaction().rollback(); + em.close(); + } + + cleanup(); + } + + @Test + public void testTopLevelManyToOneManagedNestedIsDetached() { + Item item1 = new Item(); + item1.setName( "item1 name" ); + Category category = new Category(); + category.setName( "category" ); + item1.setCategory( category ); + category.setExampleItem( item1 ); + + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.persist( item1 ); + em.getTransaction().commit(); + em.close(); + + // get another representation of item1 + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Item item1_1 = em.find( Item.class, item1.getId() ); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Item item1Merged = em.merge( item1 ); + + item1Merged.setCategory( category ); + category.setExampleItem( item1_1 ); + + // now item1Merged is managed and it has a nested detached item + try { + em.merge( item1Merged ); + fail( "should have failed due IllegalStateException"); + } + catch (IllegalStateException ex) { + //expected + } + finally { + em.getTransaction().rollback(); + em.close(); + } + + cleanup(); + } + + @SuppressWarnings( {"unchecked"}) + private void cleanup() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + + for ( Hoarder hoarder : (List) em.createQuery( "from Hoarder" ).getResultList() ) { + hoarder.getItems().clear(); + em.remove( hoarder ); + } + + for ( Category category : (List) em.createQuery( "from Category" ).getResultList() ) { + if ( category.getExampleItem() != null ) { + category.setExampleItem( null ); + em.remove( category ); + } + } + + for ( Item item : (List) em.createQuery( "from Item" ).getResultList() ) { + item.setCategory( null ); + em.remove( item ); + } + + em.createQuery( "delete from Item" ).executeUpdate(); + + em.getTransaction().commit(); + em.close(); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Category.class, + Hoarder.class, + Item.class + }; + } +}