| /* |
| * Copyright (c) 2005, 2021 Oracle and/or its affiliates. All rights reserved. |
| * 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 v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // 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 jakarta.persistence.EntityManager; |
| |
| import org.eclipse.persistence.testing.framework.wdf.JPAEnvironment; |
| import org.eclipse.persistence.testing.models.wdf.jpa1.node.CascadingNode; |
| import org.eclipse.persistence.testing.tests.wdf.jpa1.JPA1Base; |
| import org.junit.Test; |
| |
| /* |
| * The semantics of the flush operation, applied to an entity X are as follows: |
| * <p> |
| * If X is a managed entity, it is synchronized to the database. For all entities Y referenced by a relationship from X, if the |
| * relationship to Y has been annotated with the cascade element value cascade=PERSIST or cascade=ALL, the persist operation is |
| * applied to Y. |
| * <p> |
| * If X is a removed entity, it is removed from the database. No cascade options are relevant. </ul> |
| */ |
| public class TestCascadeFlush extends JPA1Base { |
| |
| /* |
| * The semantics of the persist operation, applied to an entity X are as follows: If X is a new entity, it becomes managed. |
| * The entity X will be entered into the database at or before transaction commit or as a result of the flush operation. |
| */ |
| @Test |
| public void testSimpleCascadeToNew() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| CascadingNode parent = new CascadingNode(1, null); |
| env.beginTransaction(em); |
| em.persist(parent); |
| CascadingNode child = new CascadingNode(2, parent); |
| child.setParent(null); // to avoid circular cascade |
| verify(!em.contains(child), "Child is already managed"); |
| em.flush(); |
| env.commitTransactionAndClear(em); |
| // verify existence after commit |
| verifyExistenceOnDatabase(parent.getId()); |
| verifyExistenceOnDatabase(child.getId()); |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| /* |
| * The semantics of the persist operation, applied to an entity X are as follows: If X is a preexisting managed entity, it |
| * is ignored by the persist operation. However, the persist operation is cascaded to entities referenced by X, if the |
| * relationships from X to these other entities is annotated with the cascade=PERSIST or cascade=ALL annotation element |
| * value or specified with the equivalent XML descriptor element. |
| */ |
| @Test |
| public void testSimpleCascadeToManaged() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| CascadingNode parent = new CascadingNode(11, null); |
| CascadingNode child = new CascadingNode(12, parent); |
| child.setParent(null); // to avoid circular cascade |
| env.beginTransaction(em); |
| em.persist(parent); |
| verify(em.contains(parent), "Parent not contained in persistence context after persist"); |
| verify(em.contains(child), "Child not contained in persistence context after persist"); |
| CascadingNode grandchild = new CascadingNode(13, child); |
| grandchild.setParent(null); // to avoid circular cascade |
| verify(!em.contains(grandchild), "Grandchild is already managed"); |
| em.flush(); |
| env.commitTransactionAndClear(em); |
| // verify existence after commit |
| verifyExistenceOnDatabase(parent.getId()); |
| verifyExistenceOnDatabase(child.getId()); |
| verifyExistenceOnDatabase(grandchild.getId()); |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| /* |
| * The semantics of the persist operation, applied to an entity X are as follows: If X is a detached object, |
| * the EntityExistsException may be thrown when the persist operation is invoked, |
| * or the EntityExistsException or another PersistenceException may be thrown at flush or commit time. |
| */ |
| @Test |
| public void testSimpleCascadeToDetached1() 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 |
| CascadingNode child = new CascadingNode(21, null); |
| env.beginTransaction(em); |
| em.persist(child); |
| env.commitTransactionAndClear(em); |
| env.beginTransaction(em); |
| CascadingNode parent = new CascadingNode(22, null); |
| em.persist(parent); |
| parent.addChild(child); // child is detached |
| verify(em.contains(parent), "Parent not contained in persistence context after persist"); |
| verify(!em.contains(child), "Child is already managed"); |
| boolean exceptionThrown = false; |
| try { |
| env.commitTransactionAndClear(em); |
| } catch (RuntimeException e) { |
| if (!checkForPersistenceException(e)) { |
| throw e; |
| } |
| exceptionThrown = true; |
| } |
| verify(exceptionThrown, "commit did not fail as expected"); |
| // can't verify anything on the database as state is undefined after rollback |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| @Test |
| public void testSimpleCascadeToDetached2a() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| // case 2: detached because an object with same pk but different object identity is known by persistence context |
| // case 2a: state of known object: new |
| CascadingNode existing = new CascadingNode(23, null); |
| CascadingNode parent = new CascadingNode(24, null); |
| env.beginTransaction(em); |
| em.persist(existing); // known object in state new |
| em.persist(parent); |
| CascadingNode child = new CascadingNode(existing.getId(), parent); |
| child.setParent(null); // to avoid circular cascade |
| verify(em.contains(existing), "Existing not contained in persistence context after persist"); |
| verify(em.contains(parent), "Parent not contained in persistence context after persist"); |
| verify(!em.contains(child), "Child is already managed"); |
| boolean exceptionThrown = false; |
| try { |
| env.commitTransactionAndClear(em); |
| } catch (RuntimeException e) { |
| if (!checkForPersistenceException(e)) { |
| throw e; |
| } |
| exceptionThrown = true; |
| } |
| verify(exceptionThrown, "commit did not fail as expected"); |
| // can't verify anything on the database as state is undefined after rollback |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| @Test |
| public void testSimpleCascadeToDetached2b() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| // case 2b: state of known object: managed |
| CascadingNode existing = new CascadingNode(25, null); |
| CascadingNode parent = new CascadingNode(26, null); |
| env.beginTransaction(em); |
| em.persist(existing); |
| env.commitTransactionAndClear(em); |
| env.beginTransaction(em); |
| existing = em.find(CascadingNode.class, Integer.valueOf(existing.getId())); // known object in state managed |
| em.persist(parent); |
| CascadingNode child = new CascadingNode(existing.getId(), parent); |
| child.setParent(null); // to avoid circular cascade |
| verify(em.contains(existing), "Existing not contained in persistence context after find"); |
| verify(em.contains(parent), "Parent not contained in persistence context after persist"); |
| verify(!em.contains(child), "Child is already managed"); |
| boolean exceptionThrown = false; |
| try { |
| env.commitTransactionAndClear(em); |
| } catch (RuntimeException e) { |
| if (!checkForPersistenceException(e)) { |
| throw e; |
| } |
| exceptionThrown = true; |
| } |
| verify(exceptionThrown, "commit did not fail as expected"); |
| // can't verify anything on the database as state is undefined after rollback |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| @Test |
| public void testSimpleCascadeToDetached2c() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| // case 2c: state of known object: deleted |
| CascadingNode existing = new CascadingNode(27, null); |
| CascadingNode parent = new CascadingNode(28, null); |
| env.beginTransaction(em); |
| em.persist(existing); |
| env.commitTransactionAndClear(em); |
| env.beginTransaction(em); |
| existing = em.find(CascadingNode.class, Integer.valueOf(existing.getId())); |
| em.remove(existing); // known object in state deleted |
| em.persist(parent); |
| CascadingNode child = new CascadingNode(existing.getId(), parent); |
| child.setParent(null); // to avoid circular cascade |
| verify(!em.contains(existing), "Existing contained in persistence context after remove"); |
| verify(em.contains(parent), "Parent not contained in persistence context after persist"); |
| verify(!em.contains(child), "Child is already managed"); |
| boolean exceptionThrown = false; |
| try { |
| env.commitTransactionAndClear(em); |
| } catch (RuntimeException e) { |
| if (!checkForPersistenceException(e)) { |
| throw e; |
| } |
| exceptionThrown = true; |
| } |
| verify(exceptionThrown, "commit did not fail as expected"); |
| // can't verify anything on the database as state is undefined after rollback |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| /* |
| * The semantics of the persist operation, applied to an entity X are as follows: If X is a removed entity, it becomes |
| * managed. |
| */ |
| @Test |
| public void testSimpleCascadeToRemoved() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| CascadingNode child = new CascadingNode(31, null); |
| env.beginTransaction(em); |
| em.persist(child); |
| env.commitTransactionAndClear(em); |
| env.beginTransaction(em); |
| CascadingNode parent = new CascadingNode(32, null); |
| em.persist(parent); |
| child = em.find(CascadingNode.class, Integer.valueOf(child.getId())); |
| em.remove(child); |
| parent.addChild(child); |
| verify(em.contains(parent), "Parent not contained in persistence context after persist"); |
| verify(!em.contains(child), "Child is contained in persistence context after remove"); |
| em.flush(); |
| verify(em.contains(child), "Child is not managed after cascading persist operation"); |
| env.commitTransactionAndClear(em); |
| verifyExistenceOnDatabase(parent.getId()); |
| verifyExistenceOnDatabase(child.getId()); |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| @Test |
| public void testCircularCascade() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| // cascade from parent to child |
| CascadingNode parent = new CascadingNode(41, null); |
| env.beginTransaction(em); |
| em.persist(parent); |
| CascadingNode child = new CascadingNode(42, parent); |
| env.commitTransactionAndClear(em); |
| verifyExistenceOnDatabase(parent.getId()); |
| verifyExistenceOnDatabase(child.getId()); |
| // cascade from child to parent |
| child = new CascadingNode(43, null); |
| env.beginTransaction(em); |
| em.persist(child); |
| parent = new CascadingNode(44, null); |
| child.setParent(parent); |
| parent.addChild(child); |
| env.commitTransactionAndClear(em); |
| verifyExistenceOnDatabase(parent.getId()); |
| verifyExistenceOnDatabase(child.getId()); |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| @Test |
| public void testFlushLazyCollectionWithCascadePersist() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| // cascade from parent to child |
| final int rootId = 50; |
| env.beginTransaction(em); |
| CascadingNode root = new CascadingNode(rootId, null); |
| root.addChild(new CascadingNode(51, root)); |
| em.persist(root); |
| env.commitTransaction(em); |
| em.clear(); |
| env.beginTransaction(em); |
| em.find(CascadingNode.class, Integer.valueOf(rootId)); |
| env.commitTransaction(em); |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| private void verifyExistenceOnDatabase(int nodeId) throws SQLException { |
| verify(checkForExistenceOnDatabase(nodeId), "no node with id " + nodeId + " found using JDBC."); |
| } |
| |
| private boolean checkForExistenceOnDatabase(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(); |
| } |
| } |
| |
| } |