From f5fc65f1e173cce07bb86ed8c3fe1889f1d9021f Mon Sep 17 00:00:00 2001 From: Par Winzell Date: Wed, 7 Jan 2009 19:32:24 +0000 Subject: [PATCH] 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. --- .../com/samskivert/depot/DepotRepository.java | 87 ++++++++++++++++--- .../samskivert/depot/impl/FindAllQuery.java | 35 +++++--- 2 files changed, 100 insertions(+), 22 deletions(-) diff --git a/src/java/com/samskivert/depot/DepotRepository.java b/src/java/com/samskivert/depot/DepotRepository.java index 8e6ff7c..24335f2 100644 --- a/src/java/com/samskivert/depot/DepotRepository.java +++ b/src/java/com/samskivert/depot/DepotRepository.java @@ -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 not @@ -286,7 +324,7 @@ public abstract class DepotRepository Class type, Collection 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 List findAll ( - Class type, boolean skipCache, Collection clauses) + Class type, CacheStrategy cache, Collection clauses) throws DatabaseException { DepotMarshaller 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(_ctx, type, clauses) : - new FindAllQuery.WithCache(_ctx, type, clauses)); + if (!_ctx.isUsingCache()) { + cache = CacheStrategy.NONE; + } + + if (cache == CacheStrategy.KEYS) { + return _ctx.invoke(new FindAllQuery.WithCache(_ctx, type, clauses)); + } + return _ctx.invoke(new FindAllQuery.Explicitly( + _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 update = + UpdateClause update = new UpdateClause(pClass, key, marsh.getColumnFieldNames(), record); final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update)); builder.newQuery(update); diff --git a/src/java/com/samskivert/depot/impl/FindAllQuery.java b/src/java/com/samskivert/depot/impl/FindAllQuery.java index 713bd90..7ca98a1 100644 --- a/src/java/com/samskivert/depot/impl/FindAllQuery.java +++ b/src/java/com/samskivert/depot/impl/FindAllQuery.java @@ -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 extends Query> { /** - * 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 extends FindAllQuery @@ -83,7 +83,7 @@ public abstract class FindAllQuery extends Query(_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 extends Query extends FindAllQuery @@ -183,24 +183,34 @@ public abstract class FindAllQuery extends Query extends FindAllQuery { public Explicitly (PersistenceContext ctx, Class type, - Collection clauses) + Collection clauses, boolean cachedContents) throws DatabaseException { super(ctx, type); + _select = new SelectClause(type, _marsh.getFieldNames(), clauses); + + if (cachedContents) { + _qkey = new SimpleCacheKey(_marsh.getTableName() + "Contents", _select.toString()); + } else { + _qkey = null; + } } @Override // from Query public List 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 extends Query _select; + protected SimpleCacheKey _qkey; } // from Query