More cache configuration options for findAll(), especially a new CONTENTS strategy which will cache the entire result set of the supplied query, without the restrictions of the KEYS approach. This is very lovely and perhaps a little scary.

This commit is contained in:
Par Winzell
2009-01-07 19:32:24 +00:00
parent 269a3ffb92
commit f5fc65f1e1
2 changed files with 100 additions and 22 deletions
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
//
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
@@ -56,8 +56,8 @@ import com.samskivert.depot.impl.FindAllKeysQuery;
import com.samskivert.depot.impl.FindAllQuery;
import com.samskivert.depot.impl.FindOneQuery;
import com.samskivert.depot.impl.Modifier;
import com.samskivert.depot.impl.SQLBuilder;
import com.samskivert.depot.impl.Modifier.*;
import com.samskivert.depot.impl.SQLBuilder;
import static com.samskivert.depot.Log.log;
@@ -67,6 +67,44 @@ import static com.samskivert.depot.Log.log;
*/
public abstract class DepotRepository
{
public enum CacheStrategy {
/** Completely bypass the cache for this query. */
NONE,
/** Use the {@link #KEYS} strategy if possible, else revert to {@link #NONE}. */
BEST,
/**
* Resolve this collection query in two steps: first we enumerate the primary keys for
* all the records that satisfy the query, then we acquire the actual requested data for
* each key. The cache is consulted and updated in both steps: for the first, see if we've
* already executed precisely this query and cached the resulting key set. For the second
* phase, searchthe cache for each key in said set, and retrieve the corresponding data
* record from there when possible. Finally execute a database query to retrieve the data
* for any keys that were not in the cache, making sure to cache them in the process.
*
* Note: There is currently no cache invalidation of collection queries. If records are
* inserted, deleted or modified, cached keysets will not be updated. The keyset cannot be
* guaranteed to be up to date. For sensitive operations, use Cache.NONE.
*
* Note: The KEYS strategy may not be used on @Computed records, for records that do not
* in fact have a primary key, or for queries that use @FieldOverrides.
*/
KEYS,
/**
* This cache strategy is direct and explicit, eschewing the dual phases of the KEYS
* approach. However, before the database is invoked at all, we consult the cache hoping
* to find the entire result set already stashed away in there, using the entire query
* as the key. If we failed to find it, we execute the query and update the cache with the
* result.
*
* This strategy has none of the limitations of KEYS and can be used with key-less and
* @Computed records and arbitrarily complicated queries. Note however that as with KEYS,
* there is currently no automatic invalidation and it is potentially very memory intensive.
*/
CONTENTS };
/**
* Creates a repository with the supplied persistence context. Any schema migrations needed by
* this repository should be registered in its constructor. A repository should <em>not</em>
@@ -286,7 +324,7 @@ public abstract class DepotRepository
Class<T> type, Collection<? extends QueryClause> clauses)
throws DatabaseException
{
return findAll(type, false, clauses);
return findAll(type, CacheStrategy.BEST, clauses);
}
/**
@@ -299,20 +337,45 @@ public abstract class DepotRepository
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
protected <T extends PersistentRecord> List<T> findAll (
Class<T> type, boolean skipCache, Collection<? extends QueryClause> clauses)
Class<T> type, CacheStrategy cache, Collection<? extends QueryClause> clauses)
throws DatabaseException
{
DepotMarshaller<T> marsh = _ctx.getMarshaller(type);
boolean useExplicit = skipCache || (marsh.getTableName() == null) ||
!marsh.hasPrimaryKey() || !_ctx.isUsingCache();
// queries on @Computed records or the presence of FieldOverrides use the simple algorithm
for (QueryClause clause : clauses) {
useExplicit |= (clause instanceof FieldOverride);
if (cache == CacheStrategy.KEYS || cache == CacheStrategy.BEST) {
String reason = null;
if (marsh.getTableName() == null) {
reason = type + " is computed";
} else if (!marsh.hasPrimaryKey()) {
reason = type + " has no primary key";
} else {
for (QueryClause clause : clauses) {
if (clause instanceof FieldOverride) {
reason = "query uses a FieldOverride clause";
break;
}
}
}
if (cache == CacheStrategy.BEST) {
cache = (reason != null) ? CacheStrategy.NONE : CacheStrategy.KEYS;
} else if (reason != null) {
// if user explicitly asked for the KEYS strategy and we can't do it, protest
throw new IllegalArgumentException("Cannot use KEYS strategy because " + reason);
}
}
return _ctx.invoke(useExplicit ? new FindAllQuery.Explicitly<T>(_ctx, type, clauses) :
new FindAllQuery.WithCache<T>(_ctx, type, clauses));
if (!_ctx.isUsingCache()) {
cache = CacheStrategy.NONE;
}
if (cache == CacheStrategy.KEYS) {
return _ctx.invoke(new FindAllQuery.WithCache<T>(_ctx, type, clauses));
}
return _ctx.invoke(new FindAllQuery.Explicitly<T>(
_ctx, type, clauses, cache == CacheStrategy.CONTENTS));
}
/**
@@ -423,7 +486,7 @@ public abstract class DepotRepository
throw new IllegalArgumentException("Can't update record with null primary key.");
}
UpdateClause<T> update =
UpdateClause<T> update =
new UpdateClause<T>(pClass, key, marsh.getColumnFieldNames(), record);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
//
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
@@ -60,7 +60,7 @@ import static com.samskivert.depot.Log.log;
public abstract class FindAllQuery<T extends PersistentRecord> extends Query<List<T>>
{
/**
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
* details.
*/
public static class WithCache<T extends PersistentRecord> extends FindAllQuery<T>
@@ -83,7 +83,7 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
}
_select = new SelectClause<T>(_type, _marsh.getPrimaryKeyFields(), clauses);
_qkey = new SimpleCacheKey(_marsh.getTableName() + "Query", _select.toString());
_qkey = new SimpleCacheKey(_marsh.getTableName() + "Keys", _select.toString());
}
@Override // from Query
@@ -148,7 +148,7 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
}
/**
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
* details.
*/
public static class WithKeys<T extends PersistentRecord> extends FindAllQuery<T>
@@ -183,24 +183,34 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
}
/**
* The single-pass collection query implementation. See {@link DepotRepository#findAll} for
* The single-pass collection query implementation. See {@link DepotRepository#findAll} for
* details.
*/
public static class Explicitly<T extends PersistentRecord> extends FindAllQuery<T>
{
public Explicitly (PersistenceContext ctx, Class<T> type,
Collection<? extends QueryClause> clauses)
Collection<? extends QueryClause> clauses, boolean cachedContents)
throws DatabaseException
{
super(ctx, type);
_select = new SelectClause<T>(type, _marsh.getFieldNames(), clauses);
if (cachedContents) {
_qkey = new SimpleCacheKey(_marsh.getTableName() + "Contents", _select.toString());
} else {
_qkey = null;
}
}
@Override // from Query
public List<T> getCachedResult (PersistenceContext ctx)
{
return null; // TODO: we could cache all the records as one giant list but that would
// not play nicely when records were evicted from the cache by primary key
if (_qkey != null) {
_cachedQueries++;
return ctx.cacheLookup(_qkey);
}
return null;
}
// from Query
@@ -221,12 +231,17 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
}
_explicitQueries++;
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded " + _marsh.getTableName(), "query", _select, "rows", result.size());
log.info("Loaded " + _marsh.getTableName(), "query", _select, "rows",
result.size(), "cacheKey", _qkey);
}
return result; // TODO: do we want to cache these results?
if (_qkey != null) {
ctx.cacheStore(_qkey, result); // cache the entire result set
}
return result;
}
protected SelectClause<T> _select;
protected SimpleCacheKey _qkey;
}
// from Query