mirror of https://github.com/apache/openjpa.git
OPENJPA-703: Support PersistenceCapable as query parameter during reparameterization of Prepared Query.
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@739589 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
937d0f42f0
commit
e30261e55a
|
@ -24,11 +24,17 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
||||
import org.apache.openjpa.jdbc.meta.MappingRepository;
|
||||
import org.apache.openjpa.jdbc.schema.Column;
|
||||
import org.apache.openjpa.jdbc.sql.SQLBuffer;
|
||||
import org.apache.openjpa.jdbc.sql.SelectExecutor;
|
||||
import org.apache.openjpa.kernel.Broker;
|
||||
import org.apache.openjpa.kernel.PreparedQuery;
|
||||
import org.apache.openjpa.kernel.Query;
|
||||
import org.apache.openjpa.lib.rop.ResultList;
|
||||
import org.apache.openjpa.util.ImplHelper;
|
||||
import org.apache.openjpa.util.InternalException;
|
||||
|
||||
/**
|
||||
* Implements {@link PreparedQuery} for SQL queries.
|
||||
|
@ -138,9 +144,9 @@ public class PreparedQueryImpl implements PreparedQuery {
|
|||
* must be compatible with the user parameters extracted during
|
||||
* {@link #initialize(Object) initialization}.
|
||||
*
|
||||
* @return key index starting from 1 and corresponding values.
|
||||
* @return 0-based parameter index mapped to corresponding values.
|
||||
*/
|
||||
public Map<Integer, Object> reparametrize(Map user) {
|
||||
public Map<Integer, Object> reparametrize(Map user, Broker broker) {
|
||||
Map<Integer, Object> result = new HashMap<Integer, Object>();
|
||||
for (int i = 0; i < _params.size(); i++) {
|
||||
result.put(i, _params.get(i));
|
||||
|
@ -151,12 +157,53 @@ public class PreparedQueryImpl implements PreparedQuery {
|
|||
int[] indices = _userParamPositions.get(key);
|
||||
if (indices == null)
|
||||
continue;
|
||||
for (int j : indices)
|
||||
result.put(j, user.get(key));
|
||||
Object value = user.get(key);
|
||||
if (ImplHelper.isManageable(value)) {
|
||||
setPersistenceCapableParameter(result, value, indices, broker);
|
||||
} else {
|
||||
for (int j : indices)
|
||||
result.put(j, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate primary key identity value(s) of the given managable instance
|
||||
* and fill in the given map.
|
||||
*
|
||||
* @param values a map of integer parameter index to parameter value
|
||||
* @param pc a manageable instance
|
||||
* @param indices the indices of the column values
|
||||
* @param broker used to obtain the primary key values
|
||||
*/
|
||||
private void setPersistenceCapableParameter(Map<Integer,Object> result,
|
||||
Object pc, int[] indices, Broker broker) {
|
||||
JDBCStore store = (JDBCStore)broker.getStoreManager()
|
||||
.getInnermostDelegate();
|
||||
MappingRepository repos = store.getConfiguration()
|
||||
.getMappingRepositoryInstance();
|
||||
ClassMapping mapping = repos.getMapping(pc.getClass(),
|
||||
broker.getClassLoader(), true);
|
||||
Column[] pks = mapping.getPrimaryKeyColumns();
|
||||
Object cols = mapping.toDataStoreValue(pc, pks, store);
|
||||
if (cols instanceof Object[]) {
|
||||
Object[] array = (Object[])cols;
|
||||
int n = array.length;
|
||||
if (n > indices.length || indices.length%n != 0)
|
||||
throw new InternalException();
|
||||
int k = 0;
|
||||
for (int j : indices) {
|
||||
result.put(j, array[k%n]);
|
||||
k++;
|
||||
}
|
||||
} else {
|
||||
for (int j : indices) {
|
||||
result.put(j, cols);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the positions of user parameters.
|
||||
*
|
||||
|
|
|
@ -93,6 +93,6 @@ public interface PreparedQuery {
|
|||
* @param user the map of parameter key and value set by the user on the
|
||||
* original query.
|
||||
*/
|
||||
public Map reparametrize(Map user);
|
||||
public Map reparametrize(Map user, Broker broker);
|
||||
|
||||
}
|
||||
|
|
|
@ -25,10 +25,14 @@ import javax.persistence.*;
|
|||
|
||||
@Entity
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="Company.PreparedQueryWithNoParameter", query="select x from Company x"),
|
||||
@NamedQuery(name="Company.PreparedQueryWithNamedParameter", query="select x from Company x where x.name=:name and x.startYear=:startYear"),
|
||||
@NamedQuery(name="Company.PreparedQueryWithPositionalParameter", query="select x from Company x where x.name=?1 and x.startYear=?2"),
|
||||
@NamedQuery(name="Company.PreparedQueryWithLiteral", query="select x from Company x where x.name='X' and x.startYear=1960")
|
||||
@NamedQuery(name="Company.PreparedQueryWithNoParameter",
|
||||
query="select x from Company x"),
|
||||
@NamedQuery(name="Company.PreparedQueryWithNamedParameter",
|
||||
query="select x from Company x where x.name=:name and x.startYear=:startYear"),
|
||||
@NamedQuery(name="Company.PreparedQueryWithPositionalParameter",
|
||||
query="select x from Company x where x.name=?1 and x.startYear=?2"),
|
||||
@NamedQuery(name="Company.PreparedQueryWithLiteral",
|
||||
query="select x from Company x where x.name='IBM' and x.startYear=1900")
|
||||
})
|
||||
public class Company {
|
||||
@Id
|
||||
|
|
|
@ -20,8 +20,11 @@ package org.apache.openjpa.persistence.jdbc.sqlcache;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.apache.openjpa.conf.OpenJPAConfiguration;
|
||||
import org.apache.openjpa.kernel.PreparedQuery;
|
||||
import org.apache.openjpa.kernel.QueryHints;
|
||||
|
@ -33,6 +36,7 @@ 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.SQLListenerTestCase;
|
||||
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
|
||||
|
||||
/**
|
||||
|
@ -42,33 +46,65 @@ import org.apache.openjpa.persistence.test.SingleEMFTestCase;
|
|||
* @author Pinaki Poddar
|
||||
*
|
||||
*/
|
||||
public class TestPreparedQueryCache extends SingleEMFTestCase {
|
||||
public class TestPreparedQueryCache extends SQLListenerTestCase {
|
||||
// Fail if performance degrades with cache compared to without cache
|
||||
public static final boolean FAIL_ON_PERF_DEGRADE =
|
||||
Boolean.getBoolean("FailOnPerformanceRegression");
|
||||
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[] COMPANY_NAMES = {"IBM", "BEA", "acme.org" };
|
||||
public static final int[] START_YEARS = {1900, 2000, 2010 };
|
||||
public static final String[] DEPARTMENT_NAMES = { "Marketing", "Sales",
|
||||
"Engineering" };
|
||||
public static final String[] EMPLOYEE_NAMES = { "Tom", "Dick", "Harray" };
|
||||
public static final String[] CITY_NAMES = {"Tulsa", "Durban", "Harlem"};
|
||||
|
||||
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";
|
||||
private Company IBM;
|
||||
|
||||
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')");
|
||||
createTestData();
|
||||
}
|
||||
|
||||
void createTestData() {
|
||||
EntityManager em = emf.createEntityManager();
|
||||
em.getTransaction().begin();
|
||||
for (int i = 0; i < COMPANY_NAMES.length; i++) {
|
||||
Company company = new Company();
|
||||
if (i == 0)
|
||||
IBM = company;
|
||||
company.setName(COMPANY_NAMES[i]);
|
||||
company.setStartYear(START_YEARS[i]);
|
||||
em.persist(company);
|
||||
for (int j = 0; j < DEPARTMENT_NAMES.length; j++) {
|
||||
Department dept = new Department();
|
||||
dept.setName(DEPARTMENT_NAMES[j]);
|
||||
company.addDepartment(dept);
|
||||
em.persist(dept);
|
||||
for (int k = 0; k < EMPLOYEE_NAMES.length; k++) {
|
||||
Employee emp = new Employee();
|
||||
emp.setName(EMPLOYEE_NAMES[k]);
|
||||
Address addr = new Address();
|
||||
addr.setCity(CITY_NAMES[k]);
|
||||
em.persist(emp);
|
||||
em.persist(addr);
|
||||
emp.setAddress(addr);
|
||||
dept.addEmployees(emp);
|
||||
}
|
||||
}
|
||||
}
|
||||
em.getTransaction().commit();
|
||||
}
|
||||
|
||||
public void tearDown() throws Exception {
|
||||
|
@ -323,56 +359,60 @@ public class TestPreparedQueryCache extends SingleEMFTestCase {
|
|||
public void testQueryWithNoParameter() {
|
||||
String jpql = "select p from Company p";
|
||||
Object[] params = null;
|
||||
compare(!IS_NAMED_QUERY, jpql, BIND_DIFFERENT_PARM_VALUES, params);
|
||||
compare(!IS_NAMED_QUERY, jpql, COMPANY_NAMES.length, params);
|
||||
}
|
||||
|
||||
public void testQueryWithLiteral() {
|
||||
String jpql = "select p from Company p where p.name = 'PObject'";
|
||||
String jpql = "select p from Company p where p.name = " + literal(COMPANY_NAMES[0]);
|
||||
Object[] params = null;
|
||||
compare(!IS_NAMED_QUERY, jpql, BIND_DIFFERENT_PARM_VALUES, params);
|
||||
compare(!IS_NAMED_QUERY, jpql, 1, 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);
|
||||
Object[] params = {"param", COMPANY_NAMES[0]};
|
||||
compare(!IS_NAMED_QUERY, jpql, 1, 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);
|
||||
String jpql = "select e from Employee e " + "where e.name = :emp"
|
||||
+ " and e.department.name = :dept"
|
||||
+ " and e.department.company.name LIKE " + literal(COMPANY_NAMES[0])
|
||||
+ " and e.address.city = :city";
|
||||
Object[] params = { "emp", EMPLOYEE_NAMES[0],
|
||||
"dept", DEPARTMENT_NAMES[0],
|
||||
"city", CITY_NAMES[0]};
|
||||
compare(!IS_NAMED_QUERY, jpql, 1, params);
|
||||
}
|
||||
|
||||
public void testNamedQueryWithNoParameter() {
|
||||
String namedQuery = "Company.PreparedQueryWithNoParameter";
|
||||
Object[] params = null;
|
||||
compare(IS_NAMED_QUERY, namedQuery, BIND_DIFFERENT_PARM_VALUES, params);
|
||||
compare(IS_NAMED_QUERY, namedQuery, COMPANY_NAMES.length, params);
|
||||
}
|
||||
|
||||
public void testNamedQueryWithLiteral() {
|
||||
String namedQuery = "Company.PreparedQueryWithLiteral";
|
||||
Object[] params = null;
|
||||
compare(IS_NAMED_QUERY, namedQuery, BIND_DIFFERENT_PARM_VALUES, params);
|
||||
compare(IS_NAMED_QUERY, namedQuery, 1, params);
|
||||
}
|
||||
|
||||
public void testNamedQueryWithPositionalParameter() {
|
||||
String namedQuery = "Company.PreparedQueryWithPositionalParameter";
|
||||
Object[] params = {1, "x", 2, 1960};
|
||||
compare(IS_NAMED_QUERY, namedQuery, BIND_DIFFERENT_PARM_VALUES, params);
|
||||
Object[] params = {1, COMPANY_NAMES[0], 2, START_YEARS[0]};
|
||||
compare(IS_NAMED_QUERY, namedQuery, 1, params);
|
||||
}
|
||||
|
||||
public void testNamedQueryWithNamedParameter() {
|
||||
String namedQuery = "Company.PreparedQueryWithNamedParameter";
|
||||
Object[] params = {"name", "x", "startYear", 1960};
|
||||
compare(IS_NAMED_QUERY, namedQuery, BIND_DIFFERENT_PARM_VALUES, params);
|
||||
Object[] params = {"name", COMPANY_NAMES[0], "startYear", START_YEARS[0]};
|
||||
compare(IS_NAMED_QUERY, namedQuery, 1, params);
|
||||
}
|
||||
|
||||
public void testPersistenceCapableParameter() {
|
||||
String jpql = "select e from Employee e where e.department.company=:company";
|
||||
Object[] params = {"company", IBM};
|
||||
compare(!IS_NAMED_QUERY, jpql, EMPLOYEE_NAMES.length*DEPARTMENT_NAMES.length, params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -380,36 +420,36 @@ public class TestPreparedQueryCache extends SingleEMFTestCase {
|
|||
* Prepared Query Cache.
|
||||
*
|
||||
*/
|
||||
void compare(boolean isNamed, String jpql, boolean append, Object... params) {
|
||||
void compare(boolean isNamed, String jpql, int expectedCount, Object... params) {
|
||||
String realJPQL = isNamed ? getJPQL(jpql) : jpql;
|
||||
// run the query once for warming up
|
||||
run(jpql, params, !USE_CACHE, 1, isNamed, append);
|
||||
run(jpql, params, !USE_CACHE, 1, isNamed, expectedCount);
|
||||
|
||||
// run N times without cache
|
||||
long without = run(jpql, params, !USE_CACHE, SAMPLE_SIZE, isNamed, append);
|
||||
long without = run(jpql, params, !USE_CACHE, SAMPLE_SIZE, isNamed, expectedCount);
|
||||
assertNotCached(realJPQL);
|
||||
|
||||
// run N times with cache
|
||||
long with = run(jpql, params, USE_CACHE, SAMPLE_SIZE, isNamed, append);
|
||||
long with = run(jpql, params, USE_CACHE, SAMPLE_SIZE, isNamed, expectedCount);
|
||||
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
|
||||
log("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);
|
||||
log("JPQL: " + realJPQL);
|
||||
log("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." +
|
||||
log("*** WARN: Perforamce regression with cache." +
|
||||
" Execution time degrades by " + delta + "%");
|
||||
} else {
|
||||
System.err.println("change in execution time = +" + delta + "%");
|
||||
log("change in execution time = +" + delta + "%");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -421,12 +461,14 @@ public class TestPreparedQueryCache extends SingleEMFTestCase {
|
|||
* 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());
|
||||
boolean isNamedQuery, int expectedCount) {
|
||||
trace("Executing " + N + " times " + (useCache ? " with " : "without") + " cache");
|
||||
List<Long> stats = new ArrayList<Long>();
|
||||
sql.clear();
|
||||
for (int i = 0; i < N; i++) {
|
||||
OpenJPAEntityManager em = emf.createEntityManager();
|
||||
((OpenJPAEntityManagerSPI)em).setQuerySQLCache(useCache);
|
||||
assertEquals(useCache, ((OpenJPAEntityManagerSPI)em).getQuerySQLCache());
|
||||
long start = System.nanoTime();
|
||||
OpenJPAQuery q = isNamedQuery
|
||||
? em.createNamedQuery(jpql) : em.createQuery(jpql);
|
||||
|
@ -438,15 +480,26 @@ public class TestPreparedQueryCache extends SingleEMFTestCase {
|
|||
else if (key instanceof String)
|
||||
q.setParameter(key.toString(), val);
|
||||
else
|
||||
throw new RuntimeException("key " + key + " is neither Number nor String");
|
||||
fail("key " + key + " is neither Number nor String");
|
||||
}
|
||||
q.getResultList();
|
||||
List list = q.getResultList();
|
||||
walk(list);
|
||||
int actual = list.size();
|
||||
q.closeAll();
|
||||
assertEquals(expectedCount, actual);
|
||||
long end = System.nanoTime();
|
||||
stats.add(end - start);
|
||||
em.close();
|
||||
}
|
||||
em.close();
|
||||
Collections.sort(stats);
|
||||
if (useCache) {
|
||||
String cacheKey = isNamedQuery ? getJPQL(jpql) : jpql;
|
||||
long total = getCache().getStatistics().getExecutionCount(cacheKey);
|
||||
long hits = getCache().getStatistics().getHitCount(cacheKey);
|
||||
assertEquals(N, total);
|
||||
assertEquals(N-1, hits);
|
||||
}
|
||||
assertEquals(N, sql.size());
|
||||
Collections.sort(stats);
|
||||
return stats.get(N/2);
|
||||
}
|
||||
|
||||
|
@ -468,28 +521,23 @@ public class TestPreparedQueryCache extends SingleEMFTestCase {
|
|||
.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");
|
||||
|
||||
String literal(String s) {
|
||||
return "'"+s+"'";
|
||||
}
|
||||
|
||||
void log(String s) {
|
||||
System.err.println(s);
|
||||
}
|
||||
|
||||
void trace(String s) {
|
||||
if (Boolean.getBoolean("trace"))
|
||||
System.err.println(s);
|
||||
}
|
||||
|
||||
void walk(List list) {
|
||||
Iterator i = list.iterator();
|
||||
while (i.hasNext())
|
||||
i.next();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -265,7 +265,7 @@ public class QueryImpl implements OpenJPAQuerySPI, Serializable {
|
|||
QueryStatistics stats = cache.getStatistics();
|
||||
if (alreadyCached && LANG_PREPARED_SQL.equals(lang)) {
|
||||
PreparedQuery pq = _em.getPreparedQuery(_id);
|
||||
params = pq.reparametrize(params);
|
||||
params = pq.reparametrize(params, _em.getBroker());
|
||||
stats.recordExecution(pq.getOriginalQuery(), alreadyCached);
|
||||
} else {
|
||||
stats.recordExecution(_query.getQueryString(), alreadyCached);
|
||||
|
|
Loading…
Reference in New Issue