blob: 7d6cd499ac124af28aa6f3eab2687181d59aabdf [file] [log] [blame]
/*
* 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();
}
}
}