From cf752cbc09cf5e09160ff18648f4c25b2735aac8 Mon Sep 17 00:00:00 2001 From: "Richard G. Curtis" Date: Thu, 8 Apr 2010 20:34:17 +0000 Subject: [PATCH] OPENJPA-1604: Override default lock level for NamedQueries when using the pessimistic lock manager. Code contributed by Pinaki Poddar and Rick Curtis git-svn-id: https://svn.apache.org/repos/asf/openjpa/branches/2.0.x@932095 13f79535-47bb-0310-9956-ffa450edef68 --- .../persistence/lockmgr/LockEmployee.java | 13 ++- .../lockmgr/TestNamedQueryLockMode.java | 102 ++++++++++++++++++ .../AnnotationPersistenceMetaDataParser.java | 83 +++++++++++++- .../XMLPersistenceMetaDataParser.java | 33 +++++- .../openjpa/persistence/localizer.properties | 4 +- 5 files changed, 223 insertions(+), 12 deletions(-) create mode 100644 openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/TestNamedQueryLockMode.java diff --git a/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/LockEmployee.java b/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/LockEmployee.java index b816dc6f7..15ea35a80 100644 --- a/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/LockEmployee.java +++ b/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/LockEmployee.java @@ -25,14 +25,19 @@ import java.io.ObjectOutput; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.LockModeType; +import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Version; -@NamedQuery( - name="findEmployeeById" - , query="SELECT c FROM LockEmployee c WHERE c.id = :id" - ) +@NamedQueries( { + @NamedQuery(name = "findEmployeeById", + query = "SELECT c FROM LockEmployee c WHERE c.id = :id"), + @NamedQuery(name = "findEmployeeByIdWithLock", + query = "SELECT c FROM LockEmployee c WHERE c.id = :id", lockMode = LockModeType.PESSIMISTIC_READ), + @NamedQuery(name = "findEmployeeByIdWithNoLock", + query = "SELECT c FROM LockEmployee c WHERE c.id = :id", lockMode = LockModeType.NONE) }) @Entity public class LockEmployee implements Externalizable { diff --git a/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/TestNamedQueryLockMode.java b/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/TestNamedQueryLockMode.java new file mode 100644 index 000000000..c108abacc --- /dev/null +++ b/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/TestNamedQueryLockMode.java @@ -0,0 +1,102 @@ +/* + * 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.lockmgr; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import org.apache.openjpa.persistence.TransactionRequiredException; +import org.apache.openjpa.persistence.test.AllowFailure; +import org.apache.openjpa.persistence.test.SQLListenerTestCase; + +/** + * Tests the lock mode on named query emits a FOR UPDATE clause in target SQL + * query. + * + * + */ +public class TestNamedQueryLockMode extends SQLListenerTestCase { + public void setUp() { + super.setUp(CLEAR_TABLES, LockEmployee.class, + "openjpa.LockManager", "pessimistic", + "openjpa.Optimistic", "false" + ); + } + + public void testForUpdateClausePresentInNamedQueryWithLockMode() { + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + assertClausePresentInSQL("FOR UPDATE", em.createNamedQuery("findEmployeeByIdWithLock").setParameter("id", 0)); + em.getTransaction().rollback(); + em.getTransaction().begin(); + assertClausePresentInSQL("FOR UPDATE", em.createNamedQuery("findEmployeeByIdWithLock").setParameter("id", 0)); + em.getTransaction().rollback(); + em.getTransaction().begin(); + assertClausePresentInSQL("FOR UPDATE", em.createNamedQuery("findEmployeeByIdWithLock").setParameter("id", 0)); + em.getTransaction().rollback(); + } + + @AllowFailure + public void testNamedQueryWithLockModeMustExecuteInTransaction() { + EntityManager em = emf.createEntityManager(); + // execute without a transaction + try { + em.createNamedQuery("findEmployeeByIdWithLock").setParameter("id", + 0).getResultList(); + fail("Expected " + TransactionRequiredException.class.getName()); + } catch (TransactionRequiredException e) { + // Expected + } + } + + public void testForUpdateClausePresentInQueryWithDefault() { + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + assertClausePresentInSQL("FOR UPDATE", em.createNamedQuery("findEmployeeById").setParameter("id", 0)); + assertClausePresentInSQL("FOR UPDATE", em.createNamedQuery("findEmployeeById").setParameter("id", 0)); + em.getTransaction().commit(); + } + + @AllowFailure + public void testForUpdateClauseAbsentInQueryWithExplictNoLock() { + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + assertClauseAbsentInSQL("FOR UPDATE", em.createNamedQuery("findEmployeeByIdWithNoLock").setParameter("id", 0)); + assertClauseAbsentInSQL("FOR UPDATE", em.createNamedQuery("findEmployeeByIdWithNoLock").setParameter("id", 0)); + em.getTransaction().commit(); + } + + String getLastSQL() { + String last = sql.get(getSQLCount() - 1); + assertNotNull("No last sql found", last); + return last; + } + + void assertClausePresentInSQL(String clause, Query q) { + q.getResultList(); + String last = getLastSQL(); + assertTrue(clause + " is not present in " + last, last.toUpperCase().indexOf(clause) != -1); + } + + void assertClauseAbsentInSQL(String clause, Query q) { + q.getResultList(); + String last = getLastSQL(); + assertTrue(clause + " is not absent in " + last, last.toUpperCase().indexOf(clause) == -1); + } +} diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java index ab776e4e4..8ffc4024c 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java @@ -19,7 +19,53 @@ package org.apache.openjpa.persistence; import static javax.persistence.GenerationType.AUTO; -import static org.apache.openjpa.persistence.MetaDataTag.*; +import static org.apache.openjpa.persistence.MetaDataTag.ACCESS; +import static org.apache.openjpa.persistence.MetaDataTag.CACHEABLE; +import static org.apache.openjpa.persistence.MetaDataTag.DATASTORE_ID; +import static org.apache.openjpa.persistence.MetaDataTag.DATA_CACHE; +import static org.apache.openjpa.persistence.MetaDataTag.DEPENDENT; +import static org.apache.openjpa.persistence.MetaDataTag.DETACHED_STATE; +import static org.apache.openjpa.persistence.MetaDataTag.ELEM_DEPENDENT; +import static org.apache.openjpa.persistence.MetaDataTag.ELEM_TYPE; +import static org.apache.openjpa.persistence.MetaDataTag.EMBEDDED_ID; +import static org.apache.openjpa.persistence.MetaDataTag.ENTITY_LISTENERS; +import static org.apache.openjpa.persistence.MetaDataTag.EXCLUDE_DEFAULT_LISTENERS; +import static org.apache.openjpa.persistence.MetaDataTag.EXCLUDE_SUPERCLASS_LISTENERS; +import static org.apache.openjpa.persistence.MetaDataTag.EXTERNALIZER; +import static org.apache.openjpa.persistence.MetaDataTag.EXTERNAL_VALS; +import static org.apache.openjpa.persistence.MetaDataTag.FACTORY; +import static org.apache.openjpa.persistence.MetaDataTag.FETCH_GROUP; +import static org.apache.openjpa.persistence.MetaDataTag.FETCH_GROUPS; +import static org.apache.openjpa.persistence.MetaDataTag.FLUSH_MODE; +import static org.apache.openjpa.persistence.MetaDataTag.GENERATED_VALUE; +import static org.apache.openjpa.persistence.MetaDataTag.ID; +import static org.apache.openjpa.persistence.MetaDataTag.ID_CLASS; +import static org.apache.openjpa.persistence.MetaDataTag.INVERSE_LOGICAL; +import static org.apache.openjpa.persistence.MetaDataTag.KEY_DEPENDENT; +import static org.apache.openjpa.persistence.MetaDataTag.KEY_TYPE; +import static org.apache.openjpa.persistence.MetaDataTag.LOAD_FETCH_GROUP; +import static org.apache.openjpa.persistence.MetaDataTag.LRS; +import static org.apache.openjpa.persistence.MetaDataTag.MANAGED_INTERFACE; +import static org.apache.openjpa.persistence.MetaDataTag.MAPPED_BY_ID; +import static org.apache.openjpa.persistence.MetaDataTag.MAP_KEY; +import static org.apache.openjpa.persistence.MetaDataTag.MAP_KEY_CLASS; +import static org.apache.openjpa.persistence.MetaDataTag.NATIVE_QUERIES; +import static org.apache.openjpa.persistence.MetaDataTag.NATIVE_QUERY; +import static org.apache.openjpa.persistence.MetaDataTag.ORDER_BY; +import static org.apache.openjpa.persistence.MetaDataTag.POST_LOAD; +import static org.apache.openjpa.persistence.MetaDataTag.POST_PERSIST; +import static org.apache.openjpa.persistence.MetaDataTag.POST_REMOVE; +import static org.apache.openjpa.persistence.MetaDataTag.POST_UPDATE; +import static org.apache.openjpa.persistence.MetaDataTag.PRE_PERSIST; +import static org.apache.openjpa.persistence.MetaDataTag.PRE_REMOVE; +import static org.apache.openjpa.persistence.MetaDataTag.PRE_UPDATE; +import static org.apache.openjpa.persistence.MetaDataTag.QUERIES; +import static org.apache.openjpa.persistence.MetaDataTag.QUERY; +import static org.apache.openjpa.persistence.MetaDataTag.READ_ONLY; +import static org.apache.openjpa.persistence.MetaDataTag.REPLICATED; +import static org.apache.openjpa.persistence.MetaDataTag.SEQ_GENERATOR; +import static org.apache.openjpa.persistence.MetaDataTag.TYPE; +import static org.apache.openjpa.persistence.MetaDataTag.VERSION; import java.io.File; import java.io.Serializable; @@ -60,16 +106,16 @@ import javax.persistence.FetchType; import javax.persistence.FlushModeType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; - import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.Lob; +import javax.persistence.LockModeType; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.MapKey; import javax.persistence.MapKeyClass; -import javax.persistence.MapsId; import javax.persistence.MappedSuperclass; +import javax.persistence.MapsId; import javax.persistence.NamedNativeQueries; import javax.persistence.NamedNativeQuery; import javax.persistence.NamedQueries; @@ -1777,9 +1823,11 @@ public class AnnotationPersistenceMetaDataParser meta.setLanguage(JPQLParser.LANG_JPQL); for (QueryHint hint : query.hints()) meta.addHint(hint.name(), hint.value()); - if (query.lockMode() != null) { - meta.addHint("openjpa.FetchPlan.ReadLockMode", query.lockMode()); + LockModeType lmt = processNamedQueryLockModeType(query); + if (lmt != null) { + meta.addHint("openjpa.FetchPlan.ReadLockMode", lmt); } + meta.setSource(getSourceFile(), (el instanceof Class) ? el : null, SourceTracker.SRC_ANNOTATIONS); if (isMetaDataMode()) @@ -1791,6 +1839,31 @@ public class AnnotationPersistenceMetaDataParser } } + /** + * A private worker method that calculates the lock mode for an individual NamedQuery. If the NamedQuery is + * configured to use the NONE lock mode(explicit or implicit), this method will promote the lock to a READ + * level lock. This was done to allow for JPA1 apps to function properly under a 2.0 runtime. + */ + private LockModeType processNamedQueryLockModeType(NamedQuery query) { + LockModeType lmt = query.lockMode(); + if (query.lockMode() != null) { + String lm = _conf.getLockManager(); + if (lm != null) { + lm = lm.toLowerCase(); + if (lm.contains("pessimistic")) { + if (lmt == LockModeType.NONE) { + if (_log.isWarnEnabled() == true) { + _log.warn(_loc.get("override-named-query-lock-mode", new String[] { "annotation", + query.name(), _cls.getName() })); + } + lmt = LockModeType.READ; + } + } + } + } + return lmt; + } + /** * Parse @NamedNativeQuery. */ diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/XMLPersistenceMetaDataParser.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/XMLPersistenceMetaDataParser.java index 10ac2b317..b1cecccfc 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/XMLPersistenceMetaDataParser.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/XMLPersistenceMetaDataParser.java @@ -35,6 +35,7 @@ import java.util.Stack; import javax.persistence.CascadeType; import javax.persistence.GenerationType; import javax.persistence.LockModeType; +import javax.persistence.NamedQuery; import static javax.persistence.CascadeType.*; @@ -1673,8 +1674,9 @@ public class XMLPersistenceMetaDataParser meta.setQueryString(attrs.getValue("query")); meta.setLanguage(JPQLParser.LANG_JPQL); String lockModeStr = attrs.getValue("lock-mode"); - if (lockModeStr != null) { - meta.addHint("openjpa.FetchPlan.ReadLockMode", LockModeType.valueOf(lockModeStr)); + LockModeType lmt = processNamedQueryLockModeType(log, lockModeStr, name); + if (lmt != null) { + meta.addHint("openjpa.FetchPlan.ReadLockMode", lmt); } Locator locator = getLocation().getLocator(); if (locator != null) { @@ -1694,6 +1696,33 @@ public class XMLPersistenceMetaDataParser pushElement(meta); return true; } + + /** + * A private worker method that calculates the lock mode for an individual NamedQuery. If the NamedQuery is + * configured to use the NONE lock mode(explicit or implicit), this method will promote the lock to a READ + * level lock. This was done to allow for JPA1 apps to function properly under a 2.0 runtime. + */ + private LockModeType processNamedQueryLockModeType(Log log, String lockModeString, String queryName) { + if (lockModeString == null) { + return null; + } + LockModeType lmt = LockModeType.valueOf(lockModeString); + String lm = _conf.getLockManager(); + if (lm != null) { + lm = lm.toLowerCase(); + if (lm.contains("pessimistic")) { + if (lmt == LockModeType.NONE) { + if (log != null && log.isWarnEnabled() == true) { + log.warn(_loc.get("override-named-query-lock-mode", new String[] { "xml", queryName, + _cls.getName() })); + } + lmt = LockModeType.READ; + } + } + } + + return lmt; + } protected void endNamedQuery() throws SAXException { diff --git a/openjpa-persistence/src/main/resources/org/apache/openjpa/persistence/localizer.properties b/openjpa-persistence/src/main/resources/org/apache/openjpa/persistence/localizer.properties index ef65fa9d9..b1bc10c3e 100644 --- a/openjpa-persistence/src/main/resources/org/apache/openjpa/persistence/localizer.properties +++ b/openjpa-persistence/src/main/resources/org/apache/openjpa/persistence/localizer.properties @@ -229,4 +229,6 @@ create-emf-depend-error: Failed to create a provider for "{0}" because a \ invalid-version-attribute: Persistence version attribute value "{0}" is not valid. Using version "{1}" by default. not-jpql-or-criteria-query: Query is neither a JPQL SELECT nor a Criteria API query. cache-retrieve-override: The setting of CacheRetrieveMode.USE is ignored and set to BYPASS for refresh operation. -null-detach: Can not detach null entity \ No newline at end of file +null-detach: Can not detach null entity +override-named-query-lock-mode: Encountered a read lock level less than LockModeType.READ when processing the \ +NamedQuery {0} "{1}" in class "{2}". Setting query lock level to LockModeType.READ.