/******************************************************************************* * Copyright (c) 2005, 2015 SAP. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * SAP - initial API and implementation ******************************************************************************/ package org.eclipse.persistence.testing.tests.wdf.jpa1.entitymanager; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.persistence.EntityManager; import org.eclipse.persistence.testing.framework.wdf.JPAEnvironment; import org.eclipse.persistence.testing.framework.wdf.ToBeInvestigated; import org.eclipse.persistence.testing.models.wdf.jpa1.node.CascadingNode; import org.eclipse.persistence.testing.models.wdf.jpa1.node.CascadingNodeDescription; import org.eclipse.persistence.testing.tests.wdf.jpa1.JPA1Base; import org.junit.Test; /* * A managed entity instance becomes removed by invoking the remove method on it or by cascading the remove operation. The * semantics of the remove operation, applied to an entity X are as follows: * <ul> * <li>If X is a new entity, it is ignored by the remove operation. However, the remove operation is cascaded to entities * referenced by X, if the relationships from X to these other entities is annotated with the cascade=REMOVE or cascade=ALL * annotation element value.</li> * <li>If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities * referenced by X, if the relationships from X to these other entities is annotated with the cascade=REMOVE or cascade=ALL * annotation element value.</li> * <li>If X is a detached entity, an IllegalArgumentException will be thrown by the remove operation (or the transaction commit * will fail).</li> * <li>If X is a removed entity, it is ignored by the remove operation.</li> * <li>A removed entity X will be removed from the database at or before transaction commit or as a result of the flush * operation.</li> * </ul> * After an entity has been removed, its state (except for generated state) will be that of the entity at the point at which the * remove operation was called. */ public class TestCascadeRemove extends JPA1Base { @Test public void testSimpleCascadeNew() throws SQLException { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); env.evictAll(em); try { // one-to-many relationship CascadingNode child = new CascadingNode(1, null); env.beginTransaction(em); em.persist(child); env.commitTransactionAndClear(em); env.beginTransaction(em); child = em.find(CascadingNode.class, new Integer(child.getId())); CascadingNode parent = new CascadingNode(2, null); parent.addChild(child); verify(!em.contains(parent), "Parent not a new entity"); verify(em.contains(child), "Child not managed"); em.remove(parent); env.commitTransactionAndClear(em); verifyAbsenceFromNodeTable(parent.getId()); verifyAbsenceFromNodeTable(child.getId()); // one-to-one relationship CascadingNodeDescription description = new CascadingNodeDescription(3, null, "a simple node"); env.beginTransaction(em); em.persist(description); env.commitTransactionAndClear(em); env.beginTransaction(em); description = em.find(CascadingNodeDescription.class, new Integer(description.getId())); parent = new CascadingNode(4, null); parent.setDescription(description); description.setNode(parent); verify(!em.contains(parent), "Parent not a new entity"); verify(em.contains(description), "Description not managed"); em.remove(parent); env.commitTransactionAndClear(em); verifyAbsenceFromNodeTable(parent.getId()); verifyAbsenceFromDescriptionTable(description.getId()); } finally { closeEntityManager(em); } } @Test public void testSimpleCascadeManaged() throws SQLException { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { // Case 1: status FOR_INSERT // one-to-many relationship CascadingNode child = new CascadingNode(101, null); env.beginTransaction(em); em.persist(child); env.commitTransactionAndClear(em); env.beginTransaction(em); CascadingNode parent = new CascadingNode(102, null); em.persist(parent); child = em.find(CascadingNode.class, new Integer(child.getId())); parent.addChild(child); verify(em.contains(parent), "Parent not managed"); verify(em.contains(child), "Child not managed"); em.remove(parent); env.commitTransactionAndClear(em); verifyAbsenceFromNodeTable(parent.getId()); verifyAbsenceFromNodeTable(child.getId()); // one-to-one relationship CascadingNodeDescription description = new CascadingNodeDescription(103, null, "a simple node"); env.beginTransaction(em); em.persist(description); env.commitTransactionAndClear(em); env.beginTransaction(em); parent = new CascadingNode(104, null); em.persist(parent); description = em.find(CascadingNodeDescription.class, new Integer(description.getId())); parent.setDescription(description); description.setNode(parent); verify(em.contains(parent), "Parent not managed"); verify(em.contains(description), "Description not managed"); em.remove(parent); env.commitTransactionAndClear(em); verifyAbsenceFromNodeTable(parent.getId()); verifyAbsenceFromDescriptionTable(description.getId()); // Case 2: status FOR_UPDATE // one-to-many relationship parent = new CascadingNode(105, null); child = new CascadingNode(106, null); env.beginTransaction(em); em.persist(parent); em.persist(child); env.commitTransactionAndClear(em); env.beginTransaction(em); parent = em.find(CascadingNode.class, new Integer(parent.getId())); child = em.find(CascadingNode.class, new Integer(child.getId())); parent.addChild(child); verify(em.contains(parent), "Parent not managed"); verify(em.contains(child), "Child not managed"); em.remove(parent); env.commitTransactionAndClear(em); verifyAbsenceFromNodeTable(parent.getId()); verifyAbsenceFromNodeTable(child.getId()); // one-to-one relationship parent = new CascadingNode(107, null); description = new CascadingNodeDescription(108, parent, "a simple node"); parent.setDescription(description); env.beginTransaction(em); em.persist(parent); env.commitTransactionAndClear(em); env.beginTransaction(em); parent = em.find(CascadingNode.class, new Integer(parent.getId())); description = parent.getDescription(); verify(em.contains(parent), "Parent not managed"); verify(em.contains(description), "Description not managed"); em.remove(parent); env.commitTransactionAndClear(em); verifyAbsenceFromNodeTable(parent.getId()); verifyAbsenceFromDescriptionTable(description.getId()); } finally { closeEntityManager(em); } } @Test public void testSimpleCascadeDetached() throws SQLException { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { // Case 1: detached because entity exists on db but is not known by persistence context // one-to-many relationship CascadingNode parent = new CascadingNode(201, null); CascadingNode child = new CascadingNode(202, null); env.beginTransaction(em); em.persist(parent); em.persist(child); env.commitTransactionAndClear(em); env.beginTransaction(em); child = em.find(CascadingNode.class, new Integer(child.getId())); parent.addChild(child); verify(!em.contains(parent), "Parent not detached"); verify(em.contains(child), "Child not managed"); boolean removeFailed = false; boolean immediateException = false; try { em.remove(parent); verify(!em.contains(parent), "Parent is managed"); verify(!em.contains(child), "Child is still managed"); } catch (IllegalArgumentException e) { removeFailed = true; immediateException = true; } if (!immediateException) { try { env.commitTransactionAndClear(em); } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } removeFailed = true; } } else { env.rollbackTransactionAndClear(em); } verify(removeFailed, "remove succeeded on a detached instance"); verifyExistenceInNodeTable(parent.getId()); verifyExistenceInNodeTable(child.getId()); // one-to-one relationship parent = new CascadingNode(203, null); CascadingNodeDescription description = new CascadingNodeDescription(204, null, "a simple node"); env.beginTransaction(em); em.persist(parent); em.persist(description); env.commitTransactionAndClear(em); env.beginTransaction(em); description = em.find(CascadingNodeDescription.class, new Integer(description.getId())); parent.setDescription(description); verify(!em.contains(parent), "Parent not detached"); verify(em.contains(description), "Description not managed"); removeFailed = false; immediateException = false; try { em.remove(parent); verify(!em.contains(parent), "Parent is managed"); verify(!em.contains(description), "Description is still managed"); } catch (IllegalArgumentException e) { removeFailed = true; immediateException = true; } if (!immediateException) { try { env.commitTransactionAndClear(em); } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } removeFailed = true; } } else { env.rollbackTransactionAndClear(em); } verify(removeFailed, "remove succeeded on a detached instance"); verifyExistenceInNodeTable(parent.getId()); verifyExistenceInDescriptionTable(description.getId()); // Case 2: detached because an object with same pk but different object identity is known by persistence context // Case 2a: state of known object: FOR_INSERT // one-to-many relationship CascadingNode existing = new CascadingNode(205, null); parent = new CascadingNode(existing.getId(), null); child = new CascadingNode(206, null); env.beginTransaction(em); em.persist(child); env.commitTransactionAndClear(em); env.beginTransaction(em); em.persist(existing); // status FOR_INSERT child = em.find(CascadingNode.class, new Integer(child.getId())); parent.addChild(child); verify(!em.contains(parent), "Parent not detached"); verify(em.contains(existing), "Existing not managed"); verify(em.contains(child), "Child not managed"); removeFailed = false; immediateException = false; try { em.remove(parent); verify(!em.contains(parent), "Parent is managed"); verify(!em.contains(child), "Child is still managed"); } catch (IllegalArgumentException e) { removeFailed = true; immediateException = true; } verify(em.contains(existing), "Previously managed entity no longer managed"); if (!immediateException) { try { env.commitTransactionAndClear(em); } catch (RuntimeException e) { if (immediateException || !checkForPersistenceException(e)) { throw e; } removeFailed = true; } } else { env.rollbackTransactionAndClear(em); } verify(removeFailed, "remove succeeded on a detached instance"); verifyAbsenceFromNodeTable(existing.getId()); verifyExistenceInNodeTable(child.getId()); // one-to-one relationship existing = new CascadingNode(207, null); parent = new CascadingNode(existing.getId(), null); description = new CascadingNodeDescription(208, null, "some text"); env.beginTransaction(em); em.persist(description); env.commitTransactionAndClear(em); env.beginTransaction(em); em.persist(existing); // status FOR_INSERT description = em.find(CascadingNodeDescription.class, new Integer(description.getId())); parent.setDescription(description); verify(!em.contains(parent), "Parent not detached"); verify(em.contains(existing), "Existing not managed"); verify(em.contains(description), "Description not managed"); removeFailed = false; immediateException = false; try { em.remove(parent); verify(!em.contains(parent), "Parent is managed"); verify(!em.contains(description), "Description is still managed"); } catch (IllegalArgumentException e) { removeFailed = true; immediateException = true; } verify(em.contains(existing), "Previously managed entity no longer managed"); if (!immediateException) { try { env.commitTransactionAndClear(em); } catch (RuntimeException e) { if (immediateException || !checkForPersistenceException(e)) { throw e; } removeFailed = true; } } else { env.rollbackTransactionAndClear(em); } verify(removeFailed, "remove succeeded on a detached instance"); verifyAbsenceFromNodeTable(existing.getId()); verifyExistenceInNodeTable(child.getId()); // Case 2b: state of known object: FOR_UPADTE // one-to-many relationship existing = new CascadingNode(209, null); parent = new CascadingNode(existing.getId(), null); child = new CascadingNode(210, null); env.beginTransaction(em); em.persist(existing); em.persist(child); env.commitTransactionAndClear(em); env.beginTransaction(em); existing = em.find(CascadingNode.class, new Integer(existing.getId())); // state FOR_UPADTE child = em.find(CascadingNode.class, new Integer(child.getId())); parent.addChild(child); verify(!em.contains(parent), "Parent not detached"); verify(em.contains(existing), "Existing not managed"); verify(em.contains(child), "Child not managed"); removeFailed = false; immediateException = false; try { em.remove(parent); verify(!em.contains(parent), "Parent is managed"); verify(!em.contains(child), "Child is still managed"); } catch (IllegalArgumentException e) { removeFailed = true; immediateException = true; } verify(em.contains(existing), "Previously managed entity no longer managed"); if (!immediateException) { try { env.commitTransactionAndClear(em); } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } removeFailed = true; } } else { env.rollbackTransactionAndClear(em); } verify(removeFailed, "remove succeeded on a detached instance"); verifyExistenceInNodeTable(existing.getId()); verifyExistenceInNodeTable(child.getId()); // one-to-one relationship existing = new CascadingNode(211, null); parent = new CascadingNode(existing.getId(), null); description = new CascadingNodeDescription(212, null, "some text"); env.beginTransaction(em); em.persist(existing); em.persist(description); env.commitTransactionAndClear(em); env.beginTransaction(em); existing = em.find(CascadingNode.class, new Integer(existing.getId())); // state FOR_UPADTE description = em.find(CascadingNodeDescription.class, new Integer(description.getId())); parent.setDescription(description); verify(!em.contains(parent), "Parent not detached"); verify(em.contains(existing), "Existing not managed"); verify(em.contains(description), "Description not managed"); removeFailed = false; immediateException = false; try { em.remove(parent); verify(!em.contains(parent), "Parent is managed"); verify(!em.contains(description), "Description is still managed"); } catch (IllegalArgumentException e) { removeFailed = true; immediateException = true; } verify(em.contains(existing), "Previously managed entity no longer managed"); if (!immediateException) { try { env.commitTransactionAndClear(em); } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } removeFailed = true; } } else { env.rollbackTransactionAndClear(em); } verify(removeFailed, "remove succeeded on a detached instance"); verifyExistenceInNodeTable(existing.getId()); verifyExistenceInDescriptionTable(description.getId()); // Case 2c: state of known object: FOR_REMOVE // one-to-many relationship existing = new CascadingNode(213, null); parent = new CascadingNode(existing.getId(), null); child = new CascadingNode(214, null); env.beginTransaction(em); em.persist(existing); em.persist(child); env.commitTransactionAndClear(em); env.beginTransaction(em); existing = em.find(CascadingNode.class, new Integer(existing.getId())); em.remove(existing); // state FOR_REMOVE child = em.find(CascadingNode.class, new Integer(child.getId())); parent.addChild(child); verify(!em.contains(parent), "Parent not detached"); verify(!em.contains(existing), "Existing not removed"); verify(em.contains(child), "Child not managed"); removeFailed = false; immediateException = false; try { em.remove(parent); verify(!em.contains(parent), "Parent is managed"); verify(!em.contains(child), "Child is still managed"); } catch (IllegalArgumentException e) { removeFailed = true; immediateException = true; } if (!immediateException) { try { env.commitTransactionAndClear(em); } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } removeFailed = true; } } else { env.rollbackTransactionAndClear(em); } verify(removeFailed, "remove succeeded on a detached instance"); verifyExistenceInNodeTable(existing.getId()); verifyExistenceInNodeTable(child.getId()); // one-to-one relationship existing = new CascadingNode(215, null); parent = new CascadingNode(existing.getId(), null); description = new CascadingNodeDescription(216, null, "some text"); env.beginTransaction(em); em.persist(existing); em.persist(description); env.commitTransactionAndClear(em); env.beginTransaction(em); existing = em.find(CascadingNode.class, new Integer(existing.getId())); em.remove(existing); // state FOR_REMOVE description = em.find(CascadingNodeDescription.class, new Integer(description.getId())); parent.setDescription(description); verify(!em.contains(parent), "Parent not detached"); verify(!em.contains(existing), "Existing not removed"); verify(em.contains(description), "Description not managed"); removeFailed = false; immediateException = false; try { em.remove(parent); verify(!em.contains(parent), "Parent is managed"); verify(!em.contains(description), "Description is still managed"); } catch (IllegalArgumentException e) { removeFailed = true; immediateException = true; } if (!immediateException) { try { env.commitTransactionAndClear(em); } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } removeFailed = true; } } else { env.rollbackTransactionAndClear(em); } verify(removeFailed, "remove succeeded on a detached instance"); verifyExistenceInNodeTable(existing.getId()); verifyExistenceInDescriptionTable(description.getId()); } finally { closeEntityManager(em); } } /* If X is a removed entity, it is ignored by the remove operation. */ @Test public void testSimpleCascadeRemoved() throws SQLException { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { // one-to-many relationship CascadingNode parent = new CascadingNode(301, null); CascadingNode child = new CascadingNode(302, null); env.beginTransaction(em); em.persist(parent); em.persist(child); env.commitTransactionAndClear(em); env.beginTransaction(em); parent = em.find(CascadingNode.class, new Integer(parent.getId())); em.remove(parent); child = em.find(CascadingNode.class, new Integer(child.getId())); parent.addChild(child); verify(!em.contains(parent), "Parent not removed"); verify(em.contains(child), "Child not managed"); em.remove(parent); env.commitTransactionAndClear(em); verifyAbsenceFromNodeTable(parent.getId()); verifyExistenceInNodeTable(child.getId()); // one-to-one relationship parent = new CascadingNode(303, null); CascadingNodeDescription description = new CascadingNodeDescription(304, null, "description"); env.beginTransaction(em); em.persist(parent); em.persist(description); env.commitTransactionAndClear(em); env.beginTransaction(em); parent = em.find(CascadingNode.class, new Integer(parent.getId())); em.remove(parent); description = em.find(CascadingNodeDescription.class, new Integer(description.getId())); parent.setDescription(description); verify(!em.contains(parent), "Parent not removed"); verify(em.contains(description), "Description not managed"); em.remove(parent); env.commitTransactionAndClear(em); verifyAbsenceFromNodeTable(parent.getId()); verifyExistenceInDescriptionTable(description.getId()); } finally { closeEntityManager(em); } } @Test @ToBeInvestigated public void testCircularCascade() throws SQLException { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { CascadingNode node1 = new CascadingNode(401, null); CascadingNode node2 = new CascadingNode(402, node1); node2.addChild(node1); node1.setParent(node2); env.beginTransaction(em); em.persist(node1); env.commitTransactionAndClear(em); verifyExistenceInNodeTable(node1.getId()); verifyExistenceInNodeTable(node2.getId()); env.beginTransaction(em); node1 = em.find(CascadingNode.class, new Integer(node1.getId())); node2 = em.find(CascadingNode.class, new Integer(node2.getId())); em.remove(node1); env.commitTransactionAndClear(em); verifyAbsenceFromNodeTable(node1.getId()); verifyAbsenceFromNodeTable(node2.getId()); } finally { closeEntityManager(em); } } private void verifyExistenceInNodeTable(int nodeId) throws SQLException { verify(checkForExistenceInNodeTable(nodeId), "no node with id " + nodeId + " found using JDBC."); } private void verifyAbsenceFromNodeTable(int nodeId) throws SQLException { verify(!checkForExistenceInNodeTable(nodeId), "node with id " + nodeId + " still found using JDBC."); } private boolean checkForExistenceInNodeTable(int nodeId) throws SQLException { Connection conn = getEnvironment().getDataSource().getConnection(); try { PreparedStatement stmt = conn.prepareStatement("select ID, PARENT from TMP_CASC_NODE where ID = ?"); try { stmt.setInt(1, nodeId); ResultSet rs = stmt.executeQuery(); try { return rs.next(); } finally { rs.close(); } } finally { stmt.close(); } } finally { conn.close(); } } private void verifyExistenceInDescriptionTable(int descId) throws SQLException { verify(checkForExistenceInDescriptionTable(descId), "no description with id " + descId + " found using JDBC."); } private void verifyAbsenceFromDescriptionTable(int descId) throws SQLException { verify(!checkForExistenceInDescriptionTable(descId), "description with id " + descId + " still found using JDBC."); } private boolean checkForExistenceInDescriptionTable(int descId) throws SQLException { Connection conn = getEnvironment().getDataSource().getConnection(); try { PreparedStatement stmt = conn.prepareStatement("select ID, DESC_TEXT from TMP_CASC_NODE_DESC where ID = ?"); try { stmt.setInt(1, descId); ResultSet rs = stmt.executeQuery(); try { return rs.next(); } finally { rs.close(); } } finally { stmt.close(); } } finally { conn.close(); } } }