Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java?rev=739123&view=auto ============================================================================== --- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java (added) +++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java Fri Jan 30 01:27:35 2009 @@ -0,0 +1,495 @@ +/* + * 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.jdbc.sqlcache; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.openjpa.conf.OpenJPAConfiguration; +import org.apache.openjpa.kernel.PreparedQuery; +import org.apache.openjpa.kernel.QueryHints; +import org.apache.openjpa.kernel.QueryLanguages; +import org.apache.openjpa.kernel.PreparedQueryCache; +import org.apache.openjpa.kernel.QueryStatistics; +import org.apache.openjpa.kernel.jpql.JPQLParser; +import org.apache.openjpa.persistence.OpenJPAEntityManager; +import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI; +import org.apache.openjpa.persistence.OpenJPAPersistence; +import org.apache.openjpa.persistence.OpenJPAQuery; +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +/** + * Performance oriented test to see comparative difference in response time with + * or with SQL caching. + * + * @author Pinaki Poddar + * + */ +public class TestPreparedQueryCache extends SingleEMFTestCase { + // Fail if performance degrades with cache compared to without cache + public static final boolean FAIL_ON_PERF_DEGRADE = + Boolean.getBoolean("FailOnPerformanceRegression"); + + // # observations to compute timing statistics + public static final int SAMPLE_SIZE = 100; + + public static final boolean USE_CACHE = true; + public static final boolean BIND_DIFFERENT_PARM_VALUES = true; + public static final boolean IS_NAMED_QUERY = true; + + public static final String[] COMPANY_NAMES = { "acme.org" }; + public static final String[] DEPARTMENT_NAMES = { "Marketing", "Sales", + "Engineering" }; + public static final String[] EMPLOYEE_NAMES = { "Tom", "Dick", "Harray" }; + + public static final String EXCLUDED_QUERY_1 = "select count(p) from Company p"; + public static final String EXCLUDED_QUERY_2 = "select count(p) from Department p"; + public static final String INCLUDED_QUERY = "select p from Address p"; + + public void setUp() throws Exception { + super.setUp(Company.class, Department.class, Employee.class, + Address.class, + "openjpa.Log", "SQL=WARN", + "openjpa.QuerySQLCache", + "true(excludes='select count(p) from Company p;select count(p) from Department p')"); + } + + public void tearDown() throws Exception { + super.tearDown(); + } + + PreparedQueryCache getCache() { + return emf.getConfiguration().getQuerySQLCacheInstance(); + } + + public void testPreparedQueryCacheIsActiveByDefault() { + OpenJPAConfiguration conf = emf.getConfiguration(); + assertTrue(conf.getQuerySQLCache().startsWith("true")); + assertNotNull(getCache()); + } + + public void testPreparedQueryCacheCanBeDeactivatedDynamically() { + OpenJPAConfiguration conf = emf.getConfiguration(); + assertNotNull(getCache()); + conf.setQuerySQLCache("false"); + assertNull(getCache()); + } + + public void testPreparedQueryCacheIsPerUnitSingleton() { + PreparedQueryCache c1 = getCache(); + PreparedQueryCache c2 = getCache(); + assertSame(c1, c2); + } + + public void testPreparedQueryIdentifierIsOriginalJPQLQuery() { + String jpql = "select p from Company p"; + OpenJPAEntityManager em = emf.createEntityManager(); + OpenJPAQuery q1 = em.createQuery(jpql); + q1.getResultList(); + PreparedQuery pq = getCache().get(jpql); + assertNotNull(pq); + assertEquals(jpql, pq.getIdentifier()); + assertEquals(jpql, pq.getOriginalQuery()); + } + + public void testExclusionPattern() { + OpenJPAEntityManager em = emf.createEntityManager(); + OpenJPAQuery q1 = em.createQuery(EXCLUDED_QUERY_1); + q1.getResultList(); + assertNotCached(EXCLUDED_QUERY_1); + + OpenJPAQuery q2 = em.createQuery(EXCLUDED_QUERY_2); + q2.getResultList(); + assertNotCached(EXCLUDED_QUERY_2); + + OpenJPAQuery q3 = em.createQuery(INCLUDED_QUERY); + q3.getResultList(); + assertCached(INCLUDED_QUERY); + } + + void assertLanguage(OpenJPAQuery q, String lang) { + assertEquals(lang, q.getLanguage()); + } + + void assertCached(String id) { + PreparedQuery cached = getCache().get(id); + assertNotNull(getCache() + ": " + getCache().getMapView() + + " does not contain " + id, cached); + } + + void assertNotCached(String id) { + PreparedQueryCache cache = getCache(); + if (cache != null) { + assertNull(cache.get(id)); + } + } + + public void testPreparedQueryIsCachedOnExecution() { + String jpql = "select p from Company p"; + OpenJPAEntityManager em = emf.createEntityManager(); + OpenJPAQuery q1 = em.createQuery(jpql); + assertNotCached(jpql); + assertLanguage(q1, JPQLParser.LANG_JPQL); + + q1.getResultList(); + assertCached(jpql); + assertLanguage(q1, JPQLParser.LANG_JPQL); + + PreparedQuery cached = getCache().get(jpql); + assertEquals(jpql, cached.getIdentifier()); + assertNotEquals(jpql, cached.getTargetQuery()); + } + + public void testPreparedQueryIsCachedAcrossExecution() { + String jpql = "select p from Company p"; + OpenJPAEntityManager em = emf.createEntityManager(); + OpenJPAQuery q1 = em.createQuery(jpql); + assertNotCached(jpql); + assertLanguage(q1, JPQLParser.LANG_JPQL); + + + q1.getResultList(); + assertCached(jpql); + assertLanguage(q1, JPQLParser.LANG_JPQL); + + // Create a new query with the same JPQL + // This is not only cached, its language is different too + OpenJPAQuery q2 = em.createQuery(jpql); + assertCached(jpql); + assertLanguage(q2, QueryLanguages.LANG_PREPARED_SQL); + } + + public void testInvalidatePreparedQueryWithHint() { + String jpql = "select p from Company p"; + OpenJPAEntityManager em = emf.createEntityManager(); + OpenJPAQuery q1 = em.createQuery(jpql); + assertNotCached(jpql); + + q1.getResultList(); + assertCached(jpql); + assertLanguage(q1, JPQLParser.LANG_JPQL); + + // Create a new query with the same JPQL + // This is cached on creation, its language is Prepared SQL + OpenJPAQuery q2 = em.createQuery(jpql); + assertCached(jpql); + assertLanguage(q2, QueryLanguages.LANG_PREPARED_SQL); + q2.getResultList(); + + // Now execute with hints to invalidate. + q2.setHint(QueryHints.HINT_INVALIDATE_PREPARED_QUERY, true); + // Immediately it should be removed from the cache + assertNotCached(jpql); + assertEquals(JPQLParser.LANG_JPQL, q2.getLanguage()); + q2.getResultList(); + + // Create a new query with the same JPQL + // This is not cached on creation, its language is JPQL + OpenJPAQuery q3 = em.createQuery(jpql); + assertNotCached(jpql); + assertLanguage(q3, JPQLParser.LANG_JPQL); + } + + public void testIgnorePreparedQueryWithHint() { + String jpql = "select p from Company p"; + OpenJPAEntityManager em = emf.createEntityManager(); + OpenJPAQuery q1 = em.createQuery(jpql); + assertNotCached(jpql); + + q1.getResultList(); + assertCached(jpql); + assertLanguage(q1, JPQLParser.LANG_JPQL); + + // Create a new query with the same JPQL + // This is cached on creation, its language is PREPARED SQL + OpenJPAQuery q2 = em.createQuery(jpql); + assertCached(jpql); + assertLanguage(q2, QueryLanguages.LANG_PREPARED_SQL); + q2.getResultList(); + + // Now execute with hints to ignore. + q2.setHint(QueryHints.HINT_IGNORE_PREPARED_QUERY, true); + // It should remain in the cache + assertCached(jpql); + // But its language should be JPQL and not PREPARED SQL + assertEquals(JPQLParser.LANG_JPQL, q2.getLanguage()); + q2.getResultList(); + + // Create a new query with the same JPQL + // This is cached on creation, its language is PREPARED SQL + OpenJPAQuery q3 = em.createQuery(jpql); + assertCached(jpql); + assertLanguage(q3, QueryLanguages.LANG_PREPARED_SQL); + } + + public void testQueryStatistics() { + String jpql1 = "select c from Company c"; + String jpql2 = "select c from Company c where c.name = 'PObject'"; + OpenJPAEntityManager em = emf.createEntityManager(); + int N1 = 5; + int N2 = 8; + for (int i = 0; i < N1; i++) { + OpenJPAQuery q1 = em.createQuery(jpql1); + q1.getResultList(); + } + for (int i = 0; i < N2; i++) { + OpenJPAQuery q2 = em.createQuery(jpql2); + q2.getResultList(); + } + + QueryStatistics stats = getCache().getStatistics(); + stats.dump(System.out); + + assertEquals(N1, stats.getExecutionCount(jpql1)); + assertEquals(N2, stats.getExecutionCount(jpql2)); + assertEquals(N1+N2, stats.getExecutionCount()); + assertEquals(N1-1, stats.getHitCount(jpql1)); + assertEquals(N2-1, stats.getHitCount(jpql2)); + assertEquals(N1+N2-2, stats.getHitCount()); + + } + + public void testResetQueryStatistics() { + String jpql1 = "select c from Company c"; + String jpql2 = "select c from Company c where c.name = 'PObject'"; + OpenJPAEntityManager em = emf.createEntityManager(); + int N10 = 4; + int N20 = 7; + for (int i = 0; i < N10; i++) { + OpenJPAQuery q1 = em.createQuery(jpql1); + q1.getResultList(); + } + for (int i = 0; i < N20; i++) { + OpenJPAQuery q2 = em.createQuery(jpql2); + q2.getResultList(); + } + + QueryStatistics stats = getCache().getStatistics(); + assertEquals(N10, stats.getExecutionCount(jpql1)); + assertEquals(N20, stats.getExecutionCount(jpql2)); + assertEquals(N10+N20, stats.getExecutionCount()); + assertEquals(N10-1, stats.getHitCount(jpql1)); + assertEquals(N20-1, stats.getHitCount(jpql2)); + assertEquals(N10+N20-2, stats.getHitCount()); + + stats.reset(); + + int N11 = 7; + int N21 = 4; + for (int i = 0; i < N11; i++) { + OpenJPAQuery q1 = em.createQuery(jpql1); + q1.getResultList(); + } + for (int i = 0; i < N21; i++) { + OpenJPAQuery q2 = em.createQuery(jpql2); + q2.getResultList(); + } + + stats.dump(System.out); + + assertEquals(N11, stats.getExecutionCount(jpql1)); + assertEquals(N21, stats.getExecutionCount(jpql2)); + assertEquals(N11+N21, stats.getExecutionCount()); + assertEquals(N11, stats.getHitCount(jpql1)); + assertEquals(N21, stats.getHitCount(jpql2)); + assertEquals(N11+N21, stats.getHitCount()); + + assertEquals(N10+N11, stats.getTotalExecutionCount(jpql1)); + assertEquals(N20+N21, stats.getTotalExecutionCount(jpql2)); + assertEquals(N10+N11+N20+N21, stats.getTotalExecutionCount()); + assertEquals(N10+N11-1, stats.getTotalHitCount(jpql1)); + assertEquals(N20+N21-1, stats.getTotalHitCount(jpql2)); + assertEquals(N10+N11+N20+N21-2, stats.getTotalHitCount()); + + } + + public void testQueryWithNoParameter() { + String jpql = "select p from Company p"; + Object[] params = null; + compare(!IS_NAMED_QUERY, jpql, BIND_DIFFERENT_PARM_VALUES, params); + } + + public void testQueryWithLiteral() { + String jpql = "select p from Company p where p.name = 'PObject'"; + Object[] params = null; + compare(!IS_NAMED_QUERY, jpql, BIND_DIFFERENT_PARM_VALUES, params); + } + + public void testQueryWithParameter() { + String jpql = "select p from Company p where p.name = :param"; + Object[] params = {"param", "x"}; + compare(!IS_NAMED_QUERY, jpql, BIND_DIFFERENT_PARM_VALUES, params); + } + + public void testQueryWithJoinsAndParameters() { + String jpql = "select e from Employee e " + "where e.name = :emp " + + "and e.department.name = :dept " + + "and e.department.company.name LIKE 'IBM' " + + "and e.department.company.name = :company " + + "and e.address.zip = :zip"; + Object[] params = { "emp", "John", + "dept", "Engineering", + "company", "acme.org", + "zip", 12345}; + compare(!IS_NAMED_QUERY, jpql, BIND_DIFFERENT_PARM_VALUES, params); + } + + public void testNamedQueryWithNoParameter() { + String namedQuery = "Company.PreparedQueryWithNoParameter"; + Object[] params = null; + compare(IS_NAMED_QUERY, namedQuery, BIND_DIFFERENT_PARM_VALUES, params); + } + + public void testNamedQueryWithLiteral() { + String namedQuery = "Company.PreparedQueryWithLiteral"; + Object[] params = null; + compare(IS_NAMED_QUERY, namedQuery, BIND_DIFFERENT_PARM_VALUES, params); + } + + public void testNamedQueryWithPositionalParameter() { + String namedQuery = "Company.PreparedQueryWithPositionalParameter"; + Object[] params = {1, "x", 2, 1960}; + compare(IS_NAMED_QUERY, namedQuery, BIND_DIFFERENT_PARM_VALUES, params); + } + + public void testNamedQueryWithNamedParameter() { + String namedQuery = "Company.PreparedQueryWithNamedParameter"; + Object[] params = {"name", "x", "startYear", 1960}; + compare(IS_NAMED_QUERY, namedQuery, BIND_DIFFERENT_PARM_VALUES, params); + } + + /** + * Compare the result of execution of the same query with and without + * Prepared Query Cache. + * + */ + void compare(boolean isNamed, String jpql, boolean append, Object... params) { + String realJPQL = isNamed ? getJPQL(jpql) : jpql; + // run the query once for warming up + run(jpql, params, !USE_CACHE, 1, isNamed, append); + + // run N times without cache + long without = run(jpql, params, !USE_CACHE, SAMPLE_SIZE, isNamed, append); + assertNotCached(realJPQL); + + // run N times with cache + long with = run(jpql, params, USE_CACHE, SAMPLE_SIZE, isNamed, append); + assertCached(realJPQL); + + long delta = (without == 0) ? 0 : (without - with) * 100 / without; + + String sql = getSQL(realJPQL); + System.err.println("Execution time in nanos for " + SAMPLE_SIZE + + " query execution with and without SQL cache:" + with + " " + + without + " (" + delta + "%)"); + System.err.println("JPQL: " + realJPQL); + System.err.println("SQL : " + sql); + if (delta < 0) { + if (FAIL_ON_PERF_DEGRADE) + assertFalse("change in execution time = " + delta + "%", + delta < 0); + else + System.err.println("*** WARN: Perforamce regression with cache." + + " Execution time degrades by " + delta + "%"); + } else { + System.err.println("change in execution time = +" + delta + "%"); + } + } + + /** + * Create and run a query N times with the given parameters. The time for + * each query execution is measured in nanosecond precision and + * median time taken in N observation is returned. + * + * returns median time taken for single execution. + */ + long run(String jpql, Object[] params, boolean useCache, int N, + boolean isNamedQuery, boolean appendIndexValuetoParameters) { + OpenJPAEntityManager em = emf.createEntityManager(); + ((OpenJPAEntityManagerSPI)em).setQuerySQLCache(useCache); + assertEquals(useCache, ((OpenJPAEntityManagerSPI)em).getQuerySQLCache()); + List<Long> stats = new ArrayList<Long>(); + for (int i = 0; i < N; i++) { + long start = System.nanoTime(); + OpenJPAQuery q = isNamedQuery + ? em.createNamedQuery(jpql) : em.createQuery(jpql); + for (int j = 0; params != null && j < params.length - 1; j += 2) { + Object key = params[j]; + Object val = params[j + 1]; + if (key instanceof Integer) + q.setParameter(((Number)key).intValue(), val); + else if (key instanceof String) + q.setParameter(key.toString(), val); + else + throw new RuntimeException("key " + key + " is neither Number nor String"); + } + q.getResultList(); + q.closeAll(); + long end = System.nanoTime(); + stats.add(end - start); + } + em.close(); + Collections.sort(stats); + return stats.get(N/2); + } + + /** + * Get the SQL corresponding to the given query key. + * @param jpql + * @return + */ + String getSQL(String queryKey) { + PreparedQueryCache cache = emf.getConfiguration().getQuerySQLCacheInstance(); + if (cache == null) + return "null"; + PreparedQuery query = cache.get(queryKey); + return (query != null) ? query.getTargetQuery() : "null"; + } + + String getJPQL(String namedQuery) { + return emf.getConfiguration().getMetaDataRepositoryInstance() + .getQueryMetaData(null, namedQuery, null, true) + .getQueryString(); + } + + + + public static void main(String[] args) throws Exception { + TestPreparedQueryCache _this = new TestPreparedQueryCache(); + _this.setUp(); + String jpql = "select e from Employee e where e.name = :emp and " + + "e.department.name = :dept and " + + "e.department.company.name = :company and e.address.zip = :zip"; + Object[] params = { "emp", "John", "dept", "Engineering", "company", + "acme.org", "zip", 12345 }; + System.err.println("Executing 100 times [" + jpql + "]"); + System.err.println("Press return to continue..."); +// System.in.read(); + long start = System.nanoTime(); + _this.run(jpql, params, + USE_CACHE, + SAMPLE_SIZE, + !IS_NAMED_QUERY, + BIND_DIFFERENT_PARM_VALUES); + long end = System.nanoTime(); + System.err.println("Time taken " + (end-start) + "ns"); + } + +}
Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCacheExclusion.java URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCacheExclusion.java?rev=739123&view=auto ============================================================================== --- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCacheExclusion.java (added) +++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCacheExclusion.java Fri Jan 30 01:27:35 2009 @@ -0,0 +1,124 @@ +/* + * 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.jdbc.sqlcache; + +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.openjpa.jdbc.kernel.PreparedQueryCacheImpl; +import org.apache.openjpa.jdbc.kernel.PreparedQueryImpl; +import org.apache.openjpa.kernel.PreparedQuery; +import org.apache.openjpa.kernel.PreparedQueryCache; + +/** + * Test exclusion patterns of PreparedQueryCache in isolation. + * + * @author Pinaki Poddar + * + */ +public class TestPreparedQueryCacheExclusion extends TestCase { + private PreparedQueryCache cache; + private String[] keys = {"jpql1", "jpql2", "jpql3"}; + private String[] values = {"sql1", "sql2", "sql3"}; + + protected void setUp() throws Exception { + super.setUp(); + cache = new PreparedQueryCacheImpl(); + for (int i = 0; i < keys.length; i++) { + PreparedQuery p = new PreparedQueryImpl(keys[i], values[i], null); + cache.cache(p); + } + } + + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testExclusionPatternsAreSet() { + String excludes = "a;b;c"; + cache.setExcludes(excludes); + assertEquals(3, cache.getExcludes().size()); + assertTrue(cache.isExcluded("a")); + assertTrue(cache.isExcluded("b")); + assertTrue(cache.isExcluded("c")); + assertFalse(cache.isExcluded("d")); + } + + public void testCachePopulationSetUp() { + assertContent(keys, values); + } + + public void testAddExclusionPatternDisallowsCacheing() { + int EXCLUDED = 1; + cache.addExclusionPattern(keys[EXCLUDED]); + + PreparedQuery p = new PreparedQueryImpl(keys[EXCLUDED], values[EXCLUDED], null); + assertFalse("Must not cache excluded key " + keys[EXCLUDED], cache.cache(p)); + } + + public void testAddExclusionPatternMakesExistingEntryInvalid() { + int EXCLUDED = 1; + cache.addExclusionPattern(keys[EXCLUDED]); + Map<String, String> view = cache.getMapView(); + for (int i = 0; i < keys.length; i++) { + if (i == EXCLUDED) { + assertFalse(view.containsKey(keys[i])); + assertFalse(view.containsValue(values[i])); + } else { + assertTrue(view.containsKey(keys[i])); + assertTrue(view.containsValue(values[i])); + } + } + } + + public void testRemoveExclusionPatternAllowsCacheing() { + int EXCLUDED = 1; + cache.addExclusionPattern(keys[EXCLUDED]); + + PreparedQuery p = new PreparedQueryImpl(keys[EXCLUDED], values[EXCLUDED], null); + assertFalse("Must not cache excluded key " + keys[EXCLUDED], cache.cache(p)); + + cache.removeExclusionPattern(keys[EXCLUDED]); + assertTrue("Must cache remove excluded key " + keys[EXCLUDED], cache.cache(p)); + } + + public void testRemoveExclusionPatternDoesNotRemoveUserProhbitedKeys() { + String USER_MARKED_UNCACHABLE = "[user prohibited]"; + cache.markUncachable(USER_MARKED_UNCACHABLE); + + PreparedQuery p = new PreparedQueryImpl(USER_MARKED_UNCACHABLE, "xyz", null); + assertFalse("Must not cache user-prohibited key " + + USER_MARKED_UNCACHABLE, cache.cache(p)); + + cache.removeExclusionPattern(USER_MARKED_UNCACHABLE); + assertFalse("Must not cache user-prohibited key even when removed " + + USER_MARKED_UNCACHABLE, cache.cache(p)); + } + + void assertContent(String[] keys, String[] values) { + Map<String, String> view = cache.getMapView(); + for (int i = 0; i < keys.length; i++) { + assertTrue("key " + keys[i] + " not in " + view, + view.containsKey(keys[i])); + assertTrue("value " + values[i] + " not in " + view, + view.containsValue(values[i])); + } + } +} Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestEntityTypeExpression.java URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestEntityTypeExpression.java?rev=739123&r1=739122&r2=739123&view=diff ============================================================================== --- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestEntityTypeExpression.java (original) +++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestEntityTypeExpression.java Fri Jan 30 01:27:35 2009 @@ -48,7 +48,7 @@ new Address("22 Montgomery", "SF", null, "50054") }; CompUser user1 = createUser("Seetha", "MAC", add[0], 36, true); - CompUser user2 = createUser("Shannon ", "PC", add[1], 36, false); + CompUser user2 = createUser("Shannon", "PC", add[1], 36, false); CompUser user3 = createUser("Ugo", "PC", add[2], 19, true); CompUser user4 = createUser("_Jacob", "LINUX", add[3], 10, true); CompUser user5 = createUser("Famzy", "UNIX", add[4], 29, false); @@ -91,26 +91,26 @@ String param3 = "PC"; query = "SELECT e FROM CompUser e where TYPE(e) in ?1 and e.age in ?2" + - " and e.computerName = ?3"; + " and e.computerName = ?3 ORDER By e.name"; rs = em.createQuery(query). setParameter(1, params). setParameter(2, params2). setParameter(3, param3).getResultList(); user = rs.get(0); - assertEquals("the name is not shannon", "Shannon ", user.getName()); + assertEquals("Shannon", user.getName()); - query = "SELECT e FROM CompUser e where TYPE(e) in ?1 and e.age in ?2"; + query = "SELECT e FROM CompUser e where TYPE(e) in ?1 and e.age in ?2 ORDER By e.name"; rs = em.createQuery(query). setParameter(1, params). setParameter(2, params2).getResultList(); user = rs.get(0); - assertEquals("the name is not shannon", "Shannon ", user.getName()); + assertEquals("Famzy", user.getName()); - query = "SELECT e FROM CompUser e where TYPE(e) in :params"; + query = "SELECT e FROM CompUser e where TYPE(e) in :params ORDER BY e.name DESC"; rs = em.createQuery(query). setParameter("params", params).getResultList(); user = rs.get(0); - assertEquals("the name is not shannon", "Shannon ", user.getName()); + assertEquals("_Jacob", user.getName()); query = "SELECT TYPE(e) FROM MaleUser e where TYPE(e) = MaleUser"; rs = em.createQuery(query).getResultList(); @@ -139,26 +139,26 @@ rs = em.createQuery(query). setParameter("typeName", FemaleUser.class).getResultList(); user = rs.get(0); - assertEquals("the name is not shannon", "Shannon ", user.getName()); + assertEquals("Shannon", user.getName()); - query = "SELECT e FROM CompUser e where TYPE(e) = ?1"; + query = "SELECT e FROM CompUser e where TYPE(e) = ?1 ORDER BY e.name"; rs = em.createQuery(query). setParameter(1, FemaleUser.class).getResultList(); user = rs.get(0); - assertEquals("the name is not shannon", "Shannon ", user.getName()); + assertEquals("Famzy", user.getName()); - query = "SELECT e FROM CompUser e where TYPE(e) in (?1)"; + query = "SELECT e FROM CompUser e where TYPE(e) in (?1) ORDER BY e.name"; rs = em.createQuery(query). setParameter(1, MaleUser.class).getResultList(); user = rs.get(0); - assertEquals(user.getName(), "Seetha"); + assertEquals("Seetha", user.getName()); - query = "SELECT e FROM CompUser e where TYPE(e) in (?1, ?2)"; + query = "SELECT e FROM CompUser e where TYPE(e) in (?1, ?2) ORDER BY e.name"; rs = em.createQuery(query). setParameter(1, FemaleUser.class).setParameter(2, MaleUser.class). getResultList(); user = rs.get(0); - assertEquals("the name is not shannon", "Shannon ", user.getName()); + assertEquals("Famzy", user.getName()); query = "select sum(e.age) FROM CompUser e GROUP BY e.age" + " HAVING ABS(e.age) = :param"; Modified: openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java?rev=739123&r1=739122&r2=739123&view=diff ============================================================================== --- openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java (original) +++ openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java Fri Jan 30 01:27:35 2009 @@ -31,16 +31,14 @@ import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; -import java.util.Map; -import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.Map; import java.util.Set; import javax.persistence.EntityManager; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; import javax.persistence.Query; -import javax.persistence.QueryBuilder; import javax.persistence.QueryDefinition; import org.apache.commons.lang.StringUtils; @@ -51,16 +49,17 @@ import org.apache.openjpa.kernel.AbstractBrokerFactory; import org.apache.openjpa.kernel.Broker; import org.apache.openjpa.kernel.DelegatingBroker; +import org.apache.openjpa.kernel.FetchConfiguration; import org.apache.openjpa.kernel.FindCallbacks; import org.apache.openjpa.kernel.LockLevels; import org.apache.openjpa.kernel.OpCallbacks; import org.apache.openjpa.kernel.OpenJPAStateManager; +import org.apache.openjpa.kernel.PreparedQuery; +import org.apache.openjpa.kernel.PreparedQueryCache; import org.apache.openjpa.kernel.QueryFlushModes; import org.apache.openjpa.kernel.QueryLanguages; import org.apache.openjpa.kernel.Seq; -import org.apache.openjpa.kernel.FetchConfiguration; import org.apache.openjpa.kernel.jpql.JPQLParser; -import org.apache.openjpa.kernel.jpql.ParseException; import org.apache.openjpa.lib.util.Closeable; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.meta.ClassMetaData; @@ -875,17 +874,26 @@ public OpenJPAQuery createQuery(String language, String query) { assertNotCloseInvoked(); try { + String qid = query; + PreparedQuery cached = getPreparedQuery(qid); + if (cached != null) { + language = QueryLanguages.LANG_PREPARED_SQL; + query = cached.getTargetQuery(); + } org.apache.openjpa.kernel.Query q = _broker.newQuery(language, query); // have to validate JPQL according to spec if (JPQLParser.LANG_JPQL.equals(language)) q.compile(); - return new QueryImpl(this, _ret, q); + if (cached != null) { + cached.setInto(q); + } + return new QueryImpl(this, _ret, q).setId(qid); } catch (RuntimeException re) { throw PersistenceExceptions.toPersistenceException(re); } } - + public OpenJPAQuery createQuery(Query query) { if (query == null) return createQuery((String) null); @@ -902,12 +910,25 @@ QueryMetaData meta = _broker.getConfiguration(). getMetaDataRepositoryInstance().getQueryMetaData(null, name, _broker.getClassLoader(), true); - org.apache.openjpa.kernel.Query del = - _broker.newQuery(meta.getLanguage(), null); - meta.setInto(del); + String query = null; + String language = meta.getLanguage(); + String qid = meta.getQueryString(); + + PreparedQuery cached = getPreparedQuery(qid); + if (cached != null) { + language = QueryLanguages.LANG_PREPARED_SQL; + query = cached.getTargetQuery(); + } + org.apache.openjpa.kernel.Query del = _broker.newQuery(language, + query); + if (cached != null) { + cached.setInto(del); + } else { + meta.setInto(del); + } del.compile(); - - OpenJPAQuery q = new QueryImpl(this, _ret, del); + + OpenJPAQuery q = new QueryImpl(this, _ret, del).setId(qid); String[] hints = meta.getHintKeys(); Object[] values = meta.getHintValues(); for (int i = 0; i < hints.length; i++) @@ -916,7 +937,7 @@ } catch (RuntimeException re) { throw PersistenceExceptions.toPersistenceException(re); } - } + } public OpenJPAQuery createNativeQuery(String query) { validateSQL(query); @@ -943,7 +964,23 @@ if (StringUtils.trimToNull(query) == null) throw new ArgumentException(_loc.get("no-sql"), null, null, false); } + + PreparedQueryCache getPreparedQueryCache() { + return _broker.getCachePreparedQuery() ? + getConfiguration().getQuerySQLCacheInstance() : null; + } + + /** + * Gets the prepared query cached by the given key. + * + * @return the cached PreparedQuery or null if none exists. + */ + PreparedQuery getPreparedQuery(String id) { + PreparedQueryCache cache = getPreparedQueryCache(); + return (cache == null) ? null : cache.get(id); + } + public void setFlushMode(FlushModeType flushMode) { assertNotCloseInvoked(); _broker.assertOpen(); @@ -1445,4 +1482,18 @@ throw new UnsupportedOperationException( "JPA 2.0 - Method not yet implemented"); } + + + public void setQuerySQLCache(boolean flag) { + _broker.setCachePreparedQuery(flag); + } + + public boolean getQuerySQLCache() { + return _broker.getCachePreparedQuery(); + } + + RuntimeExceptionTranslator getExceptionTranslator() { + return _ret; + } + } Modified: openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManagerSPI.java URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManagerSPI.java?rev=739123&r1=739122&r2=739123&view=diff ============================================================================== --- openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManagerSPI.java (original) +++ openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManagerSPI.java Fri Jan 30 01:27:35 2009 @@ -105,4 +105,22 @@ * previous settings. */ public void setLifecycleListenerCallbackMode(EnumSet<CallbackMode> modes); + + + /** + * Affirms if this receiver is caching database queries. + * + * @since 2.0.0 + */ + public boolean getQuerySQLCache(); + + /** + * Sets whether this receiver will cache database queries during its + * lifetime. The cache configured at BrokerFactory level is not affected by + * setting it inactive for this receiver. + * + * @since 2.0.0 + */ + public void setQuerySQLCache(boolean flag); + } Modified: openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java?rev=739123&r1=739122&r2=739123&view=diff ============================================================================== --- openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java (original) +++ openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java Fri Jan 30 01:27:35 2009 @@ -23,13 +23,11 @@ import java.sql.Time; import java.sql.Timestamp; import java.util.ArrayList; -import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -42,19 +40,26 @@ import javax.persistence.Query; import javax.persistence.TemporalType; -import org.apache.commons.collections.map.LinkedMap; import org.apache.openjpa.enhance.Reflection; +import org.apache.openjpa.kernel.Broker; import org.apache.openjpa.kernel.DelegatingQuery; import org.apache.openjpa.kernel.DelegatingResultList; +import org.apache.openjpa.kernel.FetchConfiguration; import org.apache.openjpa.kernel.Filters; +import org.apache.openjpa.kernel.PreparedQuery; +import org.apache.openjpa.kernel.PreparedQueryCache; +import org.apache.openjpa.kernel.QueryHints; import org.apache.openjpa.kernel.QueryLanguages; import org.apache.openjpa.kernel.QueryOperations; +import org.apache.openjpa.kernel.QueryStatistics; import org.apache.openjpa.kernel.exps.AggregateListener; import org.apache.openjpa.kernel.exps.FilterListener; +import org.apache.openjpa.kernel.jpql.JPQLParser; import org.apache.openjpa.lib.rop.ResultList; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.util.ImplHelper; import org.apache.openjpa.util.RuntimeExceptionTranslator; +import static org.apache.openjpa.kernel.QueryLanguages.LANG_PREPARED_SQL; /** * Implementation of {...@link Query} interface. @@ -69,14 +74,13 @@ private static final Localizer _loc = Localizer.forPackage(QueryImpl.class); - private final DelegatingQuery _query; + private DelegatingQuery _query; private transient EntityManagerImpl _em; private transient FetchPlan _fetch; private Map<String, Object> _named; private Map<Integer, Object> _positional; - - private static Object GAP_FILLER = new Object(); + private String _id; /** * Constructor; supply factory exception translator and delegate. @@ -249,183 +253,32 @@ if (_query.getOperation() != QueryOperations.OP_SELECT) throw new InvalidStateException(_loc.get("not-select-query", _query .getQueryString()), null, null, false); - - validateParameters(); - - // handle which types of parameters we are using, if any - if (_positional != null) - return _query.execute(_positional); - if (_named != null) - return _query.execute(_named); - return _query.execute(); - } - - /** - * Validate that the types of the parameters are correct. - * The idea is to catch as many validation error as possible at the facade - * layer itself. - * For native SQL queries, however, parameter validation is bypassed as - * we do not parse SQL. - * - * The expected parameters are parsed from the query and in a LinkedMap - * key : name of the parameter as declared in query - * value : expected Class of allowed value - * - * The bound parameters depends on positional or named parameter style - * - * TreeMap<Integer, Object> for positional parameters: - * key : 1-based Integer index - * value : bound value. GAP_FILLER if the position is not set. This - * simplifies validation at the kernel layer - * - * Map<String, Object> for named parameters: - * key : parameter name - * value : the bound value - * - * Validation accounts for - * a) gaps in positional parameters - * SELECT p FROM PObject p WHERE p.a1=?1 AND p.a3=?3 - * - * b) repeated parameters - * SELECT p FROM PObject p WHERE p.a1=?1 AND p.a2=?1 AND p.a3=?2 - * - * c) parameter is bound but not declared - * - * d) parameter is declared but not bound - * - * e) parameter does not match the value type - * - * f) parameter is primitive type but bound to null value - */ - private void validateParameters() { - if (isNative()) { - removeGaps(_positional); - return; - } - String query = getQueryString(); - if (_positional != null) { - LinkedMap expected = _query.getParameterTypes(); - Map<Integer, Object> actual = _positional; - for (Object o : expected.keySet()) { - String position = (String) o; - Class expectedParamType = (Class) expected.get(position); - try { - Integer.parseInt(position); - } catch (NumberFormatException ex) { - newValidationException("param-style-mismatch", query, - expected.asList(), - Arrays.toString(actual.keySet().toArray())); - } - Object actualValue = actual.get(Integer.parseInt(position)); - boolean valueUnspecified = (actualValue == GAP_FILLER) - || (actualValue == null && (actual.size() < expected - .size())); - if (valueUnspecified) - newValidationException("param-missing", position, query, - Arrays.toString(actual.keySet().toArray())); - - if (expectedParamType.isPrimitive() && actualValue == null) - newValidationException("param-type-null", - position, query, expectedParamType.getName()); - if (actualValue != null && - !Filters.wrap(expectedParamType).isInstance(actualValue)) - newValidationException("param-type-mismatch", - position, query, actualValue, - actualValue.getClass().getName(), - expectedParamType.getName()); - - } - for (Integer position : actual.keySet()) { - Object actualValue = actual.get(position); - Class expectedParamType = (Class) expected.get("" + position); - boolean paramExpected = expected.containsKey("" + position); - if (actualValue == GAP_FILLER) { - if (paramExpected) { - newValidationException("param-missing", position, query, - Arrays.toString(actual.keySet().toArray())); - } - } else { - if (!paramExpected) - newValidationException("param-extra", position, query, - expected.asList()); - if (expectedParamType.isPrimitive() && actualValue == null) - newValidationException("param-type-null", - position, query, expectedParamType.getName()); - if (actualValue != null - && !Filters.wrap(expectedParamType).isInstance(actualValue)) - newValidationException("param-type-mismatch", - position, query, actualValue, - actualValue.getClass().getName(), - expectedParamType.getName()); - - } - } - - } else if (_named != null) { - LinkedMap expected = _query.getParameterTypes(); - // key : name of the parameter used while binding - // value : user supplied parameter value. null may mean either - // user has supplied a value or not specified at all - Map<String, Object> actual = _named; - for (Object o : expected.keySet()) { - String expectedName = (String) o; - Class expectedParamType = (Class) expected.get(expectedName); - Object actualValue = actual.get(expectedName); - boolean valueUnspecified = !actual.containsKey(expectedName); - if (valueUnspecified) { - newValidationException("param-missing", expectedName, query, - Arrays.toString(actual.keySet().toArray())); - } - if (expectedParamType.isPrimitive() && actualValue == null) - newValidationException("param-type-null", - expectedName, query, expectedParamType.getName()); - if (actualValue != null - && !Filters.wrap(expectedParamType).isInstance(actualValue)) { - newValidationException("param-type-mismatch", - expectedName, query, actualValue, - actualValue.getClass().getName(), - expectedParamType.getName()); - } - } - for (String actualName : actual.keySet()) { - Object actualValue = actual.get(actualName); - Class expectedParamType = (Class) expected.get(actualName); - boolean paramExpected = expected.containsKey(actualName); - if (!paramExpected) { - newValidationException("param-extra", actualName, query, - expected.asList()); - } - if (expectedParamType.isPrimitive() && actualValue == null) - newValidationException("param-type-null", - actualName, query, expectedParamType.getName()); - if (actualValue != null - && !Filters.wrap(expectedParamType).isInstance(actualValue)) { - newValidationException("param-type-mismatch", - actualName, query, actualValue, - actualValue.getClass().getName(), - expectedParamType.getName()); - } - } - } + + Map params = _positional != null ? _positional : _named; + Boolean registered = null; + PreparedQueryCache cache = _em.getPreparedQueryCache(); + if (cache != null) { + FetchConfiguration fetch = _query.getFetchConfiguration(); + registered = cache.register(_id, _query, fetch); + boolean alreadyCached = (registered == null); + String lang = _query.getLanguage(); + QueryStatistics stats = cache.getStatistics(); + if (alreadyCached && LANG_PREPARED_SQL.equals(lang)) { + PreparedQuery pq = _em.getPreparedQuery(_id); + params = pq.reparametrize(params); + stats.recordExecution(pq.getOriginalQuery(), alreadyCached); + } else { + stats.recordExecution(_query.getQueryString(), alreadyCached); + } + } + Object result = _query.execute(params); + + if (registered == Boolean.TRUE) { + cache.initialize(_id, result); + } + return result; } - Map<Integer, Object> removeGaps(Map<Integer, Object> map) { - if (map == null || !map.containsValue(GAP_FILLER)) - return map; - List<Integer> gaps = new ArrayList<Integer>(); - for (Integer key : map.keySet()) - if (map.get(key) == GAP_FILLER) - gaps.add(key); - for (Integer gap : gaps) { - map.remove(gap); - } - return map; - } - - void newValidationException(String msgKey, Object...args) { - throw new ArgumentException(_loc.get(msgKey, args), null, null, false); - } - public List getResultList() { _em.assertNotCloseInvoked(); Object ob = execute(); @@ -548,9 +401,17 @@ throw new ArgumentException(_loc.get( "bad-query-hint-value", key, value), null, null, false); - } - _query.getFetchConfiguration().setHint(key, value); - } else + } else if (QueryHints.HINT_INVALIDATE_PREPARED_QUERY.equals + (key)) { + _query.getFetchConfiguration().setHint(key, (Boolean)value); + invalidatePreparedQuery(); + } else if (QueryHints.HINT_IGNORE_PREPARED_QUERY.equals(key)) { + _query.getFetchConfiguration().setHint(key, (Boolean)value); + ignorePreparedQuery(); + } else { + _query.getFetchConfiguration().setHint(key, value); + } + } else throw new ArgumentException(_loc.get("bad-query-hint", key), null, null, false); return this; @@ -624,10 +485,6 @@ _positional = new TreeMap<Integer, Object>(); _positional.put(position, value); - for (int i = 1; i < position; i++) - if (!_positional.containsKey(i)) - _positional.put(i, GAP_FILLER); - return this; } finally { _query.unlock(); @@ -778,4 +635,50 @@ throw new UnsupportedOperationException( "JPA 2.0 - Method not yet implemented"); } + + + /** + * Remove this query from PreparedQueryCache. + */ + private boolean invalidatePreparedQuery() { + PreparedQueryCache cache = _em.getPreparedQueryCache(); + if (cache == null) + return false; + ignorePreparedQuery(); + return cache.invalidate(_id); + } + + /** + * Ignores this query from PreparedQueryCache by recreating the original + * query if it has been cached. + */ + private void ignorePreparedQuery() { + PreparedQuery cached = _em.getPreparedQuery(_id); + if (cached == null) + return; + Broker broker = _em.getBroker(); + // Critical assumption: Only JPQL queries are cached and more + // importantly, the identifier of the prepared query is the original + // JPQL String + String JPQL = JPQLParser.LANG_JPQL; + String jpql = _id; + + org.apache.openjpa.kernel.Query newQuery = broker.newQuery(JPQL, jpql); + newQuery.getFetchConfiguration().copy(_query.getFetchConfiguration()); + newQuery.compile(); + _query = new DelegatingQuery(newQuery, _em.getExceptionTranslator()); + } + + private void recordStatistics(String query, boolean usingCachedVersion) { + PreparedQueryCache cache = _em.getPreparedQueryCache(); + if (cache == null) + return; + cache.getStatistics().recordExecution(query,usingCachedVersion); + } + + QueryImpl setId(String id) { + _id = id; + return this; + } + } Modified: openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_caching.xml URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_caching.xml?rev=739123&r1=739122&r2=739123&view=diff ============================================================================== --- openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_caching.xml (original) +++ openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_caching.xml Fri Jan 30 01:27:35 2009 @@ -1035,73 +1035,61 @@ query sql cache </secondary> </indexterm> - <para> -The query SQL cache is a <classname>Map</classname> used to cache -pushed-down SQL query strings for the find operation. As a result, -the SQL queries are only generated once in OpenJPA, and cached thereafter. -This query SQL cache is shared across entity managers and the fetch plan -is part of the cache key. You can control the SQL cache through -the <link linkend="openjpa.jdbc.QuerySQLCache"><literal> -openjpa.jdbc.QuerySQLCache</literal></link> configuration property. This -property accepts a plugin string (see <xref linkend="ref_guide_conf_plugins"/>) -describing the <classname>Map</classname> used to associate query strings and -their parsed form. This property accepts the following aliases: - </para> - <table> - <title> - Pre-defined aliases - </title> - <tgroup cols="2" align="left" colsep="1" rowsep="1"> - <colspec colname="alias"/> - <colspec colname="value"/> - <colspec colname="notes"/> - <thead> - <row> - <entry colname="alias">Alias</entry> - <entry colname="value">Value</entry> - <entry colname="notes">Notes</entry> - </row> - </thead> - <tbody> - <row> - <entry colname="alias"> -<literal>true</literal> - </entry> - <entry colname="value"> -<literal>org.apache.openjpa.util.CacheMap</literal> - </entry> - <entry colname="notes"> -The default option. Uses a -<ulink url="../javadoc/org/apache/openjpa/util/CacheMap.html"> -<literal>CacheMap</literal></ulink> to store sql string. -<literal>CacheMap</literal> maintains a fixed number of cache entries, and an -optional soft reference map for entries that are moved out of the LRU space. -So, for applications that have a monotonically increasing number of distinct -queries, this option can be used to ensure that a fixed amount of memory is -used by the cache. - </entry> - </row> - <row> - <entry colname="alias"><literal>all</literal></entry> - <entry colname="value"> -<literal>org.apache.openjpa.lib.util.ConcurrentHashMap</literal> - </entry> - <entry colname="notes"> -This is the fastest option, but sql string is never dropped from the -cache, so if you use a large number of dynamic queries, this option may result -in ever-increasing memory usage. Note that if your queries only differ in the -values of the parameters, this should not be an issue. - </entry> - </row> - <row> - <entry colname="alias"><literal>false</literal></entry> - <entry colname="value"><emphasis>none</emphasis></entry> - <entry colname="notes"> -Disables the sql cache. - </entry> - </row> - </tbody> - </tgroup> - </table> +<para> +The Query SQL Cache caches SQL statements corresponding to JPQL queries. +If a JPQL query is executed more than once in the same or different persistence +context, the SQL statement generated during the first execution is cached and +executed directly for subsequent execution. Direct execution of SQL offers +significant performance gain as it saves the cost of parsing query string and, +more importantly, populating the query expression tree during every execution. +</para> +<para> +The Query SQL Cache is configured by the <link linkend="openjpa.jdbc.QuerySQLCache"> +<literal>openjpa.QuerySQLCache</literal></link> configuration property. This +property accepts a a plugin string (see <xref linkend="ref_guide_conf_plugins"/>) +with value of <literal>true</literal> or <literal>false</literal>. +</para> +<para> +Depending upon the mapping of your persistent domain object model, +lazy/eager fetch settings on the relation paths and fetch configuration, a +single JPQL may result in multiple SQL statements. If a JPQL query requires +more than one SQL statement to be executed, then the JPQL query is not cached. +The other critical point to remember while using the Query SQL Cache is that +change in fetch plan or locking mode may cause a different target SQL statement +than the original. But Query SQL Cache does not track such changes and reuses +the cached SQL under the assumption that the query execution context has +remained unchanged. +</para> +<para> +Several mechanisms are available to the application to deactivate SQL cacheing +for a query. + <listitem>A user application can deactivate SQL Query Cache + for entire lifetime of a persistence context by invoking the following + method on OpenJPA's EntityMananger interface: + <programlisting> + <methodname>OpenJPAEntityManager.setQuerySQLCache(boolean)</methodname> + </programlisting> + </listitem> + <listitem> + A user application can instruct a particular execution of a JPQL query to + ignore any cached SQL query, by setting + <literal>QueryHints.HINT_IGNORE_PREPARED_QUERY</literal> or + <literal>"openjpa.hint.IgnorePreparedQuery"</literal> to <literal>true</literal> + via standard <literal>javax.persistence.Query.setHints()</literal>. If a + SQL query has been cached corresponding to the JPQL query prior to this + execution, then the cached SQL remains in the cache and will be reused + for any subsequent execution of the same JPQL query. + </listitem> + <listitem> + A user application can instruct all subsequent execution of a JPQL query to + ignore any cached SQL query, by setting + <literal>QueryHints.HINT_INVALIDATE_PREPARED_QUERY</literal> or + <literal>"openjpa.hint.InvalidatePreparedQuery"</literal> to <literal>true</literal> + The SQL query is removed from the cache and the JPQL query will never be + cached again during the lifetime of the entire persistence unit i.e. + EntityManagerFactory. + </listitem> +</para> + </section> </chapter>
