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:
Pinaki Poddar 2009-02-17 23:21:37 +00:00
parent 556e09b69f
commit 3fc7f54a01
16 changed files with 1092 additions and 12 deletions

View File

@ -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

View File

@ -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() {
}
}

View File

@ -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() + "]";
}
}

View File

@ -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.

View File

@ -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)

View File

@ -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);
} }

View File

@ -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();
}
} }

View File

@ -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();
}
}
} }

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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";
} }

View File

@ -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;

View File

@ -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()));
}
}
}
} }

View File

@ -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() {

View File

@ -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" +

View File

@ -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();
}
}