/******************************************************************************* * 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.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.persistence.EntityExistsException; import javax.persistence.EntityManager; import javax.persistence.PersistenceException; import javax.persistence.TransactionRequiredException; import junit.framework.Assert; 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.Department; import org.eclipse.persistence.testing.models.wdf.jpa1.timestamp.Nasty; import org.eclipse.persistence.testing.models.wdf.jpa1.timestamp.Timestamp; import org.eclipse.persistence.testing.models.wdf.jpa1.types.BasicTypesFieldAccess; import org.eclipse.persistence.testing.tests.wdf.jpa1.JPA1Base; import org.junit.Ignore; import org.junit.Test; public class TestPersist extends JPA1Base { private void verifyExistenceOnDatabase(int departmentId, String tableName) throws SQLException { if (tableName == null || !tableName.startsWith("TMP_")) { throw new IllegalArgumentException( "None or illegal tableName was submitted to this method. Valid tableNames have to begin with 'TMP_'."); } Connection conn = getEnvironment().getDataSource().getConnection(); try { StringBuilder builder = new StringBuilder(); builder.append("select ID from ").append(tableName).append(" where ID = ?"); PreparedStatement stmt = conn.prepareStatement(builder.toString()); try { stmt.setInt(1, departmentId); ResultSet rs = stmt.executeQuery(); try { verify(rs.next(), "no department with id " + departmentId + " found using JDBC."); } finally { rs.close(); } } finally { stmt.close(); } } finally { conn.close(); } } @Test public void testPersistNewDepartment() throws SQLException { /* * 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. */ final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { Department dep = new Department(1, "R&D"); env.beginTransaction(em); em.persist(dep); env.commitTransactionAndClear(em); // verify existence after commit verifyExistenceOnDatabase(1, "TMP_DEP"); Department dep2 = new Department(2, "QM"); env.beginTransaction(em); em.persist(dep2); em.flush(); // will not work on Oracle! // verify existence after flush env.commitTransactionAndClear(em); verifyExistenceOnDatabase(2, "TMP_DEP"); // TODO verify cascading } finally { closeEntityManager(em); } } @Test public void testPersistNewBasicTypesFieldAccess() throws SQLException { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { BasicTypesFieldAccess btfa = new BasicTypesFieldAccess(1); btfa.fill(); env.beginTransaction(em); em.persist(btfa); env.commitTransactionAndClear(em); // verify existence after commit verifyExistenceOnDatabase(1, "TMP_BASIC_TYPES_FA"); BasicTypesFieldAccess btfa2 = new BasicTypesFieldAccess(2); btfa2.fill(); env.beginTransaction(em); em.persist(btfa2); em.flush(); // will not work on Oracle! // verify existence after flush env.commitTransactionAndClear(em); verifyExistenceOnDatabase(2, "TMP_BASIC_TYPES_FA"); // TODO verify cascading } finally { closeEntityManager(em); } } private void verifyPersistManaged(boolean flushBeforePersist) { /* * If X is a pre-existing 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. */ final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { // 1. managed via find final int id1 = (flushBeforePersist ? 100 : 0) + 11; Department dep = new Department(id1, "OLD"); env.beginTransaction(em); em.persist(dep); env.commitTransactionAndClear(em); env.beginTransaction(em); // find a department in the state MANAGED dep = em.find(Department.class, new Integer(id1)); verify(dep != null, "department not found"); dep.setName("CHANGED"); if (flushBeforePersist) { em.flush(); } // this persist should be ignored as object is already managed em.persist(dep); // TODO verify that persist is ignored (use callback methods) // TODO verify cascading env.commitTransactionAndClear(em); // 2. managed via persist final int id2 = (flushBeforePersist ? 100 : 0) + 12; dep = new Department(id2, "OLD"); env.beginTransaction(em); em.persist(dep); dep.setName("CHANGED"); if (flushBeforePersist) { em.flush(); } // this persist should be ignored as object is already managed em.persist(dep); // TODO verify that persist is ignored (use callback methods) // TODO verify cascading env.commitTransactionAndClear(em); } finally { closeEntityManager(em); } } private void verifyPersistRemoved(boolean flushBeforePersist) { /* * If X is a removed entity, it becomes managed. */ final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { // 1. find an existing department, remove it and call persist final int id1 = (flushBeforePersist ? 100 : 0) + 21; Department dep = new Department(id1, "REMOVE"); env.beginTransaction(em); em.persist(dep); env.commitTransactionAndClear(em); env.beginTransaction(em); dep = em.find(Department.class, new Integer(id1)); em.remove(dep); // now, the entity should be REMOVED if (flushBeforePersist) { em.flush(); } em.persist(dep); // now, the entity should be managed again dep.setName("REINVIGORATED"); env.commitTransactionAndClear(em); dep = em.find(Department.class, new Integer(id1)); verify(dep != null, "department not found"); verify("REINVIGORATED".equals(dep.getName()), "department has wrong name: " + dep.getName()); // 2. persist a new department, remove it and call persist env.beginTransaction(em); final int id2 = (flushBeforePersist ? 100 : 0) + 22; dep = new Department(id2, "REMOVE"); em.persist(dep); em.remove(dep); if (flushBeforePersist) { em.flush(); } em.persist(dep); dep.setName("REINVIGORATED"); env.commitTransactionAndClear(em); dep = em.find(Department.class, new Integer(id2)); verify(dep != null, "department not found"); verify("REINVIGORATED".equals(dep.getName()), "department has wrong name: " + dep.getName()); } finally { closeEntityManager(em); } } @Test public void testPersistManaged() { verifyPersistManaged(false); } @Test public void testPersistManagedFlushed() { verifyPersistManaged(true); } @Test public void testPersistRemoved() { verifyPersistRemoved(false); } @Test public void testPersistRemovedFlushed() { verifyPersistRemoved(true); } @Test public void testPersistDetached() throws SQLException { /* * <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> */ final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); int id; try { // case 1: detached because entity exists on db but is not known by persistence context id = 31; Department dep = new Department(id, "DETACHED"); // firstly, we create a department on the database env.beginTransaction(em); em.persist(dep); env.commitTransactionAndClear(em); verifyExistenceOnDatabase(id, "TMP_DEP"); // now, the department is detached, we try to persist it again env.beginTransaction(em); boolean failed = false; try { em.persist(dep); } catch (EntityExistsException e) { failed = true; env.rollbackTransactionAndClear(em); } try { if (env.isTransactionActive(em)) { env.commitTransactionAndClear(em); } } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } failed = true; } verify(failed, "persist did succeed on a detached instance."); // case 2: detached because an object with same pk but different object identity is known by persistence context // case 2a: state of known object: FOR_INSERT id = 32; dep = new Department(id, "ORIGINAL"); Department detachedDep = new Department(id, "DETACHED"); failed = false; env.beginTransaction(em); em.persist(dep); // this is now in state new try { em.persist(detachedDep); } catch (EntityExistsException e) { failed = true; env.rollbackTransactionAndClear(em); } try { if (env.isTransactionActive(em)) { env.commitTransactionAndClear(em); } } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } failed = true; } verify(failed, "persist did succeed on a detached instance."); // case 2b: state of known object: FOR_UPDATE id = 33; dep = new Department(id, "ORIGINAL"); detachedDep = new Department(id, "DETACHED"); failed = false; env.beginTransaction(em); em.persist(dep); env.commitTransactionAndClear(em); verifyExistenceOnDatabase(id, "TMP_DEP"); env.beginTransaction(em); dep = em.find(Department.class, new Integer(id)); // this is now in state managed try { em.persist(detachedDep); } catch (EntityExistsException e) { failed = true; env.rollbackTransactionAndClear(em); } try { if (env.isTransactionActive(em)) { env.commitTransactionAndClear(em); } } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } failed = true; } verify(failed, "persist did succeed on a detached instance."); // case 2c: state of known object: FOR_DELETE id = 34; dep = new Department(id, "ORIGINAL"); detachedDep = new Department(id, "DETACHED"); failed = false; env.beginTransaction(em); em.persist(dep); env.commitTransactionAndClear(em); verifyExistenceOnDatabase(id, "TMP_DEP"); env.beginTransaction(em); dep = em.find(Department.class, new Integer(id)); em.remove(dep); // this is now in state deleted try { em.persist(detachedDep); } catch (EntityExistsException e) { failed = true; env.rollbackTransactionAndClear(em); } try { if (env.isTransactionActive(em)) { env.commitTransactionAndClear(em); } } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } failed = true; } verify(failed, "persist did succeed on a detached instance."); } finally { closeEntityManager(em); } } @Test @Bugzilla(bugid=309681) public void testPersistDeleteExecuted() throws SQLException { /* * If X is a detached object, an IllegalArgumentException will be thrown by the persist operation (or the transaction * commit will fail). */ final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); int id; try { // case 2d: state of known object: DELETE_EXECUTED id = 35; Department dep = new Department(id, "ORIGINAL"); Department detachedDep = new Department(id, "DETACHED"); boolean failed = false; env.beginTransaction(em); em.persist(dep); env.commitTransactionAndClear(em); verifyExistenceOnDatabase(id, "TMP_DEP"); env.beginTransaction(em); dep = em.find(Department.class, new Integer(id)); em.remove(dep); em.flush(); try { em.persist(detachedDep); } catch (IllegalArgumentException e) { failed = true; env.rollbackTransactionAndClear(em); } try { if (env.isTransactionActive(em)) { env.commitTransactionAndClear(em); } } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } failed = true; } verify(failed, "persist did succeed on a detached instance."); } finally { closeEntityManager(em); } } /* * throws IllegalArgumentException, if instance is not an entity */ @Test public void testNotAnEntity() { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { env.beginTransaction(em); try { em.persist("Hutzliputz"); flop("no IllegalArgumentException "); } catch (IllegalArgumentException e) { verify(true, ""); } finally { env.rollbackTransactionAndClear(em); } env.beginTransaction(em); try { em.persist(null); flop("no IllegalArgumentException "); } catch (IllegalArgumentException e) { verify(true, ""); } finally { env.rollbackTransactionAndClear(em); } } finally { closeEntityManager(em); } } @Ignore // @TestProperties(unsupportedEnvironments={JTANonSharedPCEnvironment.class, ResourceLocalEnvironment.class}) public void testNoTransaction() { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); Department dep = new Department(41, "dep41"); try { em.persist(dep); flop("exception not thrown as expected"); } catch (TransactionRequiredException e) { // $JL-EXC$ expected behavior } finally { closeEntityManager(em); } } @Test public void testTransactionMarkedForRollback() { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { // case 1: persistence context already available when tx marked for rollback Department dep = new Department(42, "dep42"); env.beginTransaction(em); verify(!em.contains(dep), "entity contained in persistence context"); // this ensures that the pc is available env.markTransactionForRollback(em); em.persist(dep); verify(em.contains(dep), "entity not contained in persistence context"); env.rollbackTransaction(em); // case 2: persistence context not yet available when tx marked for rollback // will throw an exception in the container-managed JTA case if (!(env instanceof /* JTASharedPCEnvironment */Object)) { dep = new Department(43, "dep43"); env.beginTransaction(em); env.markTransactionForRollback(em); em.persist(dep); verify(em.contains(dep), "entity not contained in persistence context"); env.rollbackTransaction(em); } } finally { closeEntityManager(em); } } @Test public void testPersistNewEntityWithIdModifiedInPrePersist() { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { Timestamp timestamp = new Timestamp(); verify(timestamp.getId() == null, "id is not null"); env.beginTransaction(em); em.persist(timestamp); Long value = timestamp.getId(); verify(value != null, "id is null"); env.commitTransactionAndClear(em); verify(em.find(Timestamp.class, value) != null, "not found"); } finally { closeEntityManager(em); } } private void verifyPersistRemovedEntityWithIdModifiedInPrePersist(boolean removeExecuted) { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { Timestamp timestamp = new Timestamp(); verify(timestamp.getId() == null, "id is not null"); env.beginTransaction(em); em.persist(timestamp); env.commitTransaction(em); env.beginTransaction(em); final Long id1 = timestamp.getId(); timestamp = em.find(Timestamp.class, id1); em.remove(timestamp); if (removeExecuted) { em.flush(); } em.persist(timestamp); env.commitTransactionAndClear(em); final Long id2 = timestamp.getId(); Assert.assertFalse(id2.equals(id1)); Assert.assertNull(em.find(Timestamp.class, id1)); Assert.assertNotNull(em.find(Timestamp.class, id1)); } finally { closeEntityManager(em); } } @Test @Bugzilla(bugid=309681) public void testPersistRemovedEntityWithIdModifiedInPrePersist() { verifyPersistRemovedEntityWithIdModifiedInPrePersist(false); } @Test @Bugzilla(bugid=309681) public void testPersistRemovedEntityWithIdModifiedInPrePersistFlushed() { verifyPersistRemovedEntityWithIdModifiedInPrePersist(true); } @Test public void testNastyTimestampTwice() { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { Nasty nasty = new Nasty(); verify(nasty.getId() == null, "id is not null"); env.beginTransaction(em); em.persist(nasty); Long value = nasty.getId(); verify(value != null, "id is null"); try { em.persist(new Nasty()); env.commitTransaction(em); flop("persisting second nasty timestamp succeeded"); } catch (PersistenceException ex) { Assert.assertTrue(true); } if (env.isTransactionActive(em)) { env.rollbackTransactionAndClear(em); } } finally { closeEntityManager(em); } } @Test public void testDuprec() { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { env.beginTransaction(em); Department department = new Department(999, "X"); em.persist(department); env.commitTransactionAndClear(em); env.beginTransaction(em); try { em.persist(department); // exception here env.commitTransaction(em); // or here flop("duprec not detected"); } catch (PersistenceException ex) { // OK } catch (/* JTARollback */Exception ex) { // JTA OK } } finally { closeEntityManager(em); } } @Test public void testDuprecBatch() { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { env.beginTransaction(em); Department d1 = new Department(998, "X"); em.persist(d1); Department d2 = new Department(997, "X"); em.persist(d2); Department d3 = new Department(996, "X"); em.persist(d3); Department d4 = new Department(995, "X"); em.persist(d4); Department d5 = new Department(994, "X"); em.persist(d5); env.commitTransactionAndClear(em); env.beginTransaction(em); try { d1 = new Department(993, "X"); em.persist(d1); d2 = new Department(992, "X"); em.persist(d2); d3 = new Department(991, "X"); em.persist(d3); em.persist(d4); // duprec: 995 d5 = new Department(990, "X"); em.persist(d5); env.commitTransaction(em); // or here flop("duprec not detected"); } catch (PersistenceException ex) { // OK } catch (/* JTARollback */Exception ex) { // JTA OK } } finally { closeEntityManager(em); } } }