| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.persistence.lock.extended; |
| |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.persistence.EntityManager; |
| import javax.persistence.LockModeType; |
| import javax.persistence.PessimisticLockScope; |
| import javax.persistence.Query; |
| |
| import org.apache.openjpa.jdbc.conf.JDBCConfiguration; |
| import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.persistence.MixedLockLevelsHelper; |
| import org.apache.openjpa.persistence.OpenJPAEntityManager; |
| import org.apache.openjpa.persistence.test.SQLListenerTestCase; |
| |
| /** |
| * Base class for locking extended scope tests. |
| * |
| * Test JPA 2.0 EM interface normal lock scope behaviors with "mixed" lock |
| * manager. When an entity instance is locked using pessimistic locking, the |
| * persistence provider must lock the database row(s) that correspond to the |
| * non-collection-valued persistent state of that instance. If a joined |
| * inheritance strategy is used, or if the entity is otherwise mapped to a |
| * secondary table, this entails locking the row(s) for the entity instance in |
| * the additional table(s). Entity relationships for which the locked entity |
| * contains the foreign key will also be locked, but not the state of the |
| * referenced entities (unless hose entities are explicitly locked). Element |
| * collections and relationships for which the entity does not contain the |
| * foreign key (such as relationships that are mapped to join tables or |
| * unidirectional one-to-many relationships for which the target entity contains |
| * the foreign key) will not be locked by default. |
| * |
| * Element collections and relationships owned by the entity that are contained |
| * in join tables will be locked if the javax.persistence.lock.scope property is |
| * specified with a value of PessimisticLockScope.EXTENDED. The state of |
| * entities referenced by such relationships will not be locked (unless those |
| * entities are explicitly locked). This property may be passed as an argument |
| * to the methods of the EntityManager and Query interfaces that allow lock |
| * modes to be specified or used with the NamedQuery annotation. |
| * |
| * @since 2.0 |
| */ |
| public abstract class LockScopeTestCase extends SQLListenerTestCase { |
| |
| protected final String Any = ".*"; |
| protected final String Select = "SELECT.*FROM.*"; |
| protected final String SelectVersion = "SELECT.*version.*FROM.*"; |
| protected final String Where = ".*WHERE.*"; |
| // protected final String Join = ".*(JOIN){1}.*"; |
| protected final String NoJoin = "(JOIN){0}"; |
| protected final String ForUpdateRex = "FOR UPDATE.*"; |
| protected final String ForUpdateClause = "(" + ForUpdateRex + ")"; |
| protected final String ForUpdate = ForUpdateClause + "{1}"; |
| protected final String NoForUpdate = ForUpdateClause + "{0}"; |
| protected final String DB2LockClause = "(" + ForUpdateRex + |
| "|FOR READ ONLY WITH R. USE AND KEEP (UPDATE|EXCLUSIVE) LOCKS)"; |
| protected final String DB2Lock = DB2LockClause + "{1}"; |
| protected final String NoDB2Lock = DB2LockClause + "{0}"; |
| |
| protected List<String> empTableName = new ArrayList<>(); |
| |
| protected Map<String, Object> normalProps; |
| protected Map<String, Object> extendedProps; |
| |
| @Override |
| protected String getPersistenceUnitName() { |
| return "locking-test"; |
| } |
| |
| protected void commonSetUp(Class<?>... eClasses ) { |
| normalProps = new HashMap<>(); |
| extendedProps = new HashMap<>(); |
| extendedProps.put("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED); |
| |
| for( Class<?> eClazz : eClasses) { |
| empTableName.add(getMapping(eClazz).getTable().getFullName()); |
| } |
| cleanupDB(); |
| } |
| |
| private void cleanupDB() { |
| EntityManager em = null; |
| try { |
| em = emf.createEntityManager(); |
| em.getTransaction().begin(); |
| for (String tableName : empTableName.toArray(new String[empTableName.size()])) { |
| em.createQuery("delete from " + tableName).executeUpdate(); |
| } |
| em.getTransaction().commit(); |
| } catch(Exception e) { |
| e.printStackTrace(); |
| } finally { |
| if (em != null && em.isOpen()) { |
| em.close(); |
| } |
| } |
| } |
| |
| protected enum DBType { |
| access, db2, derby, empress, foxpro, h2, hsql, informix, ingres, jdatastore, mariadb, mysql, oracle, pointbase, |
| postgres, sqlserver, sybase |
| } |
| |
| protected DBType getDBType(EntityManager em) { |
| JDBCConfigurationImpl conf = (JDBCConfigurationImpl) getConfiguration(em); |
| String dictClassName = getConfiguration(em).getDBDictionaryInstance().getClass().getName(); |
| String db = conf.dbdictionaryPlugin.alias(dictClassName); |
| return DBType.valueOf(db); |
| } |
| |
| @SuppressWarnings( { "unused", "deprecation" }) |
| protected JDBCConfiguration getConfiguration(EntityManager em) { |
| return ((JDBCConfiguration) ((OpenJPAEntityManager) em).getConfiguration()); |
| } |
| |
| @Override |
| protected Log getLog() { |
| return emf.getConfiguration().getLog("Tests"); |
| } |
| |
| protected Log getDumpStackLog() { |
| return emf.getConfiguration().getLog("DumpStack"); |
| } |
| |
| /* |
| * Set Log=LockTestSQL=TRACE to dump the SQL caught by the SQL listener but do not perform SQL assertion. |
| */ |
| protected Log getDumpSQLLog() { |
| return emf.getConfiguration().getLog("LockTestSQL"); |
| } |
| |
| public void assertLockTestSQLs(String... expected) { |
| Log log = getDumpSQLLog(); |
| if( log.isTraceEnabled()) { |
| log.trace("\r\n" + toString(sql)); |
| return; |
| } |
| assertAllSQLAnyOrder(expected); |
| } |
| |
| public void assertLockTestNoSQLs(String... expected) { |
| Log log = getDumpSQLLog(); |
| if( log.isTraceEnabled()) { |
| log.trace("\r\n" + toString(sql)); |
| return; |
| } |
| assertNoneSQLAnyOrder(expected); |
| } |
| |
| protected void logStack(Throwable t) { |
| StringWriter str = new StringWriter(); |
| PrintWriter print = new PrintWriter(str); |
| t.printStackTrace(print); |
| getDumpStackLog().trace(str.toString()); |
| } |
| |
| // Id designation- |
| // for basic test: |
| // [basic=0,sectable=1,singletable=2,join=4,eleColl=5,eleCollEager=6][normal=0|extended=1][entity#] |
| // For 1x1/1xm tests: |
| // [1x1=1,1xM=2] [uni=1|bi=2] [left=1|right=2] [normal=1|join=2] [default=0|lazy=1|eager=2] |
| // [normal=0|extended=1] [n-th entity] |
| protected <T> void commonLockTest(String testName, Class<T> type, int id0, boolean extended, String queryString, |
| String namedQueryString, AssertCallback verify) { |
| getLog().info("** " + testName + "()"); |
| String entityName = type.getName(); |
| String scope = extended ? "Extended" : "Normal"; |
| Map<String, Object> props = extended ? extendedProps : normalProps; |
| int id1 = id0 + 1; |
| |
| EntityManager em = null; |
| T e0 = null; |
| T e1 = null; |
| try { |
| getLog().info("-- Test find with no lock in " + scope + " scope"); |
| em = emf.createEntityManager(); |
| getLog().info(" *Begin a transaction."); |
| em.getTransaction().begin(); |
| resetSQL(); |
| getLog().info(" *Find " + entityName + "(" + id0 + ") with no lock"); |
| e0 = em.find(type, id0, props); |
| getLog().info(" *" + (e0 != null ? "F" : "Can not f") + "ind entity"); |
| verify.findNoLockDbSQL(em); |
| getLog().info(" *Found entity:" + e0); |
| assertNotNull(" *Found " + entityName + "(" + id0 + ")", e0); |
| assertEquals(" *Assert no lock applied", LockModeType.NONE, em.getLockMode(e0)); |
| |
| getLog().info(" *Find " + entityName + "(" + id1 + ") with pessimistic force increment lock"); |
| resetSQL(); |
| e1 = em.find(type, id1, LockModeType.PESSIMISTIC_FORCE_INCREMENT, props); |
| getLog().info(" *" + (e1 != null ? "F" : "Can not f") + "ind entity"); |
| verify.findPessimisticForcIncDbSQL(em); |
| getLog().info(" *Found entity:" + e1); |
| assertNotNull(" *Found " + entityName + "(" + id1 + ")", e1); |
| assertEquals(" *Assert pessimistic force increment lock applied", LockModeType.PESSIMISTIC_FORCE_INCREMENT, |
| em.getLockMode(e1)); |
| |
| getLog().info("Committing transaction."); |
| em.getTransaction().commit(); |
| } finally { |
| em = null; |
| e0 = e1 = null; |
| if (em != null && em.isOpen()) { |
| em.close(); |
| } |
| } |
| |
| try { |
| getLog().info("-- Test query with pessimistic read lock in " + scope + " scope"); |
| em = emf.createEntityManager(); |
| getLog().info(" *Begin a transaction."); |
| em.getTransaction().begin(); |
| resetSQL(); |
| int beforeReadLevel = getConfiguration(em).getReadLockLevelConstant(); |
| LockModeType beforeReadMode = MixedLockLevelsHelper.fromLockLevel(beforeReadLevel); |
| getLog().info(" *Save ReadLockLevel before Query:" + beforeReadMode); |
| getLog().info(" *Query " + entityName + "(" + id0 + ") with PESSIMISTIC_READ lock"); |
| Query q = em.createQuery(queryString); |
| if (extended) { |
| q = q.setHint("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED); |
| } |
| q = q.setLockMode(LockModeType.PESSIMISTIC_READ); |
| q = q.setParameter("firstName", "firstName%" + id0); |
| List<T> es = q.getResultList(); |
| getLog().info(" *Found " + es.size() + " entity"); |
| assertEquals(" *Should find 1 entity", es.size(), 1); |
| verify.queryPessimisticReadDbSQL(em); |
| e0 = es.get(0); |
| getLog().info(" *Found entity:" + e0); |
| assertNotNull(" *Found " + entityName + "(" + id0 + ")", e0); |
| assertEquals("Assert pessimistic read lock applied", LockModeType.PESSIMISTIC_READ, em.getLockMode(e0)); |
| assertEquals(" *Read lock should still be " + beforeReadMode + "after query set lock mode", |
| beforeReadLevel, getConfiguration(em).getReadLockLevelConstant()); |
| |
| getLog().info( |
| " *Find " + entityName + "(" + id1 |
| + ") with no lock to verify query lock set does not affect em lock mode."); |
| resetSQL(); |
| e1 = em.find(type, id1); |
| getLog().info(" *" + (e1 != null ? "F" : "Can not f") + "ind entity"); |
| verify.findNoLockAfterQueryPessimisticReadDbSQL(em); |
| getLog().info(" *Found entity:" + e1); |
| assertNotNull(" *Found " + entityName + "(" + id1 + ")", e1); |
| assertEquals(" *Assert default lock applied", LockModeType.NONE, em.getLockMode(e1)); |
| |
| getLog().info("Committing transaction."); |
| em.getTransaction().commit(); |
| } finally { |
| em = null; |
| e0 = e1 = null; |
| if (em != null && em.isOpen()) { |
| em.close(); |
| } |
| } |
| |
| try { |
| getLog().info("-- Test name query with pessimistic write lock in " + scope + " scope"); |
| em = emf.createEntityManager(); |
| getLog().info(" *Begin a transaction."); |
| em.getTransaction().begin(); |
| resetSQL(); |
| int beforeReadLevel = getConfiguration(em).getReadLockLevelConstant(); |
| LockModeType beforeReadMode = MixedLockLevelsHelper.fromLockLevel(beforeReadLevel); |
| getLog().info(" *Save ReadLockLevel before Query:" + beforeReadMode); |
| getLog().info(" *Query " + entityName + "(" + id0 + ") with PESSIMISTIC_WRITE lock"); |
| Query q = em.createNamedQuery(namedQueryString); |
| if (extended) { |
| q = q.setHint("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED); |
| } |
| q = q.setParameter("firstName", "firstName%" + id0); |
| List<T> es = q.getResultList(); |
| getLog().info(" *Found " + es.size() + " entity"); |
| assertEquals(" *Found 1 entity", es.size(), 1); |
| verify.namedQueryPessimisticWriteDbSql(em); |
| e0 = es.get(0); |
| getLog().info(" *Found entity:" + e0); |
| assertNotNull(" *Found " + entityName + "(" + id0 + ")", e0); |
| assertEquals("Assert pessimistic write lock applied", LockModeType.PESSIMISTIC_WRITE, em.getLockMode(e0)); |
| |
| getLog().info(" *Ensure ReadLockLevel remains at level " + beforeReadMode); |
| assertEquals(" *Read lock should still be " + beforeReadMode + "after query set lock mode", |
| beforeReadLevel, getConfiguration(em).getReadLockLevelConstant()); |
| |
| getLog().info( |
| " *Find " + entityName + "(" + id1 |
| + ") with no lock to verify query lock set does not affect em lock mode."); |
| resetSQL(); |
| e1 = em.find(type, id1); |
| getLog().info(" *" + (e1 != null ? "F" : "Can not f") + "ind an entity"); |
| verify.findNoLockAfterNamedQueryPessimisticWriteDbSql(em); |
| getLog().info(" *Found entity:" + e1); |
| assertNotNull(" *Found " + entityName + "(" + id1 + ")", e1); |
| assertEquals(" *Assert default lock applied", LockModeType.NONE, em.getLockMode(e1)); |
| |
| getLog().info("Committing transaction."); |
| em.getTransaction().commit(); |
| } finally { |
| em = null; |
| e0 = e1 = null; |
| if (em != null && em.isOpen()) { |
| em.close(); |
| } |
| } |
| } |
| |
| protected interface AssertCallback { |
| void findNoLockDbSQL(EntityManager em); |
| |
| void findPessimisticForcIncDbSQL(EntityManager em); |
| |
| void queryPessimisticReadDbSQL(EntityManager em); |
| |
| void findNoLockAfterQueryPessimisticReadDbSQL(EntityManager em); |
| |
| void namedQueryPessimisticWriteDbSql(EntityManager em); |
| |
| void findNoLockAfterNamedQueryPessimisticWriteDbSql(EntityManager em); |
| } |
| } |