Finished collection caching implementation. Our databases will shortly breath a
great sigh of relief.
This commit is contained in:
@@ -75,56 +75,68 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
}
|
}
|
||||||
|
|
||||||
_select = new SelectClause<T>(_type, _marsh.getPrimaryKeyFields(), clauses);
|
_select = new SelectClause<T>(_type, _marsh.getPrimaryKeyFields(), clauses);
|
||||||
_builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
|
_qkey = new SimpleCacheKey(_marsh.getTableName() + "Query", _select.toString());
|
||||||
_builder.newQuery(_select);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from Query
|
@Override // from Query
|
||||||
public List<T> getCachedResult (PersistenceContext ctx)
|
public List<T> getCachedResult (PersistenceContext ctx)
|
||||||
{
|
{
|
||||||
return null; // TODO
|
if (_qkey == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
_keys = ctx.<KeySet<T>>cacheLookup(_qkey);
|
||||||
|
if (_keys == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
_cachedQueries++;
|
||||||
|
_fetchKeys = loadFromCache(ctx, _keys, _entities);
|
||||||
|
return (_fetchKeys.size() == 0) ? resolve(_keys, _entities) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// from Query
|
// from Query
|
||||||
public List<T> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
|
public List<T> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
|
||||||
throws SQLException
|
throws SQLException
|
||||||
{
|
{
|
||||||
Map<Key<T>, T> entities = Maps.newHashMap();
|
// we want this to remain null if our key set came from the cache
|
||||||
List<Key<T>> allKeys = Lists.newArrayList();
|
String stmtString = null;
|
||||||
Set<Key<T>> fetchKeys = Sets.newHashSet();
|
|
||||||
|
|
||||||
// first load up the primary keys on which we're operating
|
// if we didn't find our key set in the cache, load the keys that match
|
||||||
PreparedStatement stmt = _builder.prepare(conn);
|
if (_keys == null) {
|
||||||
String stmtString = stmt.toString(); // for debugging
|
List<Key<T>> keys = Lists.newArrayList();
|
||||||
try {
|
SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
|
||||||
ResultSet rs = stmt.executeQuery();
|
builder.newQuery(_select);
|
||||||
while (rs.next()) {
|
PreparedStatement stmt = builder.prepare(conn);
|
||||||
Key<T> key = _marsh.makePrimaryKey(rs);
|
stmtString = stmt.toString(); // for debugging
|
||||||
allKeys.add(key);
|
try {
|
||||||
|
ResultSet rs = stmt.executeQuery();
|
||||||
|
while (rs.next()) {
|
||||||
|
keys.add(_marsh.makePrimaryKey(rs));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
JDBCUtil.close(stmt);
|
||||||
}
|
}
|
||||||
} finally {
|
_keys = KeySet.newKeySet(_type, keys);
|
||||||
JDBCUtil.close(stmt);
|
_uncachedQueries++;
|
||||||
}
|
if (PersistenceContext.CACHE_DEBUG) {
|
||||||
_uncachedQueries++;
|
log.info("Loaded " + _marsh.getTableName() + " keys", "query", _select,
|
||||||
|
"keys", keysToString(_keys), "cacheable", (_qkey != null));
|
||||||
// now fetch any records we can from the cache
|
}
|
||||||
loadFromCache(ctx, allKeys, entities, fetchKeys);
|
if (_qkey != null) {
|
||||||
_cachedRecords = entities.size();
|
ctx.cacheStore(_qkey, _keys); // cache the resulting key set
|
||||||
_uncachedRecords = fetchKeys.size();
|
}
|
||||||
|
// and fetch any records we can from the cache
|
||||||
if (PersistenceContext.CACHE_DEBUG) {
|
_fetchKeys = loadFromCache(ctx, _keys, _entities);
|
||||||
log.info("Loaded " + _marsh.getTableName(), "query", _select,
|
|
||||||
"keys", keysToString(allKeys));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally load the rest from the database
|
// finally load the rest from the database
|
||||||
return loadAndResolve(ctx, conn, allKeys, fetchKeys, entities, stmtString);
|
return loadAndResolve(ctx, conn, _keys, _fetchKeys, _entities, stmtString);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CacheKey getCacheKey ()
|
protected SimpleCacheKey _qkey;
|
||||||
{
|
protected SelectClause<T> _select;
|
||||||
return null;
|
protected KeySet<T> _keys;
|
||||||
}
|
protected Set<Key<T>> _fetchKeys;
|
||||||
|
protected Map<Key<T>, T> _entities = Maps.newHashMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,16 +150,13 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
{
|
{
|
||||||
super(ctx, keys.iterator().next().getPersistentClass());
|
super(ctx, keys.iterator().next().getPersistentClass());
|
||||||
_keys = keys;
|
_keys = keys;
|
||||||
_builder = ctx.getSQLBuilder(new DepotTypes(ctx, _type));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from Query
|
@Override // from Query
|
||||||
public List<T> getCachedResult (PersistenceContext ctx)
|
public List<T> getCachedResult (PersistenceContext ctx)
|
||||||
{
|
{
|
||||||
// look up what we can from the cache
|
// look up what we can from the cache
|
||||||
loadFromCache(ctx, _keys, _entities, _fetchKeys);
|
_fetchKeys = loadFromCache(ctx, _keys, _entities);
|
||||||
_cachedRecords = _entities.size();
|
|
||||||
_uncachedRecords = _fetchKeys.size();
|
|
||||||
|
|
||||||
// if we found everything, we can just return our result straight away, yay!
|
// if we found everything, we can just return our result straight away, yay!
|
||||||
return _fetchKeys.isEmpty() ? resolve(_keys, _entities) : null;
|
return _fetchKeys.isEmpty() ? resolve(_keys, _entities) : null;
|
||||||
@@ -161,8 +170,8 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Iterable<Key<T>> _keys;
|
protected Iterable<Key<T>> _keys;
|
||||||
|
protected Set<Key<T>> _fetchKeys;
|
||||||
protected Map<Key<T>, T> _entities = Maps.newHashMap();
|
protected Map<Key<T>, T> _entities = Maps.newHashMap();
|
||||||
protected Set<Key<T>> _fetchKeys = Sets.newHashSet();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -177,8 +186,6 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
{
|
{
|
||||||
super(ctx, type);
|
super(ctx, type);
|
||||||
_select = new SelectClause<T>(type, _marsh.getFieldNames(), clauses);
|
_select = new SelectClause<T>(type, _marsh.getFieldNames(), clauses);
|
||||||
_builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
|
|
||||||
_builder.newQuery(_select);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from Query
|
@Override // from Query
|
||||||
@@ -193,7 +200,9 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
throws SQLException
|
throws SQLException
|
||||||
{
|
{
|
||||||
List<T> result = Lists.newArrayList();
|
List<T> result = Lists.newArrayList();
|
||||||
PreparedStatement stmt = _builder.prepare(conn);
|
SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
|
||||||
|
builder.newQuery(_select);
|
||||||
|
PreparedStatement stmt = builder.prepare(conn);
|
||||||
try {
|
try {
|
||||||
ResultSet rs = stmt.executeQuery();
|
ResultSet rs = stmt.executeQuery();
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
@@ -211,6 +220,8 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
// TODO: do we want to cache these results?
|
// TODO: do we want to cache these results?
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected SelectClause<T> _select;
|
||||||
}
|
}
|
||||||
|
|
||||||
// from Query
|
// from Query
|
||||||
@@ -226,9 +237,10 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
_marsh = ctx.getMarshaller(type);
|
_marsh = ctx.getMarshaller(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void loadFromCache (PersistenceContext ctx, Iterable<Key<T>> allKeys,
|
protected Set<Key<T>> loadFromCache (PersistenceContext ctx, Iterable<Key<T>> allKeys,
|
||||||
Map<Key<T>, T> entities, Set<Key<T>> fetchKeys)
|
Map<Key<T>, T> entities)
|
||||||
{
|
{
|
||||||
|
Set<Key<T>> fetchKeys = Sets.newHashSet();
|
||||||
for (Key<T> key : allKeys) {
|
for (Key<T> key : allKeys) {
|
||||||
T value = ctx.<T>cacheLookup(key);
|
T value = ctx.<T>cacheLookup(key);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -238,6 +250,12 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
}
|
}
|
||||||
fetchKeys.add(key);
|
fetchKeys.add(key);
|
||||||
}
|
}
|
||||||
|
if (PersistenceContext.CACHE_DEBUG) {
|
||||||
|
log.info("Loaded from cache " + _marsh.getTableName(), "count", entities.size());
|
||||||
|
}
|
||||||
|
_cachedRecords = entities.size();
|
||||||
|
_uncachedRecords = fetchKeys.size();
|
||||||
|
return fetchKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<T> resolve (Iterable<Key<T>> allKeys, Map<Key<T>, T> entities)
|
protected List<T> resolve (Iterable<Key<T>> allKeys, Map<Key<T>, T> entities)
|
||||||
@@ -287,9 +305,11 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
throws SQLException
|
throws SQLException
|
||||||
{
|
{
|
||||||
boolean hasPrimaryKey = _marsh.hasPrimaryKey();
|
boolean hasPrimaryKey = _marsh.hasPrimaryKey();
|
||||||
_builder.newQuery(new SelectClause<T>(_type, _marsh.getFieldNames(),
|
SelectClause<T> select = new SelectClause<T>(
|
||||||
KeySet.newKeySet(_type, keys)));
|
_type, _marsh.getFieldNames(), KeySet.newKeySet(_type, keys));
|
||||||
PreparedStatement stmt = _builder.prepare(conn);
|
SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, select));
|
||||||
|
builder.newQuery(select);
|
||||||
|
PreparedStatement stmt = builder.prepare(conn);
|
||||||
try {
|
try {
|
||||||
Set<Key<T>> got = Sets.newHashSet();
|
Set<Key<T>> got = Sets.newHashSet();
|
||||||
ResultSet rs = stmt.executeQuery();
|
ResultSet rs = stmt.executeQuery();
|
||||||
@@ -300,10 +320,7 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
if (entities.put(key, obj) != null) {
|
if (entities.put(key, obj) != null) {
|
||||||
dups++;
|
dups++;
|
||||||
}
|
}
|
||||||
// cache our result if it has a primary key
|
ctx.cacheStore(key, obj.clone()); // cache our result
|
||||||
if (hasPrimaryKey) {
|
|
||||||
ctx.cacheStore(_marsh.getPrimaryKey(obj), obj.clone());
|
|
||||||
}
|
|
||||||
got.add(key);
|
got.add(key);
|
||||||
cnt++;
|
cnt++;
|
||||||
}
|
}
|
||||||
@@ -311,7 +328,11 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
// fewer, then complain
|
// fewer, then complain
|
||||||
if (cnt > keys.size() || (origStmt != null && cnt < keys.size())) {
|
if (cnt > keys.size() || (origStmt != null && cnt < keys.size())) {
|
||||||
log.warning("Row count mismatch in second pass", "origQuery", origStmt,
|
log.warning("Row count mismatch in second pass", "origQuery", origStmt,
|
||||||
"wanted", keys, "got", got, "dups", dups, new Exception());
|
// we need toString() here or StringUtil will get smart and dump our
|
||||||
|
// KeySet using its iterator which results in verbosity
|
||||||
|
"wanted", KeySet.newKeySet(_type, keys).toString(),
|
||||||
|
"got", KeySet.newKeySet(_type, got).toString(),
|
||||||
|
"dups", dups, new Exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PersistenceContext.CACHE_DEBUG) {
|
if (PersistenceContext.CACHE_DEBUG) {
|
||||||
@@ -335,9 +356,7 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
return builder.append(")").toString();
|
return builder.append(")").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SQLBuilder _builder;
|
|
||||||
protected DepotMarshaller<T> _marsh;
|
|
||||||
protected Class<T> _type;
|
protected Class<T> _type;
|
||||||
protected SelectClause<T> _select;
|
protected DepotMarshaller<T> _marsh;
|
||||||
protected int _cachedQueries, _uncachedQueries, _cachedRecords, _uncachedRecords;
|
protected int _cachedQueries, _uncachedQueries, _cachedRecords, _uncachedRecords;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ public class Key<T extends PersistentRecord> extends WhereClause
|
|||||||
// from CacheKey
|
// from CacheKey
|
||||||
public Serializable getCacheKey ()
|
public Serializable getCacheKey ()
|
||||||
{
|
{
|
||||||
return _values;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// from ValidatingCacheInvalidator
|
// from ValidatingCacheInvalidator
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
package com.samskivert.depot;
|
package com.samskivert.depot;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.AbstractCollection;
|
import java.util.AbstractCollection;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -48,7 +49,7 @@ import static com.samskivert.depot.Log.log;
|
|||||||
* the cache.
|
* the cache.
|
||||||
*/
|
*/
|
||||||
public abstract class KeySet<T extends PersistentRecord> extends WhereClause
|
public abstract class KeySet<T extends PersistentRecord> extends WhereClause
|
||||||
implements SQLExpression, ValidatingCacheInvalidator, Iterable<Key<T>>
|
implements Serializable, SQLExpression, ValidatingCacheInvalidator, Iterable<Key<T>>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Creates a key set for the supplied persistent record and keys.
|
* Creates a key set for the supplied persistent record and keys.
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ package com.samskivert.depot;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import com.samskivert.util.ObjectUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience class that implements {@link CacheKey} as simply as possibly. This class is
|
* Convenience class that implements {@link CacheKey} as simply as possibly. This class is
|
||||||
* typically used when the caller wants to cache a non-obvious query such as a collection,
|
* typically used when the caller wants to cache a non-obvious query such as a collection,
|
||||||
@@ -85,21 +87,8 @@ public class SimpleCacheKey
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
SimpleCacheKey other = (SimpleCacheKey) obj;
|
SimpleCacheKey other = (SimpleCacheKey) obj;
|
||||||
if (_cacheId == null) {
|
return ObjectUtil.equals(_cacheId, other._cacheId) &&
|
||||||
if (other._cacheId != null) {
|
ObjectUtil.equals(_cacheKey, other._cacheKey);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!_cacheId.equals(other._cacheId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_cacheKey == null) {
|
|
||||||
if (other._cacheKey != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!_cacheKey.equals(other._cacheKey)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ import java.util.Set;
|
|||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
import com.samskivert.jdbc.StaticConnectionProvider;
|
||||||
import com.samskivert.util.RandomUtil;
|
import com.samskivert.util.RandomUtil;
|
||||||
|
|
||||||
import com.samskivert.jdbc.StaticConnectionProvider;
|
import com.samskivert.depot.CacheAdapter;
|
||||||
import com.samskivert.depot.DepotRepository;
|
import com.samskivert.depot.DepotRepository;
|
||||||
import com.samskivert.depot.Key;
|
import com.samskivert.depot.Key;
|
||||||
import com.samskivert.depot.KeySet;
|
import com.samskivert.depot.KeySet;
|
||||||
@@ -40,6 +41,7 @@ import com.samskivert.depot.annotation.Computed;
|
|||||||
import com.samskivert.depot.clause.Where;
|
import com.samskivert.depot.clause.Where;
|
||||||
import com.samskivert.depot.expression.LiteralExp;
|
import com.samskivert.depot.expression.LiteralExp;
|
||||||
import com.samskivert.depot.operator.Conditionals;
|
import com.samskivert.depot.operator.Conditionals;
|
||||||
|
import com.samskivert.depot.util.TestCacheAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A test tool for the Depot repository services.
|
* A test tool for the Depot repository services.
|
||||||
@@ -60,7 +62,8 @@ public class TestRepository extends DepotRepository
|
|||||||
throws Exception
|
throws Exception
|
||||||
{
|
{
|
||||||
PersistenceContext perCtx = new PersistenceContext();
|
PersistenceContext perCtx = new PersistenceContext();
|
||||||
perCtx.init("test", new StaticConnectionProvider("depot.properties"), null);
|
perCtx.init("test", new StaticConnectionProvider("depot.properties"),
|
||||||
|
new TestCacheAdapter());
|
||||||
|
|
||||||
// tests a bogus rename migration
|
// tests a bogus rename migration
|
||||||
// perCtx.registerMigration(TestRecord.class, new SchemaMigration.Rename(1, "foo", "bar"));
|
// perCtx.registerMigration(TestRecord.class, new SchemaMigration.Rename(1, "foo", "bar"));
|
||||||
@@ -71,6 +74,7 @@ public class TestRepository extends DepotRepository
|
|||||||
|
|
||||||
TestRepository repo = new TestRepository(perCtx);
|
TestRepository repo = new TestRepository(perCtx);
|
||||||
|
|
||||||
|
System.out.println("Deleting old record.");
|
||||||
repo.delete(TestRecord.class, 1);
|
repo.delete(TestRecord.class, 1);
|
||||||
|
|
||||||
Date now = new Date(System.currentTimeMillis());
|
Date now = new Date(System.currentTimeMillis());
|
||||||
@@ -86,7 +90,7 @@ public class TestRepository extends DepotRepository
|
|||||||
record.numbers = new int[] { 9, 0, 2, 1, 0 };
|
record.numbers = new int[] { 9, 0, 2, 1, 0 };
|
||||||
|
|
||||||
repo.insert(record);
|
repo.insert(record);
|
||||||
System.out.println(repo.load(TestRecord.class, record.recordId));
|
System.out.println("Record: " + repo.load(TestRecord.class, record.recordId));
|
||||||
|
|
||||||
// record.age = 25;
|
// record.age = 25;
|
||||||
// record.name = "Bob";
|
// record.name = "Bob";
|
||||||
@@ -96,7 +100,7 @@ public class TestRepository extends DepotRepository
|
|||||||
repo.updatePartial(TestRecord.class, record.recordId,
|
repo.updatePartial(TestRecord.class, record.recordId,
|
||||||
TestRecord.AGE, 25, TestRecord.NAME, "Bob",
|
TestRecord.AGE, 25, TestRecord.NAME, "Bob",
|
||||||
TestRecord.NUMBERS, new int[] { 1, 2, 3, 4, 5 });
|
TestRecord.NUMBERS, new int[] { 1, 2, 3, 4, 5 });
|
||||||
System.out.println(repo.load(TestRecord.class, record.recordId));
|
System.out.println("Updated " + repo.load(TestRecord.class, record.recordId));
|
||||||
|
|
||||||
for (int ii = 2; ii < CREATE_RECORDS; ii++) {
|
for (int ii = 2; ii < CREATE_RECORDS; ii++) {
|
||||||
record = new TestRecord();
|
record = new TestRecord();
|
||||||
@@ -116,6 +120,11 @@ public class TestRepository extends DepotRepository
|
|||||||
System.out.println("Load none " + repo.loadAll(none.toCollection()) + ".");
|
System.out.println("Load none " + repo.loadAll(none.toCollection()) + ".");
|
||||||
System.out.println("Delete none " + repo.deleteAll(TestRecord.class, none) + ".");
|
System.out.println("Delete none " + repo.deleteAll(TestRecord.class, none) + ".");
|
||||||
|
|
||||||
|
// test collection caching
|
||||||
|
Where where = new Where(new Conditionals.GreaterThan(TestRecord.RECORD_ID_C, 100));
|
||||||
|
System.out.println("100 and up: " + repo.findAll(TestRecord.class, where).size());
|
||||||
|
System.out.println("100 and up again: " + repo.findAll(TestRecord.class, where).size());
|
||||||
|
|
||||||
// test a partial key set
|
// test a partial key set
|
||||||
KeySet<TestRecord> some = KeySet.newSimpleKeySet(
|
KeySet<TestRecord> some = KeySet.newSimpleKeySet(
|
||||||
TestRecord.class, Sets.newHashSet(1, 3, 5, 7, 9));
|
TestRecord.class, Sets.newHashSet(1, 3, 5, 7, 9));
|
||||||
|
|||||||
Reference in New Issue
Block a user