/******************************************************************************* * 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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 javax.persistence.EntityManager; import javax.persistence.Query; import javax.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(new Integer(3), new Integer(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(new Integer(103), new Integer(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, new Integer(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, new Integer(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, new Integer(emp1.getId())); emp2 = em.find(Employee.class, new Integer(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(new Integer(113), new Integer(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, new Integer(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, new Integer(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, new Integer(emp1.getId())); emp2 = em.find(Employee.class, new Integer(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 -> IllegalStateException expected because of relation emp2 -> 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, new Integer(dep.getId())); proj = em.find(Project.class, proj.getId()); em.remove(proj); emp1 = em.find(Employee.class, new Integer(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, new Integer(dep.getId())); proj = em.find(Project.class, proj.getId()); em.remove(proj); emp1 = em.find(Employee.class, new Integer(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, Integer.valueOf(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, Integer.valueOf(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, Integer.valueOf(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, Integer.valueOf(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, Integer.valueOf(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); } } }