blob: ffb61ebaee1fa951848b24dac266f9864a7707e6 [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.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.TransactionRequiredException;
import org.eclipse.persistence.testing.framework.wdf.Bugzilla;
import org.eclipse.persistence.testing.framework.wdf.JPAEnvironment;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Cubicle;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Department;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Employee;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Project;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Review;
import org.eclipse.persistence.testing.models.wdf.jpa1.node.Node;
import org.eclipse.persistence.testing.tests.wdf.jpa1.JPA1Base;
import org.junit.Test;
public class TestFlush extends JPA1Base {
/*
* For any entity Y referenced by a relationship from X, where the relationship to Y has not been annotated with the cascade
* element value cascade=PERSIST or cascade=ALL: <p> If Y is new or removed, an IllegalStateException will be thrown by the
* flush operation (and the transaction rolled back) or the transaction commit will fail.
*/
@Test
public void testRelationshipToNew() {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
// case 1: direct relationship Employee -> Cubicle (new) - 1:1
Department dep = new Department(1, "dep");
Employee emp1 = new Employee(2, "first", "last", dep);
Cubicle cub1 = new Cubicle(3, 3, "color", emp1);
emp1.setCubicle(cub1);
env.beginTransaction(em);
em.persist(dep);
em.persist(emp1);
boolean flushFailed = false;
try {
em.flush();
} catch (IllegalStateException e) {
// $JL-EXC$ this is expected behavior
flushFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
env.rollbackTransactionAndClear(em);
}
try {
if (env.isTransactionActive(em)) {
env.commitTransactionAndClear(em);
}
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushFailed, "flush succeeded although there is a relation to an unmanaged entity");
// case 2: direct relationship Employee -> Project (new) - n:m
dep = new Department(4, "dep");
emp1 = new Employee(5, "first", "last", dep);
Project proj = new Project("project");
Set<Project> emp1Projects = new HashSet<Project>();
emp1Projects.add(proj);
emp1.setProjects(emp1Projects);
Set<Employee> projEmployees = new HashSet<Employee>();
projEmployees.add(emp1);
proj.setEmployees(projEmployees);
env.beginTransaction(em);
em.persist(dep);
em.persist(emp1);
flushFailed = false;
try {
em.flush();
} catch (IllegalStateException e) {
// $JL-EXC$ this is expected behavior
flushFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
env.rollbackTransactionAndClear(em);
}
try {
if (env.isTransactionActive(em)) {
env.commitTransactionAndClear(em);
}
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushFailed, "flush succeeded although there is a relation to an unmanaged entity");
// case 3: indirect relationship Employee -> Project -> Employee (new)
dep = new Department(7, "dep");
emp1 = new Employee(8, "first1", "last1", dep);
Employee emp2 = new Employee(9, "first2", "last2", dep);
proj = new Project("project");
emp1Projects = new HashSet<Project>();
emp1Projects.add(proj);
emp1.setProjects(emp1Projects);
Set<Project> emp2Projects = new HashSet<Project>();
emp2Projects.add(proj);
emp2.setProjects(emp2Projects);
projEmployees = new HashSet<Employee>();
projEmployees.add(emp1);
projEmployees.add(emp2);
proj.setEmployees(projEmployees);
env.beginTransaction(em);
em.persist(dep);
em.persist(emp1);
em.persist(proj);
flushFailed = false;
try {
em.flush();
} catch (IllegalStateException e) {
// $JL-EXC$ this is expected behavior
flushFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
env.rollbackTransactionAndClear(em);
}
try {
if (env.isTransactionActive(em)) {
env.commitTransactionAndClear(em);
}
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushFailed, "flush succeeded although there is a relation to an unmanaged entity");
} finally {
closeEntityManager(em);
}
}
/*
* For any entity Y referenced by a relationship from X, where the relationship to Y has not been annotated with the cascade
* element value cascade=PERSIST or cascade=ALL: <p> If Y is new or removed, an IllegalStateException will be thrown by the
* flush operation (and the transaction rolled back) or the transaction commit will fail.
*/
@SuppressWarnings("unchecked")
@Test
@Bugzilla(bugid=311760)
public void testRelationshipToRemoved() {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
// case 1: direct relationship Employee -> Cubicle (FOR_DELETE) - 1:1
Department dep = new Department(101, "dep");
Employee emp1 = new Employee(102, "first", "last", dep);
Cubicle cub1 = new Cubicle(103, 103, "color", emp1);
emp1.setCubicle(cub1);
env.beginTransaction(em);
em.persist(dep);
em.persist(emp1);
em.persist(cub1);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
emp1 = em.find(Employee.class, emp1.getId());
cub1 = em.find(Cubicle.class, cub1.getId());
cub1.setEmployee(null); // added as suggested by Tom
em.remove(cub1);
boolean flushOrCommmitFailed = false;
try {
em.flush();
} catch (IllegalStateException e) {
// $JL-EXC$ this is expected behavior
flushOrCommmitFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
env.rollbackTransactionAndClear(em);
}
try {
if (env.isTransactionActive(em)) {
env.commitTransactionAndClear(em);
}
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushOrCommmitFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushOrCommmitFailed, "flush succeeded although there is a relation to a removed entity");
em.clear();
// case 2: direct relationship Employee -> Project (FOR_DELETE) - n:m
dep = new Department(104, "dep");
emp1 = new Employee(105, "first", "last", dep);
Project proj = new Project("project");
Set<Project> emp1Projects = new HashSet<Project>();
emp1Projects.add(proj);
emp1.setProjects(emp1Projects);
Set<Employee> projEmployees = new HashSet<Employee>();
projEmployees.add(emp1);
proj.setEmployees(projEmployees);
env.beginTransaction(em);
em.persist(proj);
em.persist(dep);
em.persist(emp1);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
emp1 = em.find(Employee.class, emp1.getId());
proj = em.find(Project.class, proj.getId());
emp1.getProjects().size();
em.remove(proj);
flushOrCommmitFailed = false;
try {
em.flush();
} catch (IllegalStateException e) {
// $JL-EXC$ this is expected behavior
flushOrCommmitFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
env.rollbackTransactionAndClear(em);
}
try {
if (env.isTransactionActive(em)) {
env.commitTransactionAndClear(em);
}
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushOrCommmitFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushOrCommmitFailed, "flush succeeded although there is a relation to an unmanaged entity");
em.clear();
// case 3: indirect relationship Employee -> Project -> Employee (FOR_DELETE)
dep = new Department(107, "dep");
emp1 = new Employee(108, "first1", "last1", dep);
Employee emp2 = new Employee(109, "first2", "last2", dep);
proj = new Project("project");
emp1Projects = new HashSet<Project>();
emp1Projects.add(proj);
emp1.setProjects(emp1Projects);
Set<Project> emp2Projects = new HashSet<Project>();
emp2Projects.add(proj);
emp2.setProjects(emp2Projects);
projEmployees = new HashSet<Employee>();
projEmployees.add(emp1);
projEmployees.add(emp2);
proj.setEmployees(projEmployees);
env.beginTransaction(em);
em.persist(proj);
em.persist(dep);
em.persist(emp1);
em.persist(emp2);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
emp1 = em.find(Employee.class, emp1.getId());
emp2 = em.find(Employee.class, emp2.getId());
proj = em.find(Project.class, proj.getId());
emp1.getProjects().size();
proj.getEmployees().size();
em.remove(emp2);
flushOrCommmitFailed = false;
try {
em.flush();
} catch (IllegalStateException e) {
// $JL-EXC$ this is expected behavior
flushOrCommmitFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
env.rollbackTransactionAndClear(em);
}
try {
if (env.isTransactionActive(em)) {
env.commitTransactionAndClear(em);
}
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushOrCommmitFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushOrCommmitFailed, "flush succeeded although there is a relation to an unmanaged entity");
em.clear();
// case 1b: direct relationship Employee -> Cubicle (DELETE_EXECUTED) - 1:1
dep = new Department(111, "dep");
emp1 = new Employee(112, "first", "last", dep);
cub1 = new Cubicle(113, 112, "color", emp1);
env.beginTransaction(em);
em.persist(dep);
em.persist(emp1);
em.persist(cub1);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
emp1 = em.find(Employee.class, emp1.getId());
cub1 = em.find(Cubicle.class, cub1.getId());
em.remove(cub1);
em.flush();
emp1.setCubicle(cub1);
flushOrCommmitFailed = false;
try {
em.flush();
} catch (IllegalStateException e) {
// $JL-EXC$ this is expected behavior
flushOrCommmitFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
env.rollbackTransactionAndClear(em);
}
try {
if (env.isTransactionActive(em)) {
env.commitTransactionAndClear(em);
}
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushOrCommmitFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushOrCommmitFailed, "flush succeeded although there is a relation to an unmanaged entity");
em.clear();
// case 2b: direct relationship Employee -> Project (DELETE_EXECUTED) - n:m
dep = new Department(114, "dep");
emp1 = new Employee(115, "first", "last", dep);
proj = new Project("project");
env.beginTransaction(em);
em.persist(dep);
em.persist(emp1);
em.persist(proj);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
emp1 = em.find(Employee.class, emp1.getId());
proj = em.find(Project.class, proj.getId());
em.remove(proj);
em.flush();
emp1Projects = new HashSet<Project>();
emp1Projects.add(proj);
emp1.setProjects(emp1Projects);
projEmployees = new HashSet<Employee>();
projEmployees.add(emp1);
proj.setEmployees(projEmployees);
flushOrCommmitFailed = false;
try {
em.flush();
} catch (IllegalStateException e) {
// $JL-EXC$ this is expected behavior
flushOrCommmitFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
env.rollbackTransactionAndClear(em);
}
try {
if (env.isTransactionActive(em)) {
env.commitTransactionAndClear(em);
}
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushOrCommmitFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushOrCommmitFailed, "flush succeeded although there is a relation to an unmanaged entity");
em.clear();
// case 3b: indirect relationship Employee -> Project -> Employee (DELETE_EXECUTED)
dep = new Department(117, "dep");
emp1 = new Employee(118, "first1", "last1", dep);
emp2 = new Employee(119, "first2", "last2", dep);
proj = new Project("project");
emp1Projects = new HashSet<Project>();
emp1Projects.add(proj);
emp1.setProjects(emp1Projects);
projEmployees = new HashSet<Employee>();
projEmployees.add(emp1);
proj.setEmployees(projEmployees);
env.beginTransaction(em);
em.persist(proj);
em.persist(dep);
em.persist(emp1);
em.persist(emp2);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
emp1 = em.find(Employee.class, emp1.getId());
emp2 = em.find(Employee.class, emp2.getId());
proj = em.find(Project.class, proj.getId());
emp1.getProjects().size();
projEmployees = proj.getEmployees();
projEmployees.size();
em.remove(emp2);
em.flush();
emp2Projects = new HashSet<Project>();
emp2Projects.add(proj);
emp2.setProjects(emp2Projects);
projEmployees.add(emp2);
flushOrCommmitFailed = false;
try {
em.flush();
} catch (IllegalStateException e) {
// $JL-EXC$ this is expected behavior
flushOrCommmitFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
env.rollbackTransactionAndClear(em);
}
try {
if (env.isTransactionActive(em)) {
env.commitTransactionAndClear(em);
}
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushOrCommmitFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushOrCommmitFailed, "flush succeeded although there is a relation to an unmanaged entity");
em.clear();
} finally {
closeEntityManager(em);
}
}
/**
* This test checks a special case that can occur with lazily loaded relationships:
* <ul>
* <li>Read Project proj1 and remove it.</li>
* <li>Read Employee emp1 with relationship to proj1 (lazy loading).</li>
* <li>Assign the set of emp1's projects to a new employee emp2 (forces implicit loading on flush).</li>
* <li>Flush -{@literal >} IllegalStateException expected because of relation emp2 -{@literal >} proj1 (removed).</li>
* </ul>
*/
@Test
@Bugzilla(bugid=311760)
public void testRelationshipToRemovedLazy() {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
// case 1: explicit flush
Department dep = new Department(201, "dep");
Employee emp1 = new Employee(202, "first", "last", dep);
Project proj = new Project("project");
Set<Project> emp1Projects = new HashSet<Project>();
emp1Projects.add(proj);
emp1.setProjects(emp1Projects);
Set<Employee> projEmployees = new HashSet<Employee>();
projEmployees.add(emp1);
proj.setEmployees(projEmployees);
env.beginTransaction(em);
em.persist(proj);
em.persist(dep);
em.persist(emp1);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
dep = em.find(Department.class, dep.getId());
proj = em.find(Project.class, proj.getId());
em.remove(proj);
emp1 = em.find(Employee.class, emp1.getId());
// copy all projects from emp1 to emp2 with out actually touching them
Employee emp2 = new Employee(203, "aaa", "bbb", dep);
proj.addEmployee(emp2); // added as suggested by Tom
emp2.setProjects(emp1.getProjects());
em.persist(emp2);
boolean flushFailed = false;
try {
em.flush();
} catch (IllegalStateException e) {
// $JL-EXC$ this is expected behavior
flushFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
env.rollbackTransactionAndClear(em);
}
try {
if (env.isTransactionActive(em)) {
env.commitTransactionAndClear(em);
}
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushFailed, "flush succeeded although there is a relation to a removed entity");
em.clear();
// case 2: implicit flush during commit
dep = new Department(204, "dep");
emp1 = new Employee(205, "first", "last", dep);
proj = new Project("project");
emp1Projects = new HashSet<Project>();
emp1Projects.add(proj);
emp1.setProjects(emp1Projects);
projEmployees = new HashSet<Employee>();
projEmployees.add(emp1);
proj.setEmployees(projEmployees);
env.beginTransaction(em);
em.persist(proj);
em.persist(dep);
em.persist(emp1);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
dep = em.find(Department.class, dep.getId());
proj = em.find(Project.class, proj.getId());
em.remove(proj);
emp1 = em.find(Employee.class, emp1.getId());
// copy all projects from emp1 to emp2 with out actually touching them
emp2 = new Employee(206, "aaa", "bbb", dep);
emp2.setProjects(emp1.getProjects());
em.persist(emp2);
flushFailed = false;
try {
env.commitTransactionAndClear(em);
} catch (RuntimeException e) {
if (!checkForIllegalStateException(e)) {
throw e;
}
flushFailed = true;
}
verify(!env.isTransactionActive(em), "Transaction still active");
verify(flushFailed, "flush succeeded although there is a relation to a removed entity");
em.clear();
} finally {
closeEntityManager(em);
}
}
/**
* Force an exception during flush and check whether the current transaction is rolled back.
*/
@Test
public void testWithException() {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
Node node = new Node(301, true); // PostPersist method will throw a Node.MyRuntimeException
env.beginTransaction(em);
em.persist(node);
boolean flushFailed = false;
try {
em.flush();
} catch (Node.MyRuntimeException e) {
// $JL-EXC$ this is expected behavior
flushFailed = true;
verify(env.isTransactionMarkedForRollback(em),
"IllegalStateException during flush did not mark transaction for rollback");
}
verify(flushFailed, "callback method did not throw exception as expected");
} finally {
closeEntityManager(em);
}
}
@Test
public void testRestoreFieldAfterFlush() {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
final String initial = "initial";
final int id = 301;
Department department = new Department(id, initial);
env.beginTransaction(em);
em.persist(department);
department.setName("changed");
em.flush();
// undo the change between flush and commit (on a new entity)
department.setName(initial);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
department = em.find(Department.class, id);
verify(initial.equals(department.getName()), "wrong name: " + department.getName());
department.setName("changed");
em.flush();
// lets try the same with a managed field
department.setName(initial);
env.commitTransactionAndClear(em);
department = em.find(Department.class, id);
verify(initial.equals(department.getName()), "wrong name: " + department.getName());
} finally {
closeEntityManager(em);
}
}
@Test
public void testRestoreRelationAfterFlush() {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
final int id = 302;
Employee frank = new Employee(id, "Frank", "Schuster", null);
env.beginTransaction(em);
Review r1 = new Review(101, Date.valueOf("2006-10-19"), "Performance");
frank.addReview(r1);
Review r2 = new Review(102, Date.valueOf("2006-10-19"), "Passion");
frank.addReview(r2);
Review r3 = new Review(103, Date.valueOf("2006-10-19"), "Six-Sigma");
frank.addReview(r3);
em.persist(frank);
em.persist(r1);
em.persist(r2);
em.persist(r3);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
frank = em.find(Employee.class, id);
Set<Review> reviewsFound = frank.getReviews();
int foundSize = reviewsFound.size();
// lets remove a department the same with a managed field
Set<Review> set = new HashSet<Review>();
set.add(r1);
set.add(r2);
frank.setReviews(set);
em.flush();
// undo the change
frank.setReviews(reviewsFound);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
frank = em.find(Employee.class, id);
verify(frank.getReviews().size() == foundSize, "wrong number of reviews: " + frank.getReviews().size());
env.rollbackTransactionAndClear(em);
} finally {
closeEntityManager(em);
}
}
@Test
public void testNoTransaction() {
final JPAEnvironment env = getEnvironment();
final EntityManager em = env.getEntityManager();
try {
em.flush();
flop("exception not thrown as expected");
} catch (TransactionRequiredException e) {
// $JL-EXC$ expected behavior
} finally {
closeEntityManager(em);
}
}
@SuppressWarnings("unchecked")
@Test
public void testTransactionMarkedForRollback() {
final JPAEnvironment env = getEnvironment();
final EntityManager em = env.getEntityManager();
Department dep = new Department(401, "dep401");
try {
env.beginTransaction(em);
em.persist(dep);
env.markTransactionForRollback(em);
em.flush();
// verify that entity is inserted
Query query = em.createQuery("select d from Department d where d.id = ?1");
query.setParameter(1, dep.getId());
List<Department> result = query.getResultList();
verify(result.size() == 1, "query returned " + result.size() + " entities");
env.rollbackTransaction(em);
} finally {
closeEntityManager(em);
}
}
@Test
@Bugzilla(bugid=309681)
public void testChangedEntityIgnoredByFlush() {
final JPAEnvironment env = getEnvironment();
final EntityManager em = env.getEntityManager();
Employee emp = new Employee(911, "Robi", "Tobi", null);
try {
env.beginTransaction(em);
em.persist(emp);
env.commitTransactionAndClear(em);
env.beginTransaction(em);
Employee found = em.find(Employee.class, 911);
found.clearPostUpdate();
found.setLastName("lesbar");
em.createQuery("select i from Island i").getResultList();
verify(!found.postUpdateWasCalled(), "post update was called");
env.rollbackTransactionAndClear(em);
} finally {
closeEntityManager(em);
}
}
}