mirror of https://github.com/apache/openjpa.git
OPENJPA-924: Support cache of Finder queries.
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@745293 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
556e09b69f
commit
3fc7f54a01
|
@ -40,6 +40,8 @@ import org.apache.openjpa.jdbc.sql.DBDictionary;
|
||||||
import org.apache.openjpa.jdbc.sql.DBDictionaryFactory;
|
import org.apache.openjpa.jdbc.sql.DBDictionaryFactory;
|
||||||
import org.apache.openjpa.jdbc.sql.SQLFactory;
|
import org.apache.openjpa.jdbc.sql.SQLFactory;
|
||||||
import org.apache.openjpa.kernel.BrokerImpl;
|
import org.apache.openjpa.kernel.BrokerImpl;
|
||||||
|
import org.apache.openjpa.kernel.FinderCache;
|
||||||
|
import org.apache.openjpa.kernel.PreparedQueryCache;
|
||||||
import org.apache.openjpa.kernel.StoreContext;
|
import org.apache.openjpa.kernel.StoreContext;
|
||||||
import org.apache.openjpa.lib.conf.IntValue;
|
import org.apache.openjpa.lib.conf.IntValue;
|
||||||
import org.apache.openjpa.lib.conf.ObjectValue;
|
import org.apache.openjpa.lib.conf.ObjectValue;
|
||||||
|
@ -317,6 +319,17 @@ public class JDBCConfigurationImpl
|
||||||
preparedQueryCachePlugin.setDynamic(true);
|
preparedQueryCachePlugin.setDynamic(true);
|
||||||
preparedQueryCachePlugin.setInstantiatingGetter("getQuerySQLCacheInstance");
|
preparedQueryCachePlugin.setInstantiatingGetter("getQuerySQLCacheInstance");
|
||||||
|
|
||||||
|
finderCachePlugin = addPlugin("jdbc.FinderCache", true);
|
||||||
|
aliases = new String[] {
|
||||||
|
"true", "org.apache.openjpa.jdbc.kernel.FinderCacheImpl",
|
||||||
|
"false", null
|
||||||
|
};
|
||||||
|
finderCachePlugin.setAliases(aliases);
|
||||||
|
finderCachePlugin.setAliasListComprehensive(true);
|
||||||
|
finderCachePlugin.setDefault(aliases[0]);
|
||||||
|
finderCachePlugin.setClassName(aliases[1]);
|
||||||
|
finderCachePlugin.setDynamic(true);
|
||||||
|
finderCachePlugin.setInstantiatingGetter("getFinderCacheInstance");
|
||||||
|
|
||||||
// this static initializer is to get past a weird
|
// this static initializer is to get past a weird
|
||||||
// ClassCircularityError that happens only under IBM's
|
// ClassCircularityError that happens only under IBM's
|
||||||
|
|
|
@ -0,0 +1,418 @@
|
||||||
|
/*
|
||||||
|
* 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.jdbc.kernel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.openjpa.conf.OpenJPAConfiguration;
|
||||||
|
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
||||||
|
import org.apache.openjpa.jdbc.sql.Result;
|
||||||
|
import org.apache.openjpa.jdbc.sql.SelectExecutor;
|
||||||
|
import org.apache.openjpa.kernel.FetchConfiguration;
|
||||||
|
import org.apache.openjpa.kernel.FinderCache;
|
||||||
|
import org.apache.openjpa.kernel.FinderQuery;
|
||||||
|
import org.apache.openjpa.kernel.QueryHints;
|
||||||
|
import org.apache.openjpa.kernel.QueryStatistics;
|
||||||
|
import org.apache.openjpa.lib.conf.Configuration;
|
||||||
|
import org.apache.openjpa.lib.log.Log;
|
||||||
|
import org.apache.openjpa.lib.util.Localizer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of FinderCache for JDBC.
|
||||||
|
*
|
||||||
|
* @author Pinaki Poddar
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class FinderCacheImpl
|
||||||
|
implements FinderCache<ClassMapping, SelectExecutor, Result> {
|
||||||
|
private static final String PATTERN_SEPARATOR = "\\;";
|
||||||
|
private static final String EXLUDED_BY_USER = "Excluded by user";
|
||||||
|
|
||||||
|
private final Map<ClassMapping,
|
||||||
|
FinderQuery<ClassMapping, SelectExecutor, Result>> _delegate;
|
||||||
|
// Key: class name Value: Reason why excluded
|
||||||
|
private final Map<String, String> _uncachables;
|
||||||
|
private List<String> _exclusionPatterns;
|
||||||
|
private QueryStatistics<FinderQuery<ClassMapping,SelectExecutor,Result>>
|
||||||
|
_stats;
|
||||||
|
private ReentrantLock _lock = new ReentrantLock();
|
||||||
|
private Log _log;
|
||||||
|
private Localizer _loc = Localizer.forPackage(FinderCacheImpl.class);
|
||||||
|
|
||||||
|
|
||||||
|
public FinderCacheImpl() {
|
||||||
|
_delegate = new HashMap<ClassMapping,
|
||||||
|
FinderQuery<ClassMapping, SelectExecutor, Result>>();
|
||||||
|
_uncachables = new HashMap<String, String>();
|
||||||
|
_stats = new QueryStatistics.Default<FinderQuery<ClassMapping,
|
||||||
|
SelectExecutor,Result>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a map-oriented view of the cache.
|
||||||
|
*
|
||||||
|
* @return a map of the query string with class names as key.
|
||||||
|
*/
|
||||||
|
public Map<String, String> getMapView() {
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
Map<String, String> view = new TreeMap<String, String>();
|
||||||
|
for (ClassMapping mapping : _delegate.keySet())
|
||||||
|
view.put(mapping.getDescribedType().getName(),
|
||||||
|
_delegate.get(mapping).getQueryString());
|
||||||
|
return view;
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets basic statistics of execution and hit count of finder queries.
|
||||||
|
*/
|
||||||
|
public QueryStatistics<FinderQuery<ClassMapping,SelectExecutor,Result>>
|
||||||
|
getStatistics() {
|
||||||
|
return _stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the finder query for the given mapping. The get operation can be
|
||||||
|
* controlled by FetchConfiguration hints.
|
||||||
|
* {@link QueryHints#HINT_IGNORE_FINDER HINT_IGNORE_FINDER} will ignore
|
||||||
|
* any cached finder that may exist in this cache and will return null.
|
||||||
|
* {@link QueryHints#HINT_INVALIDATE_FINDER HINT_INVALIDATE_FINDER} will
|
||||||
|
* invalidate any cached finder that may exist in this cache and will return
|
||||||
|
* null.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public FinderQuery<ClassMapping,SelectExecutor,Result>
|
||||||
|
get(ClassMapping mapping, FetchConfiguration fetch) {
|
||||||
|
boolean ignore = isHinted(fetch, QueryHints.HINT_IGNORE_FINDER);
|
||||||
|
boolean invalidate = isHinted(fetch, QueryHints.HINT_INVALIDATE_FINDER);
|
||||||
|
if (invalidate)
|
||||||
|
invalidate(mapping);
|
||||||
|
if (ignore)
|
||||||
|
return null;
|
||||||
|
FinderQuery<ClassMapping, SelectExecutor, Result> result =
|
||||||
|
_delegate.get(mapping);
|
||||||
|
_stats.recordExecution(result, result != null);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache a Finder Query for the given mapping and select. The put operation
|
||||||
|
* can be controlled by FetchConfiguration hints.
|
||||||
|
* If no entry exists for the given mapping then an attempt is made to
|
||||||
|
* create a new FinderQuery. The attempt, however, may not be successful
|
||||||
|
* because all Selects can not be cached.
|
||||||
|
* @see FinderQueryImpl#newFinder(ClassMapping, Select).
|
||||||
|
*
|
||||||
|
* If a entry for the given mapping exists then the value of
|
||||||
|
* {@link QueryHints#HINT_RECACHE_FINDER HINT_RECACHE_FINDER} hint
|
||||||
|
* determines whether the existing entry is returned or a new FinderQuery
|
||||||
|
* with the given argument overwrites the existing one.
|
||||||
|
*
|
||||||
|
* @param mapping the class for which the finder is to be cached
|
||||||
|
* @param select the finder query
|
||||||
|
* @param fetch may contain hints to control cache operation
|
||||||
|
*/
|
||||||
|
public FinderQuery<ClassMapping, SelectExecutor, Result> cache
|
||||||
|
(ClassMapping mapping, SelectExecutor select, FetchConfiguration fetch) {
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
boolean recache = isHinted(fetch, QueryHints.HINT_RECACHE_FINDER);
|
||||||
|
if (isExcluded(mapping)) {
|
||||||
|
return recache ? put(mapping, select) : null;
|
||||||
|
}
|
||||||
|
if (_delegate.containsKey(mapping)) {
|
||||||
|
return recache ? put(mapping, select) : _delegate.get(mapping);
|
||||||
|
}
|
||||||
|
return put(mapping, select);
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and puts a FinderQuery in the internal map indexed by the
|
||||||
|
* given ClassMapping.
|
||||||
|
* If a new FinderQuery can not be created for the given Select (because
|
||||||
|
* some Select are not cached), then the mapping is marked invalid.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private FinderQuery<ClassMapping, SelectExecutor, Result> put
|
||||||
|
(ClassMapping mapping, SelectExecutor select) {
|
||||||
|
FinderQuery<ClassMapping, SelectExecutor, Result> finder =
|
||||||
|
FinderQueryImpl.newFinder(mapping, select);
|
||||||
|
if (finder != null) {
|
||||||
|
_delegate.put(mapping, finder);
|
||||||
|
if (_log != null && _log.isTraceEnabled())
|
||||||
|
_log.trace(_loc.get("finder-cached", finder));
|
||||||
|
} else {
|
||||||
|
if (_log != null && _log.isWarnEnabled())
|
||||||
|
_log.warn(_loc.get("finder-not-cachable", mapping));
|
||||||
|
invalidate(mapping);
|
||||||
|
}
|
||||||
|
return finder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affirms if the given mapping is excluded from being cached.
|
||||||
|
*/
|
||||||
|
public boolean isExcluded(ClassMapping mapping) {
|
||||||
|
return mapping != null &&
|
||||||
|
isExcluded(mapping.getDescribedType().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the exclusion patterns to find out if the given string matches
|
||||||
|
* any element.
|
||||||
|
*/
|
||||||
|
private boolean isExcluded(String target) {
|
||||||
|
if (_exclusionPatterns != null && _exclusionPatterns.contains(target))
|
||||||
|
return true;
|
||||||
|
return getMatchedExclusionPattern(target) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a pattern for exclusion. Any cached finder whose class name
|
||||||
|
* matches the given pattern will be marked invalidated as a side-effect.
|
||||||
|
*/
|
||||||
|
public void addExclusionPattern(String pattern) {
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
if (_exclusionPatterns == null)
|
||||||
|
_exclusionPatterns = new ArrayList<String>();
|
||||||
|
_exclusionPatterns.add(pattern);
|
||||||
|
Collection<ClassMapping> invalidMappings = getMatchedKeys(pattern,
|
||||||
|
_delegate.keySet());
|
||||||
|
if (!invalidMappings.isEmpty()
|
||||||
|
&& _log != null && _log.isInfoEnabled())
|
||||||
|
_log.info(_loc.get("finder-add-pattern", pattern,
|
||||||
|
invalidMappings.size(), invalidMappings));
|
||||||
|
for (ClassMapping invalidMapping : invalidMappings)
|
||||||
|
markUncachable(invalidMapping, pattern);
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Removes a pattern for exclusion. Any query identifier marked as not
|
||||||
|
* cachable due to the given pattern will now be removed from the list of
|
||||||
|
* uncachables as a side-effect.
|
||||||
|
*/
|
||||||
|
public void removeExclusionPattern(String pattern) {
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
if (_exclusionPatterns == null)
|
||||||
|
return;
|
||||||
|
_exclusionPatterns.remove(pattern);
|
||||||
|
Collection<String> reborns = getMatchedKeys(pattern,
|
||||||
|
_uncachables.keySet());
|
||||||
|
if (!reborns.isEmpty() && _log != null && _log.isInfoEnabled())
|
||||||
|
_log.info(_loc.get("finder-remove-pattern", pattern,
|
||||||
|
reborns.size(), reborns));
|
||||||
|
for (String rebornKey : reborns)
|
||||||
|
_uncachables.remove(rebornKey);
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the pattern that matches the given identifier.
|
||||||
|
*/
|
||||||
|
private String getMatchedExclusionPattern(String id) {
|
||||||
|
if (_exclusionPatterns == null || _exclusionPatterns.isEmpty())
|
||||||
|
return null;
|
||||||
|
for (String pattern : _exclusionPatterns)
|
||||||
|
if (matches(pattern, id))
|
||||||
|
return pattern;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the elements of the given set that match the given pattern.
|
||||||
|
*/
|
||||||
|
private Collection<ClassMapping> getMatchedKeys(String pattern,
|
||||||
|
Set<ClassMapping> set) {
|
||||||
|
List<ClassMapping> result = new ArrayList<ClassMapping>();
|
||||||
|
for (ClassMapping entry : set) {
|
||||||
|
if (matches(pattern, entry)) {
|
||||||
|
result.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the elements of the given list which match the given pattern.
|
||||||
|
*/
|
||||||
|
private Collection<String> getMatchedKeys(String pattern,
|
||||||
|
Collection<String> coll) {
|
||||||
|
List<String> result = new ArrayList<String>();
|
||||||
|
for (String key : coll) {
|
||||||
|
if (matches(pattern, key)) {
|
||||||
|
result.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean matches(String pattern, ClassMapping mapping) {
|
||||||
|
return matches(pattern, mapping.getDescribedType().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean matches(String pattern, String target) {
|
||||||
|
return target != null && (target.equals(pattern)
|
||||||
|
|| target.matches(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean invalidate(ClassMapping mapping) {
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
if (_log.isTraceEnabled())
|
||||||
|
_log.trace(_loc.get("finder-invalidate", mapping));
|
||||||
|
return _delegate.remove(mapping) != null;
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FinderQuery<ClassMapping, SelectExecutor, Result> markUncachable(
|
||||||
|
ClassMapping mapping) {
|
||||||
|
return markUncachable(mapping.getDescribedType().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public FinderQuery<ClassMapping, SelectExecutor, Result> markUncachable(
|
||||||
|
String id) {
|
||||||
|
return markUncachable(id, EXLUDED_BY_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FinderQuery<ClassMapping, SelectExecutor, Result> markUncachable(
|
||||||
|
String cls, String reason) {
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
boolean excludedByUser = _uncachables.get(cls) == EXLUDED_BY_USER;
|
||||||
|
if (!excludedByUser)
|
||||||
|
_uncachables.put(cls, reason);
|
||||||
|
if (_log != null && _log.isInfoEnabled()) {
|
||||||
|
if (excludedByUser)
|
||||||
|
_log.info(_loc.get("finder-uncache-strong", cls));
|
||||||
|
else
|
||||||
|
_log.info(_loc.get("finder-uncache-weak", cls,
|
||||||
|
reason));
|
||||||
|
}
|
||||||
|
return _delegate.remove(searchMappingByName(cls));
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FinderQuery<ClassMapping, SelectExecutor, Result> markUncachable(
|
||||||
|
ClassMapping mapping, String reason) {
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
String cls = mapping.getDescribedType().getName();
|
||||||
|
boolean excludedByUser = _uncachables.get(cls) == EXLUDED_BY_USER;
|
||||||
|
if (!excludedByUser)
|
||||||
|
_uncachables.put(cls, reason);
|
||||||
|
if (_log != null && _log.isInfoEnabled()) {
|
||||||
|
if (excludedByUser)
|
||||||
|
_log.info(_loc.get("finder-uncache-strong", cls));
|
||||||
|
else
|
||||||
|
_log.info(_loc.get("finder-uncache-weak", cls, reason));
|
||||||
|
}
|
||||||
|
return _delegate.remove(mapping);
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassMapping searchMappingByName(String cls) {
|
||||||
|
for (ClassMapping mapping : _delegate.keySet())
|
||||||
|
if (matches(cls, mapping))
|
||||||
|
return mapping;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setExcludes(String excludes) {
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
if (StringUtils.isEmpty(excludes))
|
||||||
|
return;
|
||||||
|
if (_exclusionPatterns == null)
|
||||||
|
_exclusionPatterns = new ArrayList<String>();
|
||||||
|
String[] patterns = excludes.split(PATTERN_SEPARATOR);
|
||||||
|
for (String pattern : patterns)
|
||||||
|
addExclusionPattern(pattern);
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<String> getExcludes() {
|
||||||
|
return (List<String>)_exclusionPatterns == null
|
||||||
|
? Collections.EMPTY_LIST
|
||||||
|
: Collections.unmodifiableList(_exclusionPatterns);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isHinted(FetchConfiguration fetch, String hint) {
|
||||||
|
if (fetch == null)
|
||||||
|
return false;
|
||||||
|
Object result = fetch.getHint(hint);
|
||||||
|
return result != null && "true".equalsIgnoreCase(result.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void lock() {
|
||||||
|
if (_lock != null)
|
||||||
|
_lock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() {
|
||||||
|
if (_lock != null && _lock.isLocked())
|
||||||
|
_lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------
|
||||||
|
// Configuration contract
|
||||||
|
// ----------------------------------------------------
|
||||||
|
public void startConfiguration() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfiguration(Configuration conf) {
|
||||||
|
_log = conf.getLog(OpenJPAConfiguration.LOG_RUNTIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endConfiguration() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* 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.jdbc.kernel;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
||||||
|
import org.apache.openjpa.jdbc.meta.Joinable;
|
||||||
|
import org.apache.openjpa.jdbc.schema.Column;
|
||||||
|
import org.apache.openjpa.jdbc.sql.LogicalUnion;
|
||||||
|
import org.apache.openjpa.jdbc.sql.Result;
|
||||||
|
import org.apache.openjpa.jdbc.sql.SQLBuffer;
|
||||||
|
import org.apache.openjpa.jdbc.sql.SelectExecutor;
|
||||||
|
import org.apache.openjpa.jdbc.sql.SelectImpl;
|
||||||
|
import org.apache.openjpa.jdbc.sql.Union;
|
||||||
|
import org.apache.openjpa.kernel.FetchConfiguration;
|
||||||
|
import org.apache.openjpa.kernel.FinderQuery;
|
||||||
|
import org.apache.openjpa.kernel.OpenJPAStateManager;
|
||||||
|
import org.apache.openjpa.kernel.StoreManager;
|
||||||
|
import org.apache.openjpa.util.ApplicationIds;
|
||||||
|
import org.apache.openjpa.util.Id;
|
||||||
|
|
||||||
|
import serp.util.Numbers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements Finder Query identified by ClassMappping for SelectExecutor that
|
||||||
|
* can be executed to generate Result.
|
||||||
|
*
|
||||||
|
* @author Pinaki Poddar
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class FinderQueryImpl
|
||||||
|
implements FinderQuery<ClassMapping, SelectExecutor, Result> {
|
||||||
|
private final ClassMapping _mapping;
|
||||||
|
private final SelectImpl _select;
|
||||||
|
private final Column[] _pkCols;
|
||||||
|
private final Joinable[] _joins;
|
||||||
|
private final SQLBuffer _buffer;
|
||||||
|
private final String _sql;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to construct a FinderQuery from the given Select for the given
|
||||||
|
* mapping. The given Select may not be amenable for caching and then a null
|
||||||
|
* value is returned.
|
||||||
|
*/
|
||||||
|
static FinderQueryImpl newFinder(ClassMapping mapping,
|
||||||
|
SelectExecutor select) {
|
||||||
|
SelectImpl impl = extractImplementation(select);
|
||||||
|
if (impl == null)
|
||||||
|
return null;
|
||||||
|
SQLBuffer buffer = impl.getSQL();
|
||||||
|
Column[] pkCols = mapping.getPrimaryKeyColumns();
|
||||||
|
boolean canCache = pkCols.length == buffer.getParameters().size();
|
||||||
|
return (canCache)
|
||||||
|
? new FinderQueryImpl(mapping, impl, buffer) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FinderQueryImpl(ClassMapping mapping, SelectImpl select,
|
||||||
|
SQLBuffer buffer) {
|
||||||
|
super();
|
||||||
|
_mapping = mapping;
|
||||||
|
_select = select;
|
||||||
|
_buffer = buffer;
|
||||||
|
_sql = _buffer.getSQL();
|
||||||
|
_pkCols = _mapping.getPrimaryKeyColumns();
|
||||||
|
_joins = new Joinable[_pkCols.length];
|
||||||
|
for (int i = 0; i < _pkCols.length; i++)
|
||||||
|
_joins[i] = _mapping.assertJoinable(_pkCols[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassMapping getIdentifier() {
|
||||||
|
return _mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectExecutor getDelegate() {
|
||||||
|
return _select;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQueryString() {
|
||||||
|
return _sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object[] getPKValues(OpenJPAStateManager sm, JDBCStore store) {
|
||||||
|
Object[] pks = null;
|
||||||
|
Object oid = sm.getObjectId();
|
||||||
|
if (_mapping.getIdentityType() == ClassMapping.ID_APPLICATION)
|
||||||
|
pks = ApplicationIds.toPKValues(oid, _mapping);
|
||||||
|
|
||||||
|
Object[] val = new Object[_pkCols.length];
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < _pkCols.length; i++, count++) {
|
||||||
|
if (pks == null)
|
||||||
|
val[0] = (oid == null)
|
||||||
|
? null
|
||||||
|
: Numbers.valueOf(((Id) oid).getId());
|
||||||
|
else {
|
||||||
|
val[i] = pks[_mapping.getField(_joins[i].getFieldIndex()).
|
||||||
|
getPrimaryKeyIndex()];
|
||||||
|
val[i] = _joins[i].getJoinValue(val[i], _pkCols[i], store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result execute(OpenJPAStateManager sm, StoreManager store,
|
||||||
|
FetchConfiguration fetch) {
|
||||||
|
boolean forUpdate = false;
|
||||||
|
Connection conn = ((JDBCStore)store).getConnection();
|
||||||
|
PreparedStatement stmnt = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
stmnt = conn.prepareStatement(_sql);
|
||||||
|
Object[] params = getPKValues(sm, (JDBCStore)store);
|
||||||
|
int i = 0;
|
||||||
|
for (Object o : params) {
|
||||||
|
stmnt.setObject(++i, o);
|
||||||
|
}
|
||||||
|
rs = stmnt.executeQuery();
|
||||||
|
return _select.getEagerResult(conn, stmnt, rs, (JDBCStore)store,
|
||||||
|
(JDBCFetchConfiguration)fetch, forUpdate, _buffer);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
if (stmnt != null)
|
||||||
|
try { stmnt.close(); } catch (SQLException se2) {}
|
||||||
|
try { conn.close(); } catch (SQLException se2) {}
|
||||||
|
throw new RuntimeException(se);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SelectImpl extractImplementation(SelectExecutor selector) {
|
||||||
|
if (selector == null || selector.hasMultipleSelects())
|
||||||
|
return null;
|
||||||
|
if (selector instanceof SelectImpl)
|
||||||
|
return (SelectImpl)selector;
|
||||||
|
if (selector instanceof LogicalUnion.UnionSelect)
|
||||||
|
return ((LogicalUnion.UnionSelect)selector).getDelegate();
|
||||||
|
if (selector instanceof Union)
|
||||||
|
return extractImplementation(((Union)selector).getSelects()[0]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return _mapping + ": [" + getQueryString() + "]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.apache.openjpa.enhance.PersistenceCapable;
|
import org.apache.openjpa.enhance.PersistenceCapable;
|
||||||
|
@ -48,7 +49,10 @@ import org.apache.openjpa.jdbc.sql.SQLFactory;
|
||||||
import org.apache.openjpa.jdbc.sql.Select;
|
import org.apache.openjpa.jdbc.sql.Select;
|
||||||
import org.apache.openjpa.jdbc.sql.SelectExecutor;
|
import org.apache.openjpa.jdbc.sql.SelectExecutor;
|
||||||
import org.apache.openjpa.jdbc.sql.Union;
|
import org.apache.openjpa.jdbc.sql.Union;
|
||||||
|
import org.apache.openjpa.kernel.BrokerImpl;
|
||||||
import org.apache.openjpa.kernel.FetchConfiguration;
|
import org.apache.openjpa.kernel.FetchConfiguration;
|
||||||
|
import org.apache.openjpa.kernel.FinderCache;
|
||||||
|
import org.apache.openjpa.kernel.FinderQuery;
|
||||||
import org.apache.openjpa.kernel.LockManager;
|
import org.apache.openjpa.kernel.LockManager;
|
||||||
import org.apache.openjpa.kernel.OpenJPAStateManager;
|
import org.apache.openjpa.kernel.OpenJPAStateManager;
|
||||||
import org.apache.openjpa.kernel.PCState;
|
import org.apache.openjpa.kernel.PCState;
|
||||||
|
@ -483,13 +487,18 @@ public class JDBCStoreManager
|
||||||
private Result getInitializeStateResult(OpenJPAStateManager sm,
|
private Result getInitializeStateResult(OpenJPAStateManager sm,
|
||||||
ClassMapping mapping, JDBCFetchConfiguration fetch, int subs)
|
ClassMapping mapping, JDBCFetchConfiguration fetch, int subs)
|
||||||
throws SQLException {
|
throws SQLException {
|
||||||
|
FinderQueryImpl fq = getFinder(mapping, fetch);
|
||||||
|
if (fq != null)
|
||||||
|
return fq.execute(sm, this, fetch);
|
||||||
Select sel = _sql.newSelect();
|
Select sel = _sql.newSelect();
|
||||||
if (!select(sel, mapping, subs, sm, null, fetch,
|
if (!select(sel, mapping, subs, sm, null, fetch,
|
||||||
JDBCFetchConfiguration.EAGER_JOIN, true, false))
|
JDBCFetchConfiguration.EAGER_JOIN, true, false))
|
||||||
return null;
|
return null;
|
||||||
sel.wherePrimaryKey(sm.getObjectId(), mapping, this);
|
sel.wherePrimaryKey(sm.getObjectId(), mapping, this);
|
||||||
sel.setExpectedResultCount(1, false);
|
sel.setExpectedResultCount(1, false);
|
||||||
return sel.execute(this, fetch);
|
Result result = sel.execute(this, fetch);
|
||||||
|
cacheFinder(mapping, sel, fetch);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -499,6 +508,9 @@ public class JDBCStoreManager
|
||||||
private Result getInitializeStateUnionResult(final OpenJPAStateManager sm,
|
private Result getInitializeStateUnionResult(final OpenJPAStateManager sm,
|
||||||
ClassMapping mapping, final ClassMapping[] mappings,
|
ClassMapping mapping, final ClassMapping[] mappings,
|
||||||
final JDBCFetchConfiguration fetch) throws SQLException {
|
final JDBCFetchConfiguration fetch) throws SQLException {
|
||||||
|
FinderQueryImpl fq = getFinder(mapping, fetch);
|
||||||
|
if (fq != null)
|
||||||
|
return fq.execute(sm, this, fetch);
|
||||||
final JDBCStoreManager store = this;
|
final JDBCStoreManager store = this;
|
||||||
final int eager = Math.min(fetch.getEagerFetchMode(),
|
final int eager = Math.min(fetch.getEagerFetchMode(),
|
||||||
JDBCFetchConfiguration.EAGER_JOIN);
|
JDBCFetchConfiguration.EAGER_JOIN);
|
||||||
|
@ -514,7 +526,9 @@ public class JDBCStoreManager
|
||||||
sel.wherePrimaryKey(sm.getObjectId(), mappings[i], store);
|
sel.wherePrimaryKey(sm.getObjectId(), mappings[i], store);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return union.execute(this, fetch);
|
Result result = union.execute(this, fetch);
|
||||||
|
cacheFinder(mapping, union, fetch);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1350,6 +1364,24 @@ public class JDBCStoreManager
|
||||||
_stmnts.remove(stmnt);
|
_stmnts.remove(stmnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FinderQueryImpl getFinder(ClassMapping mapping, FetchConfiguration fetch) {
|
||||||
|
FinderCache cache = getFinderCache();
|
||||||
|
return cache == null
|
||||||
|
? null : (FinderQueryImpl)cache.get(mapping, fetch);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean cacheFinder(ClassMapping mapping, SelectExecutor select,
|
||||||
|
FetchConfiguration fetch) {
|
||||||
|
FinderCache cache = getFinderCache();
|
||||||
|
return cache != null && cache.cache(mapping, select, fetch) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FinderCache getFinderCache() {
|
||||||
|
return (((BrokerImpl)getContext()).getCacheFinderQuery())
|
||||||
|
? getConfiguration().getFinderCacheInstance() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection returned to client code. Makes sure its wrapped connection
|
* Connection returned to client code. Makes sure its wrapped connection
|
||||||
* ref count is decremented on finalize.
|
* ref count is decremented on finalize.
|
||||||
|
|
|
@ -199,10 +199,9 @@ public class LogicalUnion
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasMultipleSelects() {
|
public boolean hasMultipleSelects() {
|
||||||
for (UnionSelect sel : sels)
|
if (sels != null && sels.length > 1)
|
||||||
if (sel.hasMultipleSelects())
|
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return sels[0].hasMultipleSelects();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCount(JDBCStore store)
|
public int getCount(JDBCStore store)
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.apache.openjpa.kernel.BrokerFactory;
|
||||||
import org.apache.openjpa.kernel.BrokerImpl;
|
import org.apache.openjpa.kernel.BrokerImpl;
|
||||||
import org.apache.openjpa.kernel.ConnectionRetainModes;
|
import org.apache.openjpa.kernel.ConnectionRetainModes;
|
||||||
import org.apache.openjpa.kernel.FetchConfiguration;
|
import org.apache.openjpa.kernel.FetchConfiguration;
|
||||||
|
import org.apache.openjpa.kernel.FinderCache;
|
||||||
import org.apache.openjpa.kernel.InverseManager;
|
import org.apache.openjpa.kernel.InverseManager;
|
||||||
import org.apache.openjpa.kernel.LockManager;
|
import org.apache.openjpa.kernel.LockManager;
|
||||||
import org.apache.openjpa.kernel.PreparedQueryCache;
|
import org.apache.openjpa.kernel.PreparedQueryCache;
|
||||||
|
@ -1605,4 +1606,24 @@ public interface OpenJPAConfiguration
|
||||||
*/
|
*/
|
||||||
public void setQuerySQLCache(String config);
|
public void setQuerySQLCache(String config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cache of finder queries.
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public FinderCache getFinderCacheInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string configuration of the finder cache.
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public String getFinderCache();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the finder cache from a string configuration.
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public void setFinderCache(String cache);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.apache.openjpa.event.RemoteCommitProvider;
|
||||||
import org.apache.openjpa.kernel.AutoClear;
|
import org.apache.openjpa.kernel.AutoClear;
|
||||||
import org.apache.openjpa.kernel.BrokerImpl;
|
import org.apache.openjpa.kernel.BrokerImpl;
|
||||||
import org.apache.openjpa.kernel.ConnectionRetainModes;
|
import org.apache.openjpa.kernel.ConnectionRetainModes;
|
||||||
|
import org.apache.openjpa.kernel.FinderCache;
|
||||||
import org.apache.openjpa.kernel.InverseManager;
|
import org.apache.openjpa.kernel.InverseManager;
|
||||||
import org.apache.openjpa.kernel.LockLevels;
|
import org.apache.openjpa.kernel.LockLevels;
|
||||||
import org.apache.openjpa.kernel.LockManager;
|
import org.apache.openjpa.kernel.LockManager;
|
||||||
|
@ -148,6 +149,7 @@ public class OpenJPAConfigurationImpl
|
||||||
public CacheMarshallersValue cacheMarshallerPlugins;
|
public CacheMarshallersValue cacheMarshallerPlugins;
|
||||||
public BooleanValue eagerInitialization;
|
public BooleanValue eagerInitialization;
|
||||||
public PluginValue preparedQueryCachePlugin;
|
public PluginValue preparedQueryCachePlugin;
|
||||||
|
public PluginValue finderCachePlugin;
|
||||||
public ObjectValue specification;
|
public ObjectValue specification;
|
||||||
public IntValue queryTimeout;
|
public IntValue queryTimeout;
|
||||||
|
|
||||||
|
@ -1550,5 +1552,18 @@ public class OpenJPAConfigurationImpl
|
||||||
return (PreparedQueryCache)preparedQueryCachePlugin.get();
|
return (PreparedQueryCache)preparedQueryCachePlugin.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFinderCache(String finderCache) {
|
||||||
|
finderCachePlugin.setString(finderCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFinderCache() {
|
||||||
|
return finderCachePlugin.getString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FinderCache getFinderCacheInstance() {
|
||||||
|
if (finderCachePlugin.get() == null) {
|
||||||
|
finderCachePlugin.instantiate(FinderCache.class, this);
|
||||||
|
}
|
||||||
|
return (FinderCache)finderCachePlugin.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,6 +225,7 @@ public class BrokerImpl
|
||||||
private boolean _detachedNew = true;
|
private boolean _detachedNew = true;
|
||||||
private boolean _orderDirty = false;
|
private boolean _orderDirty = false;
|
||||||
private boolean _cachePreparedQuery = true;
|
private boolean _cachePreparedQuery = true;
|
||||||
|
private boolean _cacheFinderQuery = true;
|
||||||
|
|
||||||
|
|
||||||
// Map of properties whose values have been changed
|
// Map of properties whose values have been changed
|
||||||
|
@ -4890,4 +4891,23 @@ public class BrokerImpl
|
||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getCacheFinderQuery() {
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
return _cacheFinderQuery
|
||||||
|
&& _conf.getFinderCacheInstance() != null;
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCacheFinderQuery(boolean flag) {
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
_cachePreparedQuery = flag;
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
* 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.kernel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.openjpa.lib.conf.Configurable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cache to create and maintain {@link FinderQuery finder queries}.
|
||||||
|
*
|
||||||
|
* A finder query is a query to find instance of a given class by its primary
|
||||||
|
* identity. This cache maintains finder queries by generic identifier of
|
||||||
|
* parameterized type K.
|
||||||
|
*
|
||||||
|
* A cached query by an identifier of parameterized type K.
|
||||||
|
*
|
||||||
|
* A query can be cached by an identifier and value represented by parameterized
|
||||||
|
* type V. Caching results in creating a new instance of FinderQuery FQ from the
|
||||||
|
* value V such that FQ can be executed to return a result of parameterized type
|
||||||
|
* R. A request to cache may not be successful if this cache determines the
|
||||||
|
* value V to be not cachable.
|
||||||
|
*
|
||||||
|
* Both get() and put() operations can be controlled by the hints associated
|
||||||
|
* with FetchConfiguration.
|
||||||
|
*
|
||||||
|
* The target database query FQ associated to a cached finder query F
|
||||||
|
* <em>may</em> depend upon query execution context such as fetch plan or
|
||||||
|
* lock group. This cache, by design, does not monitor the context or
|
||||||
|
* automatically invalidate an entry when the original query F is executed
|
||||||
|
* with context parameters that affect the target query FQ.
|
||||||
|
*
|
||||||
|
* The user must notify this receiver to invalidate a cached entry when
|
||||||
|
* execution context changes in a way that will modify the resultant database
|
||||||
|
* language query FQ.
|
||||||
|
*
|
||||||
|
* One of the built-in mechanism (available in JPA facade) is to set query hints
|
||||||
|
* to either invalidate the query entirely or ignore the cached version for the
|
||||||
|
* current execution.
|
||||||
|
*
|
||||||
|
* @see QueryHints#HINT_IGNORE_FINDER
|
||||||
|
* @see QueryHints#HINT_INVALIDATE_FINDER
|
||||||
|
* @see QueryHints#HINT_RECACHE_FINDER
|
||||||
|
*
|
||||||
|
* This cache allows customization of whether a query can be cached or not
|
||||||
|
* via either explicit marking of certain classes as non-cachable (which is
|
||||||
|
* irreversible) or addition/removal of exclusion patterns (which is reversible)
|
||||||
|
*
|
||||||
|
* @author Pinaki Poddar
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public interface FinderCache<K,V,R> extends Configurable {
|
||||||
|
/**
|
||||||
|
* Get the FinderQuery for the given key.
|
||||||
|
*
|
||||||
|
* @param key for which the finder is looked up
|
||||||
|
* @param fecth may contain hints to control lookup operation
|
||||||
|
*
|
||||||
|
* @return FinderQuery for the given mapping.
|
||||||
|
*/
|
||||||
|
public FinderQuery<K,V,R> get(K key, FetchConfiguration fetch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache a FinderQuery for the given key and value.
|
||||||
|
*
|
||||||
|
* @param key for which the finder is cached.
|
||||||
|
* @param value used to construct the finder query
|
||||||
|
* @param fetch may contain hints to control cache operation.
|
||||||
|
*
|
||||||
|
* @return the finder query that has been cached. It may be newly
|
||||||
|
* constructed or an existing query. If the given key-value can not be
|
||||||
|
* cached, then return null.
|
||||||
|
*/
|
||||||
|
public FinderQuery<K,V,R> cache(K key, V value, FetchConfiguration fetch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a map view of the cached entries as strings.
|
||||||
|
*/
|
||||||
|
public Map<String, String> getMapView();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the FinderQuery for the given key from this cache.
|
||||||
|
*/
|
||||||
|
public boolean invalidate(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the given key as not amenable to caching.
|
||||||
|
* Explicit marking helps to avoid repeated computational cost of
|
||||||
|
* determining whether finder for a key can be cached or not.
|
||||||
|
*
|
||||||
|
* Explicit marking can not be reversed by removal of exclusion patterns.
|
||||||
|
*
|
||||||
|
* @return finder query for the given class if it had been cached before.
|
||||||
|
* null otherwise.
|
||||||
|
*/
|
||||||
|
public FinderQuery<K,V,R> markUncachable(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affirms if the given key matches any of the exclusion patterns.
|
||||||
|
*/
|
||||||
|
public boolean isExcluded(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the excluded stringified keys.
|
||||||
|
*/
|
||||||
|
public List<String> getExcludes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given pattern to the list of excluded patterns. Any existing
|
||||||
|
* cache entry whose key matches the given pattern will be marked
|
||||||
|
* non-cachable in a reversible manner.
|
||||||
|
*/
|
||||||
|
public void addExclusionPattern(String pattern);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given pattern from the list of excluded patterns.
|
||||||
|
* Any excluded entry that matches the given pattern can now be cached
|
||||||
|
* again, unless it has been marked non-cachable explicitly.
|
||||||
|
*/
|
||||||
|
public void removeExclusionPattern(String pattern);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the simple statistics for executed finder queries.
|
||||||
|
*/
|
||||||
|
public QueryStatistics<FinderQuery<K,V,R>> getStatistics();
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* 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.kernel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A finder query is a query for an instance of a class by its primary key.
|
||||||
|
* A finder query is parameterized by the type of key K, type of value V and
|
||||||
|
* type of result R.
|
||||||
|
*
|
||||||
|
* @author Pinaki Poddar
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface FinderQuery<K,V,R> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the identifier of this receiver.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public K getIdentifier();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value to which this receiver delegates its execution.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public V getDelegate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the query for a given instance.
|
||||||
|
*
|
||||||
|
* @param sm the StateManager for a given instance carrying the primary key
|
||||||
|
* values.
|
||||||
|
* @param store the data store against which the query is to be executed.
|
||||||
|
* @param fetch fetch parameters
|
||||||
|
*
|
||||||
|
* @return the result of execution.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public R execute(OpenJPAStateManager sm, StoreManager store,
|
||||||
|
FetchConfiguration fetch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the query string.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public String getQueryString();
|
||||||
|
}
|
|
@ -75,4 +75,21 @@ public interface QueryHints {
|
||||||
public static final String HINT_IGNORE_PREPARED_QUERY =
|
public static final String HINT_IGNORE_PREPARED_QUERY =
|
||||||
"openjpa.hint.IgnorePreparedQuery";
|
"openjpa.hint.IgnorePreparedQuery";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A directive to ignore any cached finder query for find() operation.
|
||||||
|
* The cached entry, if any, remains in the cache.
|
||||||
|
*/
|
||||||
|
public static final String HINT_IGNORE_FINDER = "openjpa.hint.IgnoreFinder";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A directive to invalidate any cached finder query.
|
||||||
|
*/
|
||||||
|
public static final String HINT_INVALIDATE_FINDER =
|
||||||
|
"openjpa.hint.InvalidateFinder";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A directive to overwrite a cached finder query by a new query.
|
||||||
|
*/
|
||||||
|
public static final String HINT_RECACHE_FINDER =
|
||||||
|
"openjpa.hint.RecacheFinder";
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,10 @@ public class TxRollbackEntity
|
||||||
@Basic
|
@Basic
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
protected TxRollbackEntity() {
|
||||||
|
this("?");
|
||||||
|
}
|
||||||
|
|
||||||
public TxRollbackEntity(String name)
|
public TxRollbackEntity(String name)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
|
@ -77,7 +77,9 @@ public class TestDefaultInheritanceStrategy
|
||||||
BaseClass4.class, SubclassG.class,
|
BaseClass4.class, SubclassG.class,
|
||||||
BaseClass5.class, MidClass2.class, SubclassH.class,
|
BaseClass5.class, MidClass2.class, SubclassH.class,
|
||||||
AbstractClass.class, SubclassI.class, SubclassJ.class,
|
AbstractClass.class, SubclassI.class, SubclassJ.class,
|
||||||
BaseClass6.class, SubclassK.class);
|
BaseClass6.class, SubclassK.class,
|
||||||
|
"openjpa.jdbc.FinderCache", "true",
|
||||||
|
CLEAR_TABLES);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Class[] classArray(Class... classes) {
|
private Class[] classArray(Class... classes) {
|
||||||
|
@ -551,6 +553,43 @@ public class TestDefaultInheritanceStrategy
|
||||||
em.close();
|
em.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testFinder() {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
SubclassK sck = new SubclassK();
|
||||||
|
sck.setId(479);
|
||||||
|
sck.setClassKName("SubclassKName");
|
||||||
|
sck.setMidClass3Name("SubclassKMidClass3Name");
|
||||||
|
sck.setName("SubclassKBaseClass6Name");
|
||||||
|
|
||||||
|
BaseClass6 bk6 = new BaseClass6();
|
||||||
|
bk6.setId(302);
|
||||||
|
bk6.setName("BaseClass6Name");
|
||||||
|
|
||||||
|
SubclassI sci = new SubclassI();
|
||||||
|
sci.setId(109);
|
||||||
|
sci.setClassIName("SubclassIName");
|
||||||
|
sci.setName("SubclassIBaseClassName");
|
||||||
|
|
||||||
|
SubclassJ scj = new SubclassJ();
|
||||||
|
scj.setId(238);
|
||||||
|
scj.setClassJName("SubclassJName");
|
||||||
|
scj.setName("SubclassJBaseClassName");
|
||||||
|
|
||||||
|
em.getTransaction().begin();
|
||||||
|
em.persist(sck);
|
||||||
|
em.persist(bk6);
|
||||||
|
em.persist(sci);
|
||||||
|
em.persist(scj);
|
||||||
|
em.getTransaction().commit();
|
||||||
|
|
||||||
|
em.clear();
|
||||||
|
|
||||||
|
verifyInheritanceFinderResult(em, SubclassK.class, 479);
|
||||||
|
verifyInheritanceFinderResult(em, BaseClass6.class, 479, 302);
|
||||||
|
verifyInheritanceFinderResult(em, SubclassI.class, 109);
|
||||||
|
verifyInheritanceFinderResult(em, SubclassJ.class, 238);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that a table contains the specified number of entries
|
* Verifies that a table contains the specified number of entries
|
||||||
* in its DTYPE (default discriminator) column.
|
* in its DTYPE (default discriminator) column.
|
||||||
|
@ -637,4 +676,16 @@ public class TestDefaultInheritanceStrategy
|
||||||
assertTrue("Returned expected entities",
|
assertTrue("Returned expected entities",
|
||||||
count == expectedValues.length);
|
count == expectedValues.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void verifyInheritanceFinderResult(EntityManager em,
|
||||||
|
Class entityType, int... ids) {
|
||||||
|
for (int j = 0; j < 2; j++) {
|
||||||
|
em.clear();
|
||||||
|
for (int id : ids) {
|
||||||
|
Object pc = em.find(entityType, id);
|
||||||
|
assertTrue(entityType.isAssignableFrom(pc.getClass()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package org.apache.openjpa.persistence.jdbc.sqlcache;
|
package org.apache.openjpa.persistence.jdbc.sqlcache;
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.Inheritance;
|
import javax.persistence.Inheritance;
|
||||||
import javax.persistence.InheritanceType;
|
import javax.persistence.InheritanceType;
|
||||||
|
@ -10,7 +9,6 @@ import javax.persistence.InheritanceType;
|
||||||
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
|
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
|
||||||
public class Merchandise {
|
public class Merchandise {
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
|
||||||
private long id;
|
private long id;
|
||||||
|
|
||||||
public long getId() {
|
public long getId() {
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.openjpa.persistence.jdbc.sqlcache;
|
package org.apache.openjpa.persistence.jdbc.sqlcache;
|
||||||
|
|
||||||
|
import javax.persistence.DiscriminatorValue;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.GeneratedValue;
|
import javax.persistence.GeneratedValue;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
|
@ -32,6 +33,7 @@ import javax.persistence.OneToOne;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
|
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
|
||||||
|
@DiscriminatorValue("PERSON")
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
@NamedQuery(name="JPQLNamedSelectPositionalParameter",
|
@NamedQuery(name="JPQLNamedSelectPositionalParameter",
|
||||||
query="select p from Person p where p.firstName=?2" +
|
query="select p from Person p where p.firstName=?2" +
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* 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 javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
|
||||||
|
import org.apache.openjpa.kernel.FinderCache;
|
||||||
|
import org.apache.openjpa.persistence.OpenJPAPersistence;
|
||||||
|
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic test to check FinderQuery caches.
|
||||||
|
*
|
||||||
|
* @author Pinaki Poddar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TestFinderCache extends SingleEMFTestCase {
|
||||||
|
public static final long[] BOOK_IDS = {1000, 2000, 3000};
|
||||||
|
public static final String[] BOOK_NAMES = {"Argumentative Indian", "Tin Drum", "Blink"};
|
||||||
|
public static final long[] CD_IDS = {1001, 2001, 3001};
|
||||||
|
public static final String[] CD_LABELS = {"Beatles", "Sinatra", "Don't Rock My Boat"};
|
||||||
|
|
||||||
|
void createTestData() {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
em.getTransaction().begin();
|
||||||
|
for (int i = 0; i < BOOK_IDS.length; i++) {
|
||||||
|
Book book = new Book();
|
||||||
|
book.setId(BOOK_IDS[i]);
|
||||||
|
book.setTitle(BOOK_NAMES[i]);
|
||||||
|
em.persist(book);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < CD_IDS.length; i++) {
|
||||||
|
CD cd = new CD();
|
||||||
|
cd.setId(CD_IDS[i]);
|
||||||
|
cd.setLabel(CD_LABELS[i]);
|
||||||
|
em.persist(cd);
|
||||||
|
}
|
||||||
|
em.getTransaction().commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUp() {
|
||||||
|
super.setUp(CLEAR_TABLES, Merchandise.class, Book.class, CD.class,
|
||||||
|
Author.class, Person.class, Singer.class, Address.class);
|
||||||
|
createTestData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFinder() {
|
||||||
|
int N = 200;
|
||||||
|
|
||||||
|
emf = createEMF("openjpa.jdbc.FinderCache", "false");
|
||||||
|
run(1, Book.class, BOOK_IDS); // for warmup
|
||||||
|
|
||||||
|
assertNull(getCache());
|
||||||
|
long without = run(N, Book.class, BOOK_IDS);
|
||||||
|
|
||||||
|
emf = createEMF("openjpa.jdbc.FinderCache", "true");
|
||||||
|
assertNotNull(getCache());
|
||||||
|
long with = run(N, Book.class, BOOK_IDS);
|
||||||
|
|
||||||
|
getCache().getStatistics().dump(System.out);
|
||||||
|
|
||||||
|
long pct = (without-with)*100/without;
|
||||||
|
System.err.println(BOOK_IDS.length*N + " find");
|
||||||
|
System.err.println("with " + with);
|
||||||
|
System.err.println("without " + without);
|
||||||
|
System.err.println("delta " + (pct > 0 ? "+" : "") + pct + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a finder query for each identifiers N times and report the median
|
||||||
|
* execution time.
|
||||||
|
*/
|
||||||
|
<T> long run(int N, Class<T> cls, long[] ids) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
List<Long> stats = new ArrayList<Long>();
|
||||||
|
for (int n = 0; n < N; n++) {
|
||||||
|
em.clear();
|
||||||
|
long start = System.nanoTime();
|
||||||
|
for (int i = 0; i < ids.length; i++) {
|
||||||
|
T pc = em.find(cls, ids[i]);
|
||||||
|
assertNotNull(pc);
|
||||||
|
assertTrue(cls.isInstance(pc));
|
||||||
|
}
|
||||||
|
long end = System.nanoTime();
|
||||||
|
stats.add(end-start);
|
||||||
|
}
|
||||||
|
Collections.sort(stats);
|
||||||
|
return stats.get(N/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
FinderCache getCache() {
|
||||||
|
return ((JDBCConfiguration)emf.getConfiguration()).getFinderCacheInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue