blob: 46ad5261b9824a6e6b0f1ddf27d4d2dcc466c4b0 [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.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, 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, 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, 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, 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();
}
}
}