A whole bunch of revampery on the way to collection caching, including some

basic stats reporting and query and cache logging.

PersistenceContext no longer sticks its nose so deeply into the business of
Query and Modifier. It just passes itself along and allows them to do any cache
activity they need during their normal execution. The one situation where we
keep our nose in their business is to allow a Query to return a result from the
cache before it is even invoked so that we can avoid requesting a database
connection if we won't ever need one.
This commit is contained in:
Michael Bayne
2008-11-21 01:49:28 +00:00
parent 5f26eb28c9
commit 9880ff326c
18 changed files with 548 additions and 222 deletions
@@ -578,7 +578,7 @@ public class DepotMarshaller<T extends PersistentRecord>
// check to see if our schema version table exists, create it if not
ctx.invoke(new Modifier() {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.createTableIfMissing(
conn, SCHEMA_VERSION_TABLE,
new String[] { P_COLUMN, V_COLUMN, MV_COLUMN },
@@ -697,7 +697,7 @@ public class DepotMarshaller<T extends PersistentRecord>
final Iterable<Index> indexen = _indexes.values();
ctx.invoke(new Modifier() {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// create the table
String[] primaryKeyColumns = null;
if (_pkColumns != null) {
@@ -803,8 +803,8 @@ public class DepotMarshaller<T extends PersistentRecord>
if (hasPrimaryKey() && metaData.pkName == null) {
log.info("Adding primary key.");
ctx.invoke(new Modifier() {
@Override public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.addPrimaryKey(
conn, getTableName(), fieldsToColumns(getPrimaryKeyFields()));
return 0;
@@ -815,8 +815,8 @@ public class DepotMarshaller<T extends PersistentRecord>
final String pkName = metaData.pkName;
log.info("Dropping primary key: " + pkName);
ctx.invoke(new Modifier() {
@Override public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.dropPrimaryKey(conn, getTableName(), pkName);
return 0;
}
@@ -833,8 +833,8 @@ public class DepotMarshaller<T extends PersistentRecord>
}
// but this is a new, named index, so we create it
ctx.invoke(new Modifier() {
@Override public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.addIndexToTable(
conn, getTableName(), fieldsToColumns(index.fields()),
ixName, index.unique());
@@ -869,8 +869,8 @@ public class DepotMarshaller<T extends PersistentRecord>
final String[] colArr = colSet.toArray(new String[colSet.size()]);
final String fName = indexName;
ctx.invoke(new Modifier() {
@Override public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.addIndexToTable(conn, getTableName(), colArr, fName, true);
return 0;
}
@@ -891,8 +891,8 @@ public class DepotMarshaller<T extends PersistentRecord>
// but not this one, so let's create it
ctx.invoke(new Modifier() {
@Override public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
builder.addFullTextSearch(conn, DepotMarshaller.this, recordFts);
return 0;
}
@@ -937,8 +937,8 @@ public class DepotMarshaller<T extends PersistentRecord>
// initialValue which will use the new column name to obtain the sequence name which
// ain't going to work either; we punt!
ctx.invoke(new Modifier() {
@Override public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
valgen.create(conn, liaison);
return 0;
}
@@ -983,7 +983,7 @@ public class DepotMarshaller<T extends PersistentRecord>
protected abstract class SimpleModifier extends Modifier {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
Statement stmt = conn.createStatement();
try {
return invoke(liaison, stmt);
@@ -1035,10 +1035,13 @@ public class DepotMarshaller<T extends PersistentRecord>
throws DatabaseException
{
return ctx.invoke(new Query.Trivial<TableMetaData>() {
@Override public TableMetaData invoke (Connection conn, DatabaseLiaison dl)
throws SQLException {
@Override public TableMetaData invoke (PersistenceContext ctx, Connection conn,
DatabaseLiaison dl) throws SQLException {
return new TableMetaData(conn.getMetaData(), tableName);
}
public void updateStats (Stats stats) {
// nothing doing
}
});
}
@@ -37,6 +37,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.StringUtil;
import com.samskivert.jdbc.ConnectionProvider;
import com.samskivert.jdbc.DatabaseLiaison;
@@ -308,7 +309,7 @@ public abstract class DepotRepository
* the latest data.
*/
protected <T extends PersistentRecord> List<Key<T>> findAllKeys (
Class<T> type, boolean forUpdate, Collection<? extends QueryClause> clauses)
final Class<T> type, boolean forUpdate, Collection<? extends QueryClause> clauses)
throws DatabaseException
{
final List<Key<T>> keys = Lists.newArrayList();
@@ -319,36 +320,53 @@ public abstract class DepotRepository
if (forUpdate) {
_ctx.invoke(new Modifier(null) {
@Override public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
keys.add(marsh.makePrimaryKey(rs));
}
// TODO: cache this result?
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded " + StringUtil.shortClassName(type) + " keys",
"count", keys.size());
}
return 0;
} finally {
JDBCUtil.close(stmt);
}
}
@Override public void updateStats (Stats stats) {
stats.noteQuery(0, 1, 0, 0); // one uncached query
}
});
} else {
_ctx.invoke(new Query.Trivial<Void>() {
@Override
public Void invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
public Void invoke (PersistenceContext ctx, Connection conn,
DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
keys.add(marsh.makePrimaryKey(rs));
}
// TODO: cache this result?
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded " + StringUtil.shortClassName(type) + " keys",
"count", keys.size());
}
return null;
} finally {
JDBCUtil.close(stmt);
}
}
public void updateStats (Stats stats) {
stats.noteQuery(0, 1, 0, 0); // one uncached query
}
});
}
@@ -375,7 +393,7 @@ public abstract class DepotRepository
// key will be null if record was supplied without a primary key
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// if needed, update our modifier's key so that it can cache our results
Set<String> identityFields = Collections.emptySet();
if (_key == null) {
@@ -427,7 +445,7 @@ public abstract class DepotRepository
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
@@ -464,7 +482,7 @@ public abstract class DepotRepository
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
// clear out _result so that we don't rewrite this partial record to the cache
_result = null;
@@ -604,7 +622,7 @@ public abstract class DepotRepository
return _ctx.invoke(new Modifier(invalidator) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
@@ -735,7 +753,7 @@ public abstract class DepotRepository
return _ctx.invoke(new Modifier(invalidator) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
@@ -773,7 +791,7 @@ public abstract class DepotRepository
final boolean[] created = new boolean[1];
_ctx.invoke(new CachingModifier<T>(record, key, key) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = null;
try {
if (_key != null) {
@@ -897,7 +915,7 @@ public abstract class DepotRepository
return _ctx.invoke(new Modifier(invalidator) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
+113 -64
View File
@@ -75,13 +75,12 @@ public abstract class FindAllQuery<T extends PersistentRecord>
}
}
SelectClause<T> select =
new SelectClause<T>(_type, _marsh.getPrimaryKeyFields(), clauses);
_builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, select));
_builder.newQuery(select);
_select = new SelectClause<T>(_type, _marsh.getPrimaryKeyFields(), clauses);
_builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
_builder.newQuery(_select);
}
public List<T> invoke (Connection conn, DatabaseLiaison liaison)
public List<T> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
Map<Key<T>, T> entities = Maps.newHashMap();
@@ -96,15 +95,11 @@ public abstract class FindAllQuery<T extends PersistentRecord>
Key<T> key = _marsh.makePrimaryKey(rs);
allKeys.add(key);
// TODO: All this cache fiddling needs to move to PersistenceContext?
CacheAdapter.CachedValue<T> hit = _ctx.cacheLookup(key);
if (hit != null) {
T value = hit.getValue();
if (value != null) {
@SuppressWarnings("unchecked") T newValue = (T) value.clone();
entities.put(key, newValue);
continue;
}
T value = _ctx.<T>cacheLookup(key);
if (value != null) {
@SuppressWarnings("unchecked") T newValue = (T) value.clone();
entities.put(key, newValue);
continue;
}
fetchKeys.add(key);
@@ -114,8 +109,23 @@ public abstract class FindAllQuery<T extends PersistentRecord>
JDBCUtil.close(stmt);
}
return loadAndResolve(conn, allKeys, fetchKeys, entities, stmtString);
_cachedRecords = entities.size();
_uncachedRecords = fetchKeys.size();
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded " + _marsh.getTableName(), "query", _select,
"keys", keysToString(allKeys));
}
return loadAndResolve(ctx, conn, allKeys, fetchKeys, entities, stmtString);
}
public void updateStats (Stats stats) {
stats.noteQuery(0, 1, _cachedRecords, _uncachedRecords); // one uncached query
}
protected SelectClause<T> _select;
protected int _cachedRecords, _uncachedRecords;
}
/**
@@ -132,29 +142,33 @@ public abstract class FindAllQuery<T extends PersistentRecord>
_builder = ctx.getSQLBuilder(new DepotTypes(ctx, _type));
}
public List<T> invoke (Connection conn, DatabaseLiaison liaison)
public List<T> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
Map<Key<T>, T> entities = Maps.newHashMap();
Set<Key<T>> fetchKeys = Sets.newHashSet();
for (Key<T> key : _keys) {
// TODO: All this cache fiddling needs to move to PersistenceContext?
CacheAdapter.CachedValue<T> hit = _ctx.cacheLookup(key);
if (hit != null) {
T value = hit.getValue();
if (value != null) {
@SuppressWarnings("unchecked") T newValue = (T) value.clone();
entities.put(key, newValue);
continue;
}
T value = _ctx.<T>cacheLookup(key);
if (value != null) {
@SuppressWarnings("unchecked") T newValue = (T) value.clone();
entities.put(key, newValue);
continue;
}
fetchKeys.add(key);
}
return loadAndResolve(conn, _keys, fetchKeys, entities, null);
_cachedRecords = entities.size();
_uncachedRecords = fetchKeys.size();
return loadAndResolve(ctx, conn, _keys, fetchKeys, entities, null);
}
public void updateStats (Stats stats) {
stats.noteQuery(0, 0, _cachedRecords, _uncachedRecords);
}
protected Collection<Key<T>> _keys;
protected int _cachedRecords, _uncachedRecords;
}
/**
@@ -168,12 +182,12 @@ public abstract class FindAllQuery<T extends PersistentRecord>
throws DatabaseException
{
super(ctx, type);
SelectClause<T> select = new SelectClause<T>(type, _marsh.getFieldNames(), clauses);
_builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, select));
_builder.newQuery(select);
_select = new SelectClause<T>(type, _marsh.getFieldNames(), clauses);
_builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
_builder.newQuery(_select);
}
public List<T> invoke (Connection conn, DatabaseLiaison liaison)
public List<T> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
List<T> result = Lists.newArrayList();
@@ -182,12 +196,25 @@ public abstract class FindAllQuery<T extends PersistentRecord>
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
result.add(_marsh.createObject(rs));
_uncachedRecords++;
}
} finally {
JDBCUtil.close(stmt);
}
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded " + _marsh.getTableName(), "query", _select,
"uncached", _uncachedRecords);
}
// TODO: do we want to cache these results?
return result;
}
public void updateStats (Stats stats) {
stats.noteQuery(0, 1, 0, _uncachedRecords);
}
protected SelectClause<T> _select;
protected int _uncachedRecords;
}
public FindAllQuery (PersistenceContext ctx, Class<T> type)
@@ -198,45 +225,46 @@ public abstract class FindAllQuery<T extends PersistentRecord>
_marsh = _ctx.getMarshaller(type);
}
// from Query
public CacheKey getCacheKey ()
// from interface Query
public List<T> getCachedResult (PersistenceContext ctx)
{
return null;
return null; // TODO
}
// from Query
public void updateCache (PersistenceContext ctx, List<T> result) {
if (_marsh.hasPrimaryKey()) {
for (T bit : result) {
ctx.cacheStore(_marsh.getPrimaryKey(bit), bit.clone());
}
}
}
// // from interface Query
// public List<T> transformCacheHit (CacheKey key, List<T> bits)
// {
// if (bits == null) {
// return bits;
// }
// from Query
public List<T> transformCacheHit (CacheKey key, List<T> bits)
// List<T> result = Lists.newArrayList();
// for (T bit : bits) {
// if (bit != null) {
// @SuppressWarnings("unchecked") T cbit = (T) bit.clone();
// result.add(cbit);
// } else {
// result.add(null);
// }
// }
// return result;
// }
// from interface Operation
public void updateStats (Stats stats)
{
if (bits == null) {
return bits;
}
List<T> result = Lists.newArrayList();
for (T bit : bits) {
if (bit != null) {
@SuppressWarnings("unchecked") T cbit = (T) bit.clone();
result.add(cbit);
} else {
result.add(null);
}
}
return result;
// TODO
}
protected List<T> loadAndResolve (Connection conn, Collection<Key<T>> allKeys,
Set<Key<T>> fetchKeys, Map<Key<T>, T> entities,
String origStmt)
protected List<T> loadAndResolve (PersistenceContext ctx, Connection conn,
Collection<Key<T>> allKeys, Set<Key<T>> fetchKeys,
Map<Key<T>, T> entities, String origStmt)
throws SQLException
{
if (PersistenceContext.CACHE_DEBUG && fetchKeys.size() > 0) {
log.info("Loading " + _marsh.getTableName(), "keys", keysToString(fetchKeys));
}
// if we're fetching a huge number of records, we have to do it in multiple queries
if (fetchKeys.size() > In.MAX_KEYS) {
int keyCount = fetchKeys.size();
@@ -248,11 +276,11 @@ public abstract class FindAllQuery<T extends PersistentRecord>
iter.remove();
}
keyCount -= keys.size();
loadRecords(conn, keys, entities, origStmt);
loadRecords(ctx, conn, keys, entities, origStmt);
} while (keyCount > 0);
} else if (fetchKeys.size() > 0) {
loadRecords(conn, fetchKeys, entities, origStmt);
loadRecords(ctx, conn, fetchKeys, entities, origStmt);
}
List<T> result = Lists.newArrayList();
@@ -265,10 +293,11 @@ public abstract class FindAllQuery<T extends PersistentRecord>
return result;
}
protected void loadRecords (Connection conn, Set<Key<T>> keys, Map<Key<T>, T> entities,
String origStmt)
protected void loadRecords (PersistenceContext ctx, Connection conn, Set<Key<T>> keys,
Map<Key<T>, T> entities, String origStmt)
throws SQLException
{
boolean hasPrimaryKey = _marsh.hasPrimaryKey();
_builder.newQuery(new SelectClause<T>(_type, _marsh.getFieldNames(),
new KeySet<T>(_type, keys)));
PreparedStatement stmt = _builder.prepare(conn);
@@ -282,6 +311,10 @@ public abstract class FindAllQuery<T extends PersistentRecord>
if (entities.put(key, obj) != null) {
dups++;
}
// cache our result if it has a primary key
if (hasPrimaryKey) {
ctx.cacheStore(_marsh.getPrimaryKey(obj), obj.clone());
}
got.add(key);
cnt++;
}
@@ -292,11 +325,27 @@ public abstract class FindAllQuery<T extends PersistentRecord>
"wanted", keys, "got", got, "dups", dups, new Exception());
}
if (PersistenceContext.CACHE_DEBUG) {
log.info("Cached " + _marsh.getTableName(), "count", cnt);
}
} finally {
JDBCUtil.close(stmt);
}
}
protected String keysToString (Iterable<Key<T>> keySet)
{
StringBuilder builder = new StringBuilder("(");
for (Key<T> key : keySet) {
if (builder.length() > 1) {
builder.append(", ");
}
key.toShortString(builder);
}
return builder.append(")").toString();
}
protected PersistenceContext _ctx;
protected SQLBuilder _builder;
protected DepotMarshaller<T> _marsh;
+47 -28
View File
@@ -27,9 +27,13 @@ import java.sql.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.util.StringUtil;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.SelectClause;
import static com.samskivert.depot.Log.log;
/**
* The implementation of {@link DepotRepository#load} functionality.
*/
@@ -49,21 +53,30 @@ public class FindOneQuery<T extends PersistentRecord>
_builder.newQuery(_select);
}
// from Query
public CacheKey getCacheKey ()
// from interface Query
public T getCachedResult (PersistenceContext ctx)
{
WhereClause where = _select.getWhereClause();
if (where != null && where instanceof CacheKey) {
return (CacheKey) where;
CacheKey key = getCacheKey();
if (key == null) {
return null;
}
return null;
T value = ctx.<T>cacheLookup(key);
if (value == null) {
return null;
}
_cachedRecords = 1;
// we do not want to return a reference to the actual cached entity so we clone it
@SuppressWarnings("unchecked") T cvalue = (T) value.clone();
return cvalue;
}
// from Query
public T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException
// from interface Query
public T invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
PreparedStatement stmt = _builder.prepare(conn);
try {
// load up the record in question
T result = null;
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
@@ -71,6 +84,25 @@ public class FindOneQuery<T extends PersistentRecord>
}
// TODO: if (rs.next()) issue warning?
rs.close();
// potentially cache the result
CacheKey key = getCacheKey();
if (key == null) {
// no row-specific cache key was given, if we can, create a key from the record
if (result != null && _marsh.hasPrimaryKey()) {
key = _marsh.getPrimaryKey(result);
}
}
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded " + (key != null ? key : _marsh.getTableName()));
}
if (key != null) {
ctx.cacheStore(key, (result != null) ? result.clone() : null);
if (PersistenceContext.CACHE_DEBUG) {
log.info("Cached " + key);
}
}
return result;
} finally {
@@ -78,33 +110,20 @@ public class FindOneQuery<T extends PersistentRecord>
}
}
// from Query
public void updateCache (PersistenceContext ctx, T result)
// from interface Operation
public void updateStats (Stats stats)
{
CacheKey key = getCacheKey();
if (key == null) {
// no row-specific cache key was given
if (result == null || !_marsh.hasPrimaryKey()) {
return;
}
// if we can, create a key from what was actually returned
key = _marsh.getPrimaryKey(result);
}
ctx.cacheStore(key, (result != null) ? result.clone() : null);
stats.noteQuery(0, 0, _cachedRecords, 1-_cachedRecords);
}
// from Query
public T transformCacheHit (CacheKey key, T value)
protected CacheKey getCacheKey ()
{
if (value == null) {
return null;
}
// we do not want to return a reference to the actual cached entity so we clone it
@SuppressWarnings("unchecked") T cvalue = (T) value.clone();
return cvalue;
WhereClause where = _select.getWhereClause();
return (where != null && where instanceof CacheKey) ? (CacheKey)where : null;
}
protected DepotMarshaller<T> _marsh;
protected SelectClause<T> _select;
protected SQLBuilder _builder;
protected int _cachedRecords;
}
+15 -7
View File
@@ -199,6 +199,20 @@ public class Key<T extends PersistentRecord> extends WhereClause
ctx.cacheInvalidate(this);
}
/**
* Appends just the key=value portion of our {@link #toString} to the supplied buffer.
*/
public void toShortString (StringBuilder builder)
{
String[] keyFields = KeyUtil.getKeyFields(_pClass);
for (int ii = 0; ii < keyFields.length; ii ++) {
if (ii > 0) {
builder.append(":");
}
builder.append(keyFields[ii]).append("=").append(_values.get(ii));
}
}
@Override // from WhereClause
public void validateQueryType (Class<?> pClass)
{
@@ -229,13 +243,7 @@ public class Key<T extends PersistentRecord> extends WhereClause
{
StringBuilder builder = new StringBuilder(_pClass.getSimpleName());
builder.append("(");
String[] keyFields = KeyUtil.getKeyFields(_pClass);
for (int ii = 0; ii < keyFields.length; ii ++) {
if (ii > 0) {
builder.append(", ");
}
builder.append(keyFields[ii]).append("=").append(_values.get(ii));
}
toShortString(builder);
builder.append(")");
return builder.toString();
}
+20 -21
View File
@@ -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
@@ -41,8 +41,8 @@ public abstract class Modifier implements Operation<Integer>
super(null);
}
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
@Override // from Modifier
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
Statement stmt = conn.createStatement();
try {
return stmt.executeUpdate(createQuery(liaison));
@@ -86,25 +86,21 @@ public abstract class Modifier implements Operation<Integer>
}
@Override // from Modifier
public void cacheUpdate (PersistenceContext ctx)
public Integer invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
super.cacheUpdate(ctx);
Integer rows = super.invoke(ctx, conn, liaison);
// if we have both a key and a record, cache
if (_key != null && _result != null) {
ctx.cacheStore(_key, _result.clone());
}
return rows;
}
protected CacheKey _key;
protected T _result;
}
/**
* Overriden to perform the actual database modifications represented by this object; should
* return the number of modified rows.
*/
public abstract Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException;
/**
* Constructs a {@link Modifier} without a cache invalidator.
*/
@@ -121,24 +117,27 @@ public abstract class Modifier implements Operation<Integer>
_invalidator = invalidator;
}
/**
* Do any cache invalidation needed for this modification. This method is called just
* before the database statement is executed.
*/
public void cacheInvalidation (PersistenceContext ctx)
// from interface Operation
public Integer invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
if (_invalidator != null) {
_invalidator.invalidate(ctx);
}
return invoke(conn, liaison);
}
// from interface Operation
public void updateStats (Stats stats)
{
// nothing to update for modifiers
}
/**
* Do any cache updates needed for this modification. This method is called just after
* the successful execution of the database statement.
* Overriden to perform the actual database modifications represented by this object; should
* return the number of modified rows.
*/
public void cacheUpdate (PersistenceContext ctx)
{
}
protected abstract int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException;
protected CacheInvalidator _invalidator;
}
+6 -1
View File
@@ -33,6 +33,11 @@ public interface Operation<T>
/**
* Performs the actual JDBC interactions associated with this operation.
*/
public T invoke (Connection conn, DatabaseLiaison liaison)
public T invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Called after the operation has been invoked so that it can update our runtime statistics.
*/
public void updateStats (Stats stats);
}
@@ -53,6 +53,9 @@ public class PersistenceContext
/** Allow toggling of query logging and other debug output via a system property. */
public static final boolean DEBUG = Boolean.getBoolean("com.samskivert.depot.debug");
/** Allow toggling of cache-related logging via a system property. */
public static final boolean CACHE_DEBUG = Boolean.getBoolean("com.samskivert.depot.cache_debug");
/** Map {@link TableGenerator} instances by name. */
public Map<String, TableGenerator> tableGenerators = Maps.newHashMap();
@@ -173,6 +176,14 @@ public class PersistenceContext
_conprov.shutdown();
}
/**
* Returns a snapshot of our current runtime statistics.
*/
public Stats.Snapshot getStats ()
{
return _stats.getSnapshot();
}
/**
* Create and return a new {@link SQLBuilder} for the appropriate dialect.
*
@@ -258,26 +269,14 @@ public class PersistenceContext
public <T> T invoke (Query<T> query)
throws DatabaseException
{
CacheKey key = query.getCacheKey();
// if there is a cache key, check the cache
if (key != null && _cache != null) {
CacheAdapter.CachedValue<T> cacheHit = cacheLookup(key);
if (cacheHit != null) {
log.debug("cache hit [key=" + key + ", hit=" + cacheHit + "]");
T value = cacheHit.getValue();
value = query.transformCacheHit(key, value);
if (value != null) {
return value;
}
log.debug("transformCacheHit returned null; rejecting cached value.");
}
log.debug("cache miss [key=" + key + "]");
// we check to see if the query is already cached before invoking it to avoid requesting a
// database connection if we don't actually need one
T result = query.getCachedResult(this);
if (result != null) {
query.updateStats(_stats);
return result;
}
// otherwise, perform the query
T result = invoke(query, true);
// and let the caller figure out if it wants to cache itself somehow
query.updateCache(this, result);
return result;
return invoke(query, true);
}
/**
@@ -286,12 +285,7 @@ public class PersistenceContext
public int invoke (Modifier modifier)
throws DatabaseException
{
modifier.cacheInvalidation(this);
int rows = invoke(modifier, true);
if (rows > 0) {
modifier.cacheUpdate(this);
}
return rows;
return invoke(modifier, true);
}
/**
@@ -305,13 +299,14 @@ public class PersistenceContext
/**
* Looks up an entry in the cache by the given key.
*/
public <T> CacheAdapter.CachedValue<T> cacheLookup (CacheKey key)
public <T> T cacheLookup (CacheKey key)
{
if (_cache == null) {
return null;
}
CacheAdapter.CacheBin<T> bin = _cache.getCache(key.getCacheId());
return bin.lookup(key.getCacheKey());
CacheAdapter.CachedValue<T> ref = bin.lookup(key.getCacheKey());
return (ref == null) ? null : ref.getValue();
}
/**
@@ -530,6 +525,7 @@ public class PersistenceContext
boolean isReadOnly = !(op instanceof Modifier);
Connection conn;
long preConnect = System.nanoTime();
try {
conn = _conprov.getConnection(_ident, isReadOnly);
} catch (PersistenceException pe) {
@@ -541,10 +537,15 @@ public class PersistenceContext
// connection pooling which will block in getConnection() instead of returning a connection
// that someone else may be using
synchronized (conn) {
long preInvoke = System.nanoTime();
try {
// if this becomes more complex than this single statement, then this should turn
// into a method call that contains the complexity
return op.invoke(conn, _liaison);
// invoke our database operation
T value = op.invoke(this, conn, _liaison);
// note the time it took to invoke this operation
_stats.noteOp(isReadOnly, preConnect, preInvoke, System.nanoTime());
// have the operation update any appropriate runtime statistics as well
op.updateStats(_stats);
return value;
} catch (SQLException sqe) {
if (!isReadOnly) {
@@ -594,6 +595,9 @@ public class PersistenceContext
protected DatabaseLiaison _liaison;
protected boolean _warnOnLazyInit;
/** Used to track various statistics. */
protected Stats _stats = new Stats();
/** The object through which all our caching is relayed, or null, for no caching. */
protected CacheAdapter _cache;
+7 -25
View File
@@ -34,40 +34,22 @@ public interface Query<T> extends Operation<T>
/** A simple base class for non-complex queries. */
public abstract class Trivial<T> implements Query<T>
{
public abstract T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException;
public abstract T invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException;
public CacheKey getCacheKey () {
public T getCachedResult (PersistenceContext ctx) {
return null;
}
public T transformCacheHit (CacheKey key, T value) {
return value;
}
public void updateCache (PersistenceContext ctx, T result) {
}
}
/**
* Any query may elect to utilize the built-in cache by returning a non-null {@link CacheKey}
* in this method. This is done automatically by the {@link DepotRepository} when looking up
* single entities by primary key, but even entire collections can be cached under a single
* key.
*
* Great care must be taken to invalidate such cached collections when their constituent
* entities are invalidated. This is generally done using {@link CacheListener} and
* {@link CacheInvalidator}.
* If this query has a simple cached result, it should return the non-null result from this
* method. If null is returned, the query will be {@link #invoke}d to obtain its result from
* persistent storage.
*/
public CacheKey getCacheKey ();
/**
* Overriden by subclasses to perform special operations when the query would return a cache
* hit. The value may be mutated, modified, or null may be return to force a database hit.
*/
public T transformCacheHit (CacheKey key, T value);
/**
* Overriden by subclasses to perform case-by-case cache updates.
*/
public void updateCache (PersistenceContext ctx, T result);
public T getCachedResult (PersistenceContext ctx);
}
@@ -53,7 +53,7 @@ public abstract class SchemaMigration extends Modifier
}
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
if (!liaison.tableContainsColumn(conn, _tableName, _columnName)) {
// we'll accept this inconsistency
log.warning(_tableName + "." + _columnName + " already dropped.");
@@ -78,8 +78,12 @@ public abstract class SchemaMigration extends Modifier
_newColumnName = newColumnName;
}
@Override public boolean runBeforeDefault () {
return true;
}
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
if (!liaison.tableContainsColumn(conn, _tableName, _oldColumnName)) {
if (liaison.tableContainsColumn(conn, _tableName, _newColumnName)) {
// we'll accept this inconsistency
@@ -104,10 +108,6 @@ public abstract class SchemaMigration extends Modifier
conn, _tableName, _oldColumnName, _newColumnName, _newColumnDef) ? 1 : 0;
}
@Override public boolean runBeforeDefault () {
return true;
}
@Override
protected void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
super.init(tableName, marshallers);
@@ -135,18 +135,18 @@ public abstract class SchemaMigration extends Modifier
_fieldName = fieldName;
}
@Override public boolean runBeforeDefault () {
return false;
}
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
log.info("Updating type of '" + _fieldName + "' in " + _tableName);
return liaison.changeColumn(conn, _tableName, _fieldName, _newColumnDef.getType(),
_newColumnDef.isNullable(), _newColumnDef.isUnique(),
_newColumnDef.getDefaultValue()) ? 1 : 0;
}
@Override public boolean runBeforeDefault () {
return false;
}
@Override
protected void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
super.init(tableName, marshallers);
@@ -174,8 +174,12 @@ public abstract class SchemaMigration extends Modifier
_defaultValue = defaultValue;
}
@Override public boolean runBeforeDefault () {
return true;
}
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// override the default value in the column definition with the one provided
ColumnDefinition defColumnDef = new ColumnDefinition(
_newColumnDef.getType(), _newColumnDef.isNullable(),
@@ -190,10 +194,6 @@ public abstract class SchemaMigration extends Modifier
return 0;
}
@Override public boolean runBeforeDefault () {
return true;
}
@Override
protected void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
super.init(tableName, marshallers);
+138
View File
@@ -0,0 +1,138 @@
//
// $Id$
//
// 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.depot;
import com.samskivert.util.Histogram;
import static com.samskivert.depot.Log.log;
/**
* Manages Depot performance statistics.
*/
public class Stats
{
/**
* An immutable class used to report statistics on repository activity. Statistics are tracked
* from the start of the VM and are never reset.
*/
public static class Snapshot
{
/** The total number of queries and modifiers executed. */
public final int totalOps;
/** The total number of milliseconds spent waiting for a JDBC connection. */
public final long connectionWaitTime;
/** The total number of collection queries that were loaded from the cache. */
public final int cachedQueries;
/** The total number of collection queries that were loaded from the database. */
public final int uncachedQueries;
/** The number of record loads (individual or as part of a collection query) that were
* loaded from the cache. */
public final long cachedRecords;
/** The number of record loads (individual or as part of a collection query) that were
* loaded from the database. */
public final long uncachedRecords;
/** A histogram of query durations (with 500ms buckets). */
public final Histogram queryHisto;
/** The total number of milliseconds spent executing queries. */
public final long queryTime;
/** A histogram of modifier durations (with 500ms buckets). */
public final Histogram modifierHisto;
/** The total number of milliseconds spent executing modifiers. */
public final long modifierTime;
/** Creates a stats instance. */
protected Snapshot (int totalOps, long connectionWaitTime,
int cachedQueries, int uncachedQueries,
int cachedRecords, int uncachedRecords,
Histogram queryHisto, long queryTime,
Histogram modifierHisto, long modifierTime)
{
this.totalOps = totalOps;
this.connectionWaitTime = connectionWaitTime;
this.cachedQueries = cachedQueries;
this.uncachedQueries = uncachedQueries;
this.cachedRecords = cachedRecords;
this.uncachedRecords = uncachedRecords;
this.queryHisto = queryHisto;
this.queryTime = queryTime;
this.modifierHisto = modifierHisto;
this.modifierTime = modifierTime;
}
}
public synchronized Snapshot getSnapshot ()
{
return new Snapshot(_totalOps, _connectionWaitTime,
_cachedQueries, _uncachedQueries, _cachedRecords, _uncachedRecords,
_readHisto.clone(), _readTime, _writeHisto.clone(), _writeTime);
}
public synchronized void noteOp (
boolean isReadOnly, long preConnect, long preInvoke, long postInvoke)
{
_totalOps++;
_connectionWaitTime += (preInvoke - preConnect) / 1000L;
long opTime = (postInvoke - preInvoke) / 1000L;
if (opTime > Integer.MAX_VALUE) {
log.warning("ZOMG! A database operation took " + opTime + "ms to complete!");
opTime = Integer.MAX_VALUE;
}
if (isReadOnly) {
_readTime += opTime;
_readHisto.addValue((int)opTime);
} else {
_writeTime += opTime;
_writeHisto.addValue((int)opTime);
}
}
public synchronized void noteQuery (int cachedQueries, int uncachedQueries,
int cachedRecords, int uncachedRecords)
{
_cachedQueries += cachedQueries;
_uncachedQueries += uncachedQueries;
_cachedRecords += cachedRecords;
_uncachedRecords += uncachedRecords;
}
protected int _totalOps;
protected long _connectionWaitTime;
protected Histogram _readHisto = new Histogram(0, 500, 20);
protected long _readTime;
protected Histogram _writeHisto = new Histogram(0, 500, 20);
protected long _writeTime;
protected int _cachedQueries, _uncachedQueries;
protected int _cachedRecords, _uncachedRecords;
}
@@ -195,6 +195,32 @@ public class SelectClause<T extends PersistentRecord> extends QueryClause
builder.visit(this);
}
@Override // from Object
public String toString ()
{
StringBuilder builder = new StringBuilder();
builder.append("(where=").append(_where);
if (_fromOverride != null) {
builder.append(", from=").append(_fromOverride);
}
if (!_joinClauses.isEmpty()) {
builder.append(", join=").append(_joinClauses);
}
if (_orderBy != null) {
builder.append(", orderBy=").append(_orderBy);
}
if (_groupBy != null) {
builder.append(", groupBy=").append(_groupBy);
}
if (_limit != null) {
builder.append(", limit=").append(_limit);
}
if (_forUpdate != null) {
builder.append(", forUpdate=").append(_forUpdate);
}
return builder.append(")").toString();
}
/** Persistent class fields mapped to field override clauses. */
protected Map<String, FieldDefinition> _disMap = Maps.newHashMap();
@@ -85,6 +85,12 @@ public class Where extends WhereClause
_condition.addClasses(classSet);
}
@Override // from Object
public String toString ()
{
return String.valueOf(_condition);
}
protected static SQLExpression toCondition (ColumnExp[] columns, Comparable<?>[] values)
{
SQLExpression[] comparisons = new SQLExpression[columns.length];
@@ -59,6 +59,12 @@ public class ColumnExp
return _pField;
}
@Override // from Object
public String toString ()
{
return "\"" + _pField + "\""; // TODO: qualify with record name and be uber verbose?
}
/** The table that hosts the column we reference, or null. */
protected final Class<? extends PersistentRecord> _pClass;
@@ -51,6 +51,12 @@ public class ValueExp
return _value;
}
@Override // from Object
public String toString ()
{
return (_value instanceof Number) ? String.valueOf(_value) : ("'" + _value + "'");
}
/** The value to be bound to the SQL parameters. */
protected Object _value;
}
@@ -69,6 +69,12 @@ public abstract class Conditionals
{
}
@Override // from Object
public String toString ()
{
return "IsNull(" + _column + ")";
}
protected ColumnExp _column;
}
@@ -240,6 +246,20 @@ public abstract class Conditionals
_column.addClasses(classSet);
}
@Override // from Object
public String toString ()
{
StringBuilder builder = new StringBuilder();
builder.append(_column).append(" in (");
for (int ii = 0; ii < _values.length; ii++) {
if (ii > 0) {
builder.append(", ");
}
builder.append(_values[ii]);
}
return builder.append(")").toString();
}
protected ColumnExp _column;
protected Comparable<?>[] _values;
}
@@ -286,6 +306,12 @@ public abstract class Conditionals
return _clause;
}
@Override // from Object
public String toString ()
{
return "Exists(" + _clause + ")";
}
protected SelectClause<T> _clause;
}
@@ -329,6 +355,12 @@ public abstract class Conditionals
{
}
@Override // from Object
public String toString ()
{
return "FullText(" + _name + "=" + _query + ")";
}
protected Class<? extends PersistentRecord> _pClass;
protected String _name;
protected String _query;
@@ -49,7 +49,7 @@ public abstract class Logic
@Override public String operator()
{
return "or";
return " or ";
}
}
@@ -70,7 +70,7 @@ public abstract class Logic
@Override public String operator()
{
return "and";
return " and ";
}
}
@@ -102,6 +102,12 @@ public abstract class Logic
_condition.addClasses(classSet);
}
@Override // from Object
public String toString ()
{
return "Not(" + _condition + ")";
}
protected SQLExpression _condition;
}
}
@@ -69,6 +69,19 @@ public interface SQLOperator extends SQLExpression
*/
public abstract String operator ();
@Override // from Object
public String toString ()
{
StringBuilder builder = new StringBuilder("(");
for (SQLExpression condition : _conditions) {
if (builder.length() > 1) {
builder.append(operator());
}
builder.append(condition);
}
return builder.append(")").toString();
}
protected SQLExpression[] _conditions;
}
@@ -116,6 +129,12 @@ public interface SQLOperator extends SQLExpression
return _rhs;
}
@Override // from Object
public String toString ()
{
return "(" + _lhs + operator() + _rhs + ")";
}
protected SQLExpression _lhs;
protected SQLExpression _rhs;
}