diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestQuerySQLCache.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestQuerySQLCache.java new file mode 100644 index 000000000..30691e538 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestQuerySQLCache.java @@ -0,0 +1,272 @@ +/* + * 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.compat; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.apache.openjpa.persistence.EntityManagerImpl; +import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI; +import org.apache.openjpa.persistence.OpenJPAPersistence; +import org.apache.openjpa.persistence.relations.TblChild; +import org.apache.openjpa.persistence.relations.TblGrandChild; +import org.apache.openjpa.persistence.relations.TblParent; +import org.apache.openjpa.persistence.simple.Person; +import org.apache.openjpa.persistence.test.AllowFailure; +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +/** + * TestQuerySQLCache is used to verify multiple permutations of openjpa.jdbc.QuerySQLCache settings that were + * valid in JPA 1.2 but may not be valid in JPA 2.0 + */ +public class TestQuerySQLCache extends SingleEMFTestCase { + + final int nThreads = 5; + final int nPeople = 100; + final int nIterations = 10; + + @Override + public void setUp() { + // need this to cleanup existing tables as some entity names are reused + setUp(DROP_TABLES, Person.class, TblChild.class, TblGrandChild.class, TblParent.class); + } + + /* + * Verify an exception is thrown if a bad cache implementation class is specified + */ + public void testBadCustomCacheSetting() { + Map props = new HashMap(System.getProperties()); + props.put("openjpa.MetaDataFactory", "jpa(Types=" + Person.class.getName() + ")"); + props.put("openjpa.jdbc.QuerySQLCache", + "org.apache.openjpa.persistence.compatible.TestQuerySQLCache.BadCacheMap"); + + try { + OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)OpenJPAPersistence. + cast(Persistence.createEntityManagerFactory("test", props)); + // + // EMF creation must throw an exception because the cache implementation class will not be found + // + assertFalse(false); + } + catch (Exception e) { + assertTrue(true); + } + } + + + /* + * Verify multi-threaded multi-entity manager finder works with the QuerySQLCache set to "all" + */ + @AllowFailure(message="OPENJPA-1179 2.0 doesn't allow 'all' as in previous releases") + public void testMultiEMCachingAll() { + Map props = new HashMap(System.getProperties()); + props.put("openjpa.MetaDataFactory", "jpa(Types=" + Person.class.getName() + ")"); + props.put("openjpa.jdbc.QuerySQLCache", "all"); + runMultiEMCaching(props); + } + + + /* + * Verify multi-threaded multi-entity manager finder works with the QuerySQLCache set to "true" + */ + public void testMultiEMCachingTrue() { + Map props = new HashMap(System.getProperties()); + props.put("openjpa.MetaDataFactory", "jpa(Types=" + Person.class.getName() + ")"); + props.put("openjpa.jdbc.QuerySQLCache", "true"); + runMultiEMCaching(props); + } + + + /* + * Verify QuerySQLCacheValue setting "true" uses the expected cache implementation and is caching + */ + @AllowFailure(message="Fails after first run with duplicate key value in a unique PK constraint or index") + public void testEagerFetch() { + Map props = new HashMap(System.getProperties()); + props.put("openjpa.MetaDataFactory", "jpa(Types=" + TblChild.class.getName() + ";" + + TblGrandChild.class.getName() + ";" + + TblParent.class.getName() + ")"); + props.put("openjpa.jdbc.QuerySQLCache", "true"); + + OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI) OpenJPAPersistence. + cast(Persistence.createEntityManagerFactory("test", props)); + EntityManagerImpl em = (EntityManagerImpl)emf.createEntityManager(); + + em.getTransaction().begin(); + + for (int i = 1; i < 3; i++) { + TblParent p = new TblParent(); + p.setParentId(i); + TblChild c = new TblChild(); + c.setChildId(i); + c.setTblParent(p); + p.addTblChild(c); + em.persist(p); + em.persist(c); + + TblGrandChild gc = new TblGrandChild(); + gc.setGrandChildId(i); + gc.setTblChild(c); + c.addTblGrandChild(gc); + + em.persist(p); + em.persist(c); + em.persist(gc); + } + em.flush(); + em.getTransaction().commit(); + em.clear(); + + for (int i = 1; i < 3; i++) { + TblParent p = em.find(TblParent.class, i); + int pid = p.getParentId(); + assertEquals(pid, i); + Collection children = p.getTblChildren(); + boolean hasChild = false; + for (TblChild c : children) { + hasChild = true; + Collection gchildren = c.getTblGrandChildren(); + int cid = c.getChildId(); + assertEquals(cid, i); + boolean hasGrandChild = false; + for (TblGrandChild gc : gchildren) { + hasGrandChild = true; + int gcId = gc.getGrandChildId(); + assertEquals(gcId, i); + } + assertTrue(hasGrandChild); + } + assertTrue(hasChild); + } + em.close(); + emf.close(); + } + + + private void runMultiEMCaching(Map props) { + EntityManagerFactory emfac = Persistence.createEntityManagerFactory("test", props); + EntityManager em = emfac.createEntityManager(); + + // + // Create some entities + // + em.getTransaction().begin(); + for (int i = 0; i < nPeople; i++) { + Person p = new Person(); + p.setId(i); + em.persist(p); + } + em.flush(); + em.getTransaction().commit(); + em.close(); + + Thread[] newThreads = new Thread[nThreads]; + FindPeople[] customer = new FindPeople[nThreads]; + for (int i=0; i < nThreads; i++) { + customer[i] = new FindPeople(emfac, 0, nPeople, nIterations, i); + newThreads[i] = new Thread(customer[i]); + newThreads[i].start(); + } + + // + // Wait for the worker threads to complete + // + for (int i = 0; i < nThreads; i++) { + try { + newThreads[i].join(); + } + catch (InterruptedException e) { + this.fail("Caught Interrupted Exception: " + e); + } + } + + // + // Run through the state of all runnables to assert if any of them failed. + // + for (int i = 0; i < nThreads; i++) { + assertFalse(customer[i].hadFailures()); + } + + // + // Clean up the entities used in this test + // + em = emfac.createEntityManager(); + em.getTransaction().begin(); + for (int i = 0; i < nPeople; i++) { + Person p = em.find(Person.class, i); + em.remove(p); + } + em.flush(); + em.getTransaction().commit(); + em.close(); + } + + + /* + * Simple runnable to test finder in a tight loop. Multiple instances of this runnable will run simultaneously + */ + private class FindPeople implements Runnable { + private int startId; + private int endId; + private int thread; + private int iterations; + private EntityManagerFactory emf; + private boolean failures = false; + + public FindPeople(EntityManagerFactory emf, int startId, int endId, int iterations, int thread) { + super(); + this.startId = startId; + this.endId = endId; + this.thread = thread; + this.iterations = iterations; + this.emf = emf; + } + + public boolean hadFailures() { + return failures; + } + + public void run() { + try { + EntityManager em = emf.createEntityManager(); + for (int j = 0; j < iterations; j++) { + + for (int i = startId; i < endId; i++) { + Person p1 = em.find(Person.class, i); + if (p1.getId() != i) { + System.out.println("Finder failed: " + i); + failures = true; + break; + } + } + em.clear(); + } + em.close(); + } + catch (Exception e) { + failures = true; + System.out.println("Thread " + thread + " exception :" + e ); + } + } + } +} diff --git a/openjpa-project/src/doc/manual/migration_considerations.xml b/openjpa-project/src/doc/manual/migration_considerations.xml index 4a8c09819..52debcd1b 100644 --- a/openjpa-project/src/doc/manual/migration_considerations.xml +++ b/openjpa-project/src/doc/manual/migration_considerations.xml @@ -204,6 +204,22 @@ +
+ + openjpa.jdbc.QuerySQLCache + + + + In prior 1.x.x releases, the openjpa.jdbc.QuerySQLCache + configuration property for Prepared SQL Cache accepted + value all to never drop items from the + cache, but this option is no longer supported and will cause + a PersistenceException with a root cause of a ParseException + to be thrown. See + + for details on the available configuration values. + +
diff --git a/openjpa-project/src/doc/manual/ref_guide_caching.xml b/openjpa-project/src/doc/manual/ref_guide_caching.xml index cad4b83a3..5ebd9de58 100644 --- a/openjpa-project/src/doc/manual/ref_guide_caching.xml +++ b/openjpa-project/src/doc/manual/ref_guide_caching.xml @@ -1166,6 +1166,50 @@ property accepts a plugin string (see <xref linkend="ref_guide_conf_plugins"/>) with value of <literal>true</literal> or <literal>false</literal>. The default is <literal>true</literal>. </para> + <table> + <title> + Pre-defined aliases + + + + + + + + Alias + Value + Notes + + + + + +true + + +org.apache.openjpa.util.CacheMap + + +The default option. Uses a + +CacheMap to store SQL string. +CacheMap 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. + + + + false + none + +Disables the SQL cache. + + + + + Following salient points to be noted regarding usage of Prepared Query Cache.