| /* |
| * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2010, 2019 SAP. All rights reserved. |
| * Copyright (c) 2019 IBM Corporation. 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: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // SAP - tests rewritten |
| package org.eclipse.persistence.testing.tests.jpa.advanced; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import jakarta.persistence.EntityManager; |
| import jakarta.persistence.LockModeType; |
| import jakarta.persistence.PessimisticLockScope; |
| import jakarta.persistence.Query; |
| |
| import junit.framework.Test; |
| import junit.framework.TestSuite; |
| |
| import org.eclipse.persistence.config.QueryHints; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.sessions.server.ServerSession; |
| import org.eclipse.persistence.testing.framework.junit.JUnitTestCase; |
| import org.eclipse.persistence.testing.models.jpa.advanced.Address; |
| import org.eclipse.persistence.testing.models.jpa.advanced.AdvancedTableCreator; |
| import org.eclipse.persistence.testing.models.jpa.advanced.Dealer; |
| import org.eclipse.persistence.testing.models.jpa.advanced.Employee; |
| import org.eclipse.persistence.testing.models.jpa.advanced.Equipment; |
| import org.eclipse.persistence.testing.models.jpa.advanced.EquipmentCode; |
| import org.eclipse.persistence.testing.models.jpa.advanced.LargeProject; |
| import org.eclipse.persistence.testing.models.jpa.advanced.SmallProject; |
| import org.eclipse.persistence.testing.models.jpa.advanced.entities.EntyA; |
| import org.eclipse.persistence.testing.models.jpa.advanced.entities.EntyB; |
| import org.eclipse.persistence.testing.models.jpa.advanced.entities.EntyC; |
| import org.eclipse.persistence.testing.models.jpa.advanced.entities.EntyD; |
| import org.eclipse.persistence.testing.models.jpa.advanced.entities.EntyE; |
| |
| /** |
| * <p> |
| * <b>Purpose</b>: Test Pessimistic Locking Extended Scope functionality. |
| * <p> |
| * <b>Description</b>: Test the relationship will be locked or unlocked under different situations |
| * |
| */ |
| public class PessimisticLockingExtendedScopeTestSuite extends JUnitTestCase { |
| |
| public PessimisticLockingExtendedScopeTestSuite() { |
| super(); |
| } |
| |
| public PessimisticLockingExtendedScopeTestSuite(String name) { |
| super(name); |
| } |
| |
| public static Test suite() { |
| TestSuite suite = new TestSuite("PessimisticLocking ExtendedScope TestSuite"); |
| suite.addTest(new PessimisticLockingExtendedScopeTestSuite("testSetup")); |
| suite.addTest(new PessimisticLockingExtendedScopeTestSuite("testPESSMISTIC_ES1")); |
| suite.addTest(new PessimisticLockingExtendedScopeTestSuite("testPESSMISTIC_ES2")); |
| suite.addTest(new PessimisticLockingExtendedScopeTestSuite("testPESSMISTIC_ES3")); |
| suite.addTest(new PessimisticLockingExtendedScopeTestSuite("testPESSMISTIC_ES4")); |
| suite.addTest(new PessimisticLockingExtendedScopeTestSuite("testPESSMISTIC_ES5")); |
| suite.addTest(new PessimisticLockingExtendedScopeTestSuite("testPESSMISTIC_ES6")); |
| suite.addTest(new PessimisticLockingExtendedScopeTestSuite("testPESSMISTIC_ES7")); |
| suite.addTest(new PessimisticLockingExtendedScopeTestSuite("testPESSMISTIC_ES8")); |
| suite.addTest(new PessimisticLockingExtendedScopeTestSuite("testPESSMISTIC_ES9")); |
| return suite; |
| } |
| |
| public void testSetup() { |
| ServerSession session = JUnitTestCase.getServerSession(); |
| new AdvancedTableCreator().replaceTables(session); |
| //make the entity EquipmentCode read-write for the following tests |
| ClassDescriptor descriptor = session.getDescriptor(EquipmentCode.class); |
| boolean shouldBeReadOnly = descriptor.shouldBeReadOnly(); |
| descriptor.setShouldBeReadOnly(false); |
| clearCache(); |
| } |
| |
| interface Actor<X> { |
| void setup(EntityManager em); |
| |
| X getEntityToLock(EntityManager em); |
| |
| void modify(EntityManager em); |
| |
| void check(EntityManager em, X lockedEntity); |
| } |
| |
| // Entity relationships for which the locked entity contains the foreign key |
| // will be locked with bidirectional one-to-one mapping without mappedBy |
| // (Scenario 1.1) |
| public void testPESSMISTIC_ES1() throws Exception { |
| if (getPlatform().isSQLServer()) { |
| warning("This test deadlocks on SQL Server"); |
| return; |
| } |
| final EntyA a = new EntyA(); |
| |
| final Actor actor = new Actor<EntyA>() { |
| |
| @Override |
| public void setup(EntityManager em) { |
| EntyC c = new EntyC(); |
| em.persist(c); |
| a.setName("test"); |
| a.setEntyC(c); |
| em.persist(a); |
| } |
| |
| @Override |
| public EntyA getEntityToLock(EntityManager em) { |
| return em.find(EntyA.class, a.getId()); |
| } |
| |
| @Override |
| public void modify(EntityManager em) { |
| EntyA a2 = em.find(EntyA.class, a.getId()); |
| a2.setEntyC(null); |
| } |
| |
| @Override |
| public void check(EntityManager em, EntyA lockedEntity) { |
| em.refresh(lockedEntity); |
| assertNotNull("other transaction modified row concurrently", lockedEntity.getEntyC()); |
| } |
| |
| }; |
| |
| testNonrepeatableRead(actor); |
| } |
| |
| // Entity relationships for which the locked entity contains the foreign key |
| // will be locked with unidirectional one-to-one mapping(Scenario 1.2) |
| public void testPESSMISTIC_ES2() throws Exception { |
| if (getPlatform().isSQLServer()) { |
| warning("This test deadlocks on SQL Server"); |
| return; |
| } |
| final EntyA a = new EntyA(); |
| |
| final Actor actor = new Actor<EntyA>() { |
| |
| @Override |
| public void setup(EntityManager em) { |
| EntyB b = new EntyB(); |
| a.setEntyB(b); |
| em.persist(a); |
| } |
| |
| @Override |
| public EntyA getEntityToLock(EntityManager em1) { |
| return em1.find(EntyA.class, a.getId()); |
| } |
| |
| @Override |
| public void modify(EntityManager em2) { |
| EntyA a2 = em2.find(EntyA.class, a.getId()); |
| a2.setEntyB(null); |
| } |
| |
| @Override |
| public void check(EntityManager em1, EntyA lockedEntity) { |
| em1.refresh(lockedEntity); |
| assertNotNull("other transaction modified row concurrently", lockedEntity.getEntyB()); |
| } |
| |
| }; |
| |
| testNonrepeatableRead(actor); |
| } |
| |
| |
| // Entity relationships for which the locked entity contains the foreign key |
| // will be locked with unidirectional many-to-one mapping(Scenario 1.3) |
| public void testPESSMISTIC_ES3() throws Exception { |
| if (getPlatform().isSQLServer()) { |
| warning("This test deadlocks on SQL Server"); |
| return; |
| } |
| final Equipment eq = new Equipment(); |
| |
| final Actor actor = new Actor<Equipment>() { |
| |
| @Override |
| public void setup(EntityManager em) { |
| EquipmentCode eqCode = new EquipmentCode(); |
| eqCode.setCode("A"); |
| em.persist(eqCode); |
| eq.setEquipmentCode(eqCode); |
| em.persist(eq); |
| } |
| |
| @Override |
| public Equipment getEntityToLock(EntityManager em1) { |
| return em1.find(Equipment.class, eq.getId()); |
| } |
| |
| @Override |
| public void modify(EntityManager em2) { |
| Equipment eq2 = em2.find(Equipment.class, eq.getId()); |
| eq2.setEquipmentCode(null); |
| } |
| |
| @Override |
| public void check(EntityManager em1, Equipment lockedEntity) { |
| em1.refresh(lockedEntity); |
| assertNotNull("other transaction modified row concurrently", lockedEntity.getEquipmentCode()); |
| } |
| |
| }; |
| |
| testNonrepeatableRead(actor); |
| } |
| |
| |
| // Entity relationships for which the locked entity contains the foreign key |
| // will be locked with bidirectional many-to-one mapping(Scenario 1.4) |
| public void testPESSMISTIC_ES4() throws Exception { |
| if (getPlatform().isSQLServer()) { |
| warning("This test deadlocks on SQL Server"); |
| return; |
| } |
| if ((JUnitTestCase.getServerSession()).getPlatform().isHANA()) { |
| // HANA currently doesn't support pessimistic locking with queries on multiple tables |
| // feature is under development (see bug 384129), but test should be skipped for the time being |
| return; |
| } |
| final Employee emp = new Employee(); |
| |
| final Actor actor = new Actor<Employee>() { |
| |
| @Override |
| public void setup(EntityManager em) { |
| Address ads = new Address("SomeStreet", "somecity", "province", "country", "postalcode"); |
| emp.setAddress(ads); |
| em.persist(emp); |
| } |
| |
| @Override |
| public Employee getEntityToLock(EntityManager em1) { |
| return em1.find(Employee.class, emp.getId()); |
| } |
| |
| @Override |
| public void modify(EntityManager em2) { |
| Employee emp2 = em2.find(Employee.class, emp.getId()); |
| emp2.setAddress((Address)null); |
| } |
| |
| @Override |
| public void check(EntityManager em1, Employee lockedEntity) { |
| em1.refresh(lockedEntity); |
| assertNotNull("other transaction modified row concurrently", lockedEntity.getAddress()); |
| } |
| |
| }; |
| |
| testNonrepeatableRead(actor); |
| } |
| |
| |
| // Relationships owned by the entity that are contained in join tables will |
| // be locked with Unidirectional OneToMany mapping (Scenario 2.2) |
| public void testPESSMISTIC_ES5() throws Exception { |
| if (getPlatform().isSQLServer()) { |
| warning("This test deadlocks on SQL Server"); |
| return; |
| } |
| final EntyA entyA = new EntyA(); |
| |
| final Actor actor = new Actor<EntyA>() { |
| |
| @Override |
| public void setup(EntityManager em) { |
| em.persist(entyA); |
| entyA.getEntyDs().add(new EntyD()); |
| } |
| |
| @Override |
| public EntyA getEntityToLock(EntityManager em1) { |
| return em1.find(EntyA.class, entyA.getId()); |
| } |
| |
| @Override |
| public void modify(EntityManager em2) { |
| EntyA entyA2 = em2.find(EntyA.class, entyA.getId()); |
| entyA2.setEntyDs(null); |
| } |
| |
| @Override |
| public void check(EntityManager em1, EntyA lockedEntity) { |
| em1.refresh(lockedEntity); |
| assertNotNull("other transaction modified row concurrently", lockedEntity.getEntyDs()); |
| |
| final Collection collection; |
| if (getServerSession().getPlatform().isMaxDB()) { |
| // avoid accessing EntyD's table as this would lead to a dead lock |
| Query query = em1.createNativeQuery("SELECT t2.entyDs_ID FROM ADV_ENTYA_ADV_ENTYD t2, ADV_ENTYA t1 WHERE ((? = t1.ID) AND ((t2.EntyA_ID = t1.ID)))"); |
| query.setParameter(1, lockedEntity.getId()); |
| collection = query.getResultList(); |
| } else { |
| collection = lockedEntity.getEntyDs(); |
| } |
| |
| assertFalse("other transaction modified row concurrently", collection.isEmpty()); |
| } |
| |
| }; |
| |
| testNonrepeatableRead(actor); |
| } |
| |
| |
| //Relationships owned by the entity that are contained in join tables will be locked with Unidirectional ManyToMany mapping (Scenario 2.3) |
| public void testPESSMISTIC_ES6() throws Exception { |
| if (getPlatform().isSQLServer()) { |
| warning("This test deadlocks on SQL Server"); |
| return; |
| } |
| final EntyA entyA = new EntyA(); |
| |
| final Actor actor = new Actor<EntyA>() { |
| |
| @Override |
| public void setup(EntityManager em) { |
| Collection entyEs = new ArrayList(); |
| EntyE entyE1 = new EntyE(); |
| EntyE entyE2 = new EntyE(); |
| entyEs.add(entyE1); |
| entyEs.add(entyE2); |
| entyA.setEntyEs(entyEs); |
| em.persist(entyA); |
| } |
| |
| @Override |
| public EntyA getEntityToLock(EntityManager em1) { |
| return em1.find(EntyA.class, entyA.getId()); |
| } |
| |
| @Override |
| public void modify(EntityManager em2) { |
| EntyA entyA2 = em2.find(EntyA.class, entyA.getId()); |
| entyA2.setEntyEs(null); |
| } |
| |
| @Override |
| public void check(EntityManager em1, EntyA lockedEntity) { |
| em1.refresh(lockedEntity); |
| assertNotNull("other transaction modified row concurrently", lockedEntity.getEntyEs()); |
| assertFalse("other transaction modified row concurrently", lockedEntity.getEntyEs().isEmpty()); |
| } |
| |
| }; |
| |
| testNonrepeatableRead(actor); |
| } |
| |
| |
| |
| /* |
| * The test should assert that the following phenomenon does not occur |
| * after a row has been locked by T1: |
| * |
| * - P2 (Non-repeatable read): Transaction T1 reads a row. Another |
| * transaction T2 then modifies or deletes that row, before T1 has |
| * committed or rolled back. |
| */ |
| private <X> void testNonrepeatableRead(final Actor<X> actor) throws InterruptedException { |
| // Cannot create parallel entity managers in the server. |
| if (isOnServer() || !isSelectForUpateSupported()) { |
| return; |
| } |
| |
| EntityManager em = createEntityManager(); |
| EntyC c = null; |
| try { |
| beginTransaction(em); |
| actor.setup(em); |
| commitTransaction(em); |
| } catch (RuntimeException ex) { |
| throw ex; |
| } finally { |
| if (isTransactionActive(em)) { |
| rollbackTransaction(em); |
| } |
| closeEntityManager(em); |
| } |
| |
| Exception lockTimeOutException = null; |
| LockModeType lockMode = LockModeType.PESSIMISTIC_WRITE; |
| Map<String, Object> properties = new HashMap(); |
| properties.put(QueryHints.PESSIMISTIC_LOCK_SCOPE, PessimisticLockScope.EXTENDED); |
| properties.put(QueryHints.PESSIMISTIC_LOCK_TIMEOUT, 10000); |
| |
| EntityManager em1 = createEntityManager(); |
| try { |
| beginTransaction(em1); |
| X locked = actor.getEntityToLock(em1); |
| em1.lock(locked, lockMode, properties); |
| |
| final EntityManager em2 = createEntityManager(); |
| try { |
| // P2 (Non-repeatable read) |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| beginTransaction(em2); |
| actor.modify(em2); |
| commitTransaction(em2); // might wait for lock to be released |
| } catch (jakarta.persistence.RollbackException ex) { |
| if (ex.getMessage().indexOf("org.eclipse.persistence.exceptions.DatabaseException") == -1) { |
| ex.printStackTrace(); |
| fail("it's not the right exception"); |
| } |
| } |
| } |
| }; |
| |
| Thread t2 = new Thread(runnable); |
| t2.start(); |
| Thread.sleep(1000); // allow t2 to atempt update |
| actor.check(em1, locked); // assert repeatable read |
| rollbackTransaction(em1); // release lock |
| t2.join(); // wait until t2 finished |
| } finally { |
| if (isTransactionActive(em2)) { |
| rollbackTransaction(em2); |
| } |
| closeEntityManager(em2); |
| } |
| } finally { |
| if (isTransactionActive(em1)) { |
| rollbackTransaction(em1); |
| } |
| closeEntityManager(em1); |
| } |
| } |
| |
| |
| |
| //Bidirectional OneToOne Relationship with target entity has foreign key, entity does not contain the foreign key will not be locked (Scenario 3.1) |
| public void testPESSMISTIC_ES7() throws Exception { |
| if (getPlatform().isSQLServer()) { |
| warning("This test deadlocks on SQL Server"); |
| return; |
| } |
| // Cannot create parallel entity managers in the server. |
| if (! isOnServer() && isSelectForUpateSupported()) { |
| EntityManager em = createEntityManager(); |
| EntyA a = null; |
| EntyC c = null; |
| try{ |
| beginTransaction(em); |
| a = new EntyA(); |
| c = new EntyC(); |
| em.persist(c); |
| a.setEntyC(c); |
| em.persist(a); |
| commitTransaction(em); |
| }catch (RuntimeException ex){ |
| throw ex; |
| }finally{ |
| if (isTransactionActive(em)){ |
| rollbackTransaction(em); |
| } |
| closeEntityManager(em); |
| } |
| |
| Exception lockTimeOutException = null; |
| LockModeType lockMode = LockModeType.PESSIMISTIC_WRITE; |
| Map<String, Object> properties = new HashMap(); |
| properties.put(QueryHints.PESSIMISTIC_LOCK_SCOPE, PessimisticLockScope.NORMAL); |
| EntityManager em1= createEntityManager(); |
| |
| try{ |
| beginTransaction(em1); |
| c = em1.find(EntyC.class, c.getId()); |
| em1.lock(c, lockMode, properties); |
| EntityManager em2 = createEntityManager(); |
| try{ |
| beginTransaction(em2); |
| c = em2.find(EntyC.class, c.getId()); |
| c.setEntyA(null); |
| commitTransaction(em2); |
| } catch(jakarta.persistence.RollbackException ex){ |
| fail("it should not throw the exception!!!"); |
| }finally{ |
| if (isTransactionActive(em2)){ |
| rollbackTransaction(em2); |
| } |
| closeEntityManager(em2); |
| } |
| }catch (Exception ex){ |
| throw ex; |
| }finally{ |
| if (isTransactionActive(em1)){ |
| rollbackTransaction(em1); |
| } |
| closeEntityManager(em1); |
| } |
| } |
| } |
| |
| //Unidirectional OneToMany Relationship, in which entity does not contain the foreign key will not be locked (Scenario 3.2) |
| public void testPESSMISTIC_ES8() throws Exception { |
| if (getPlatform().isSQLServer()) { |
| warning("This test deadlocks on SQL Server"); |
| return; |
| } |
| if ((JUnitTestCase.getServerSession()).getPlatform().isHANA()) { |
| // HANA currently doesn't support pessimistic locking with queries on multiple tables |
| // feature is under development (see bug 384129), but test should be skipped for the time being |
| return; |
| } |
| // Cannot create parallel entity managers in the server. |
| if (! isOnServer() && isSelectForUpateSupported()) { |
| EntityManager em = createEntityManager(); |
| Employee emp = null; |
| try{ |
| beginTransaction(em); |
| emp = new Employee(); |
| emp.getDealers().add(new Dealer("Honda", "Kanata")); |
| em.persist(emp); |
| commitTransaction(em); |
| }catch (RuntimeException ex){ |
| throw ex; |
| }finally{ |
| if (isTransactionActive(em)){ |
| rollbackTransaction(em); |
| } |
| closeEntityManager(em); |
| } |
| |
| Exception lockTimeOutException = null; |
| LockModeType lockMode = LockModeType.PESSIMISTIC_WRITE; |
| Map<String, Object> properties = new HashMap(); |
| properties.put(QueryHints.PESSIMISTIC_LOCK_SCOPE, PessimisticLockScope.NORMAL); |
| EntityManager em1= createEntityManager(); |
| |
| try { |
| beginTransaction(em1); |
| emp = em1.find(Employee.class, emp.getId()); |
| em1.lock(emp, lockMode, properties); |
| EntityManager em2 = createEntityManager(); |
| try { |
| beginTransaction(em2); |
| emp = em1.find(Employee.class, emp.getId()); |
| emp.setDealers(null); |
| commitTransaction(em2); |
| } catch (jakarta.persistence.RollbackException ex){ |
| fail("it should not throw the exception!!!"); |
| } finally { |
| if (isTransactionActive(em2)) { |
| rollbackTransaction(em2); |
| } |
| closeEntityManager(em2); |
| } |
| } catch (Exception ex){ |
| fail("it should not throw the exception!!!"); |
| throw ex; |
| } finally { |
| if (isTransactionActive(em1)){ |
| rollbackTransaction(em1); |
| } |
| closeEntityManager(em1); |
| } |
| } |
| } |
| |
| //Bidirectional ManyToMany Relationship, in which entity does not contain the foreign key will not be locked by default (Scenario 3.3) |
| public void testPESSMISTIC_ES9() throws Exception { |
| if (getPlatform().isSQLServer()) { |
| warning("This test deadlocks on SQL Server"); |
| return; |
| } |
| if ((JUnitTestCase.getServerSession()).getPlatform().isHANA()) { |
| // HANA currently doesn't support pessimistic locking with queries on multiple tables |
| // feature is under development (see bug 384129), but test should be skipped for the time being |
| return; |
| } |
| // Cannot create parallel entity managers in the server. |
| if (! isOnServer() && isSelectForUpateSupported()) { |
| EntityManager em = createEntityManager(); |
| Employee emp = null; |
| try{ |
| beginTransaction(em); |
| emp = new Employee(); |
| SmallProject smallProject = new SmallProject(); |
| smallProject.setName("New High School Set Up"); |
| emp.addProject(smallProject); |
| LargeProject largeProject = new LargeProject(); |
| largeProject.setName("Downtown Light Rail"); |
| largeProject.setBudget(5000); |
| emp.addProject(largeProject); |
| em.persist(emp); |
| commitTransaction(em); |
| }catch (RuntimeException ex){ |
| throw ex; |
| }finally{ |
| if (isTransactionActive(em)){ |
| rollbackTransaction(em); |
| } |
| closeEntityManager(em); |
| } |
| |
| Exception lockTimeOutException = null; |
| LockModeType lockMode = LockModeType.PESSIMISTIC_WRITE; |
| Map<String, Object> properties = new HashMap(); |
| properties.put(QueryHints.PESSIMISTIC_LOCK_SCOPE, PessimisticLockScope.NORMAL); |
| EntityManager em1= createEntityManager(); |
| |
| try{ |
| beginTransaction(em1); |
| emp = em1.find(Employee.class, emp.getId()); |
| em1.lock(emp, lockMode, properties); |
| EntityManager em2 = createEntityManager(); |
| try{ |
| beginTransaction(em2); |
| emp = em1.find(Employee.class, emp.getId()); |
| emp.setProjects(null); |
| commitTransaction(em2); |
| }catch(jakarta.persistence.RollbackException ex){ |
| fail("it should not throw the exception!!!"); |
| }finally{ |
| if (isTransactionActive(em2)){ |
| rollbackTransaction(em2); |
| } |
| closeEntityManager(em2); |
| } |
| }catch (Exception ex){ |
| fail("it should not throw the exception!!!"); |
| throw ex; |
| }finally{ |
| if (isTransactionActive(em1)){ |
| rollbackTransaction(em1); |
| } |
| closeEntityManager(em1); |
| } |
| } |
| } |
| } |