| /* |
| * 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.EntityExistsException; |
| 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; |
| |
| /** |
| * A new entity instance becomes both managed and persistent by invoking the persist method on it or by cascading the persist |
| * operation. The semantics of the persist operation, applied to an entity X are as follows: |
| * <ul> |
| * <li>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.</li> |
| * <li>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.</li> |
| * <li>If X is a removed entity, it becomes managed.</li> |
| * <li>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.</li> |
| * <li>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.</li> |
| * </ul> |
| */ |
| public class TestCascadePersist extends JPA1Base { |
| |
| @Test |
| public void testSimpleCascadeNew() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| CascadingNode parent = new CascadingNode(1, null); |
| CascadingNode child = new CascadingNode(2, parent); |
| child.setParent(null); // to avoid circular cascade |
| // cascade from parent to child |
| 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"); |
| env.commitTransactionAndClear(em); |
| // verify existence after commit |
| verifyExistenceOnDatabase(parent.getId()); |
| verifyExistenceOnDatabase(child.getId()); |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| @Test |
| public void testSimpleCascadeManaged() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| CascadingNode parent = new CascadingNode(11, null); |
| env.beginTransaction(em); |
| em.persist(parent); |
| env.commitTransactionAndClear(em); |
| // cascade from parent to child |
| env.beginTransaction(em); |
| parent = em.find(CascadingNode.class, parent.getId()); // parent is now managed |
| CascadingNode child = new CascadingNode(12, parent); |
| child.setParent(null); // to avoid circular cascade |
| 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"); |
| env.commitTransactionAndClear(em); |
| // verify existence after commit |
| verifyExistenceOnDatabase(parent.getId()); |
| verifyExistenceOnDatabase(child.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 |
| CascadingNode parent = new CascadingNode(21, null); |
| env.beginTransaction(em); |
| em.persist(parent); |
| env.commitTransactionAndClear(em); |
| // parent is now detached |
| |
| CascadingNode child = new CascadingNode(22, parent); |
| child.setParent(null); // to avoid circular cascade |
| // cascade from parent to child |
| env.beginTransaction(em); |
| boolean persistFailed = false; |
| boolean immediateException = false; |
| try { |
| 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"); |
| } catch (EntityExistsException e) { |
| persistFailed = true; |
| immediateException = true; |
| } |
| if (!immediateException) { |
| try { |
| env.commitTransactionAndClear(em); |
| } catch (RuntimeException e) { |
| if (!checkForPersistenceException(e)) { |
| throw e; |
| } |
| persistFailed = true; |
| } |
| } else { |
| env.rollbackTransactionAndClear(em); |
| } |
| verify(persistFailed, "persist succeeded on a detached instance"); |
| // can't verify anything on the database as state is undefined after rollback |
| |
| |
| // 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); |
| parent = new CascadingNode(existing.getId(), null); |
| child = new CascadingNode(24, parent); |
| child.setParent(null); // to avoid circular cascade |
| env.beginTransaction(em); |
| em.persist(existing); // known object in state new |
| persistFailed = false; |
| immediateException = false; |
| try { |
| // cascade from parent to child |
| 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"); |
| } catch (EntityExistsException e) { |
| persistFailed = true; |
| immediateException = true; |
| } |
| verify(em.contains(existing), "Previously managed entity not contained in persistence context any more"); |
| if (!immediateException) { |
| try { |
| env.commitTransactionAndClear(em); |
| } catch (RuntimeException e) { |
| if (!checkForPersistenceException(e)) { |
| throw e; |
| } |
| persistFailed = true; |
| } |
| } else { |
| env.rollbackTransactionAndClear(em); |
| } |
| verify(persistFailed, "persist succeeded on a detached instance"); |
| // can't verify anything on the database as state is undefined after rollback |
| |
| |
| // case 2b: state of known object: managed |
| existing = new CascadingNode(25, null); |
| parent = new CascadingNode(existing.getId(), null); |
| child = new CascadingNode(26, parent); |
| child.setParent(null); // to avoid circular cascade |
| env.beginTransaction(em); |
| em.persist(existing); |
| env.commitTransactionAndClear(em); |
| |
| env.beginTransaction(em); |
| existing = em.find(CascadingNode.class, existing.getId()); // known object in state managed |
| persistFailed = false; |
| immediateException = false; |
| try { |
| // cascade from parent to child |
| 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"); |
| } catch (EntityExistsException e) { |
| persistFailed = true; |
| immediateException = true; |
| } |
| verify(em.contains(existing), "Previously managed entity not contained in persistence context any more"); |
| if (!immediateException) { |
| try { |
| env.commitTransactionAndClear(em); |
| } catch (RuntimeException e) { |
| if (!checkForPersistenceException(e)) { |
| throw e; |
| } |
| persistFailed = true; |
| } |
| } else { |
| env.rollbackTransactionAndClear(em); |
| } |
| verify(persistFailed, "persist did succeed on a detached instance"); |
| // can't verify anything on the database as state is undefined after rollback |
| |
| // case 2c: state of known object: deleted |
| existing = new CascadingNode(27, null); |
| parent = new CascadingNode(existing.getId(), null); |
| child = new CascadingNode(28, parent); |
| child.setParent(null); // to avoid circular cascade |
| env.beginTransaction(em); |
| em.persist(existing); |
| env.commitTransactionAndClear(em); |
| |
| env.beginTransaction(em); |
| existing = em.find(CascadingNode.class, existing.getId()); |
| em.remove(existing); // known object in state deleted |
| persistFailed = false; |
| immediateException = false; |
| try { |
| // cascade from parent to child |
| 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"); |
| } catch (EntityExistsException e) { |
| persistFailed = true; |
| immediateException = true; |
| } |
| if (!immediateException) { |
| try { |
| env.commitTransactionAndClear(em); |
| } catch (RuntimeException e) { |
| if (!checkForPersistenceException(e)) { |
| throw e; |
| } |
| persistFailed = true; |
| } |
| } else { |
| env.rollbackTransactionAndClear(em); |
| } |
| verify(persistFailed, "persist did succeed on a detached instance"); |
| // can't verify anything on the database as state is undefined after rollback |
| } finally { |
| closeEntityManager(em); |
| } |
| } |
| |
| @Test |
| public void testCircularCascade() throws SQLException { |
| final JPAEnvironment env = getEnvironment(); |
| final EntityManager em = env.getEntityManager(); |
| try { |
| CascadingNode parent = new CascadingNode(31, null); |
| CascadingNode child = new CascadingNode(32, parent); |
| // cascade from parent to child |
| env.beginTransaction(em); |
| em.persist(parent); |
| env.commitTransactionAndClear(em); |
| // verify existence after commit |
| verifyExistenceOnDatabase(parent.getId()); |
| verifyExistenceOnDatabase(child.getId()); |
| emptyDatabaseTable(new int[] { 32, 31 }); |
| // cascade from child to parent |
| env.beginTransaction(em); |
| em.persist(child); |
| env.commitTransactionAndClear(em); |
| // verify existence after commit |
| verifyExistenceOnDatabase(parent.getId()); |
| verifyExistenceOnDatabase(child.getId()); |
| } 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(); |
| } |
| } |
| |
| private void emptyDatabaseTable(int[] keys) throws SQLException { |
| Connection conn = getEnvironment().getDataSource().getConnection(); |
| try { |
| PreparedStatement stmt = conn.prepareStatement("delete from TMP_CASC_NODE where ID = ?"); |
| try { |
| for (int i = 0; i < keys.length; i++) { |
| stmt.setInt(1, keys[i]); |
| stmt.executeUpdate(); |
| } |
| if (conn.getAutoCommit() != true) { |
| conn.commit(); |
| } |
| } finally { |
| stmt.close(); |
| } |
| } finally { |
| conn.close(); |
| } |
| } |
| } |