Caching support from Zell. Hard drives the world round rejoice!

This commit is contained in:
Michael Bayne
2007-01-11 00:43:08 +00:00
parent 58439d0285
commit 8f4b37d9dc
30 changed files with 1322 additions and 453 deletions
@@ -0,0 +1,35 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne, 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.jdbc.depot;
/**
* Implementors of this interface performs perform cache invalidation for calls to
* {@link DepotRepository#updateLiteral}, {@link DepotRepository#updatePartial} and
* {@link DepotRepository#deleteAll).
*/
public interface CacheInvalidator
{
/**
* Must invalidate all cache entries that depend on the records being modified or deleted.
* This method is called just before the database statement is executed.
*/
public void invalidate (PersistenceContext ctx);
}
@@ -0,0 +1,42 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne, 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.jdbc.depot;
import java.io.Serializable;
/**
* This interface uniquely identifies a single persistent entry for caching purposes.
* Queries that are given a {@link CacheKey} consult the cache before they hit the
* database.
*/
public interface CacheKey
{
/**
* Returns the id of the cache in whose scope this key makes sense.
*/
public String getCacheId ();
/**
* Returns the actual opaque serializable cache key under which results are stored
* in the cache identified by {@link #getCacheId}.
*/
public Serializable getCacheKey ();
}
@@ -0,0 +1,302 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne
//
// 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.jdbc.depot;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.annotation.Computed;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.ForUpdate;
import com.samskivert.jdbc.depot.clause.FromOverride;
import com.samskivert.jdbc.depot.clause.GroupBy;
import com.samskivert.jdbc.depot.clause.Join;
import com.samskivert.jdbc.depot.clause.Limit;
import com.samskivert.jdbc.depot.clause.OrderBy;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.Where;
/**
* Encapsulates a non-modifying query of persistent objects.
*/
public abstract class ConstructedQuery<T>
implements Query<T>
{
/**
* Maps a class referenced by this query to its associated table.
*
* This method is called by individual clauses.
*/
public String getTableName (Class cl)
{
return _classMap.get(cl).getTableName();
}
/**
* Maps a class referenced by this query to the abbreviation used for its associated table. The
* abbreviation is transientand only has meaning within the scope of this particular query.
*
* This method is called by individual clauses.
*/
public String getTableAbbreviation (Class cl)
{
int ix = _classList.indexOf(cl);
if (ix < 0) {
throw new IllegalArgumentException("Unknown persistence class: " + cl);
}
return "T" + (ix+1);
}
// from Query
public CacheKey getCacheKey ()
{
if (_where != null && _where instanceof CacheKey) {
return (CacheKey) _where;
}
return null;
}
/**
* Translates the data in this Query object into a SQL query suited to instantiate a persistent
* object given the supplied key and clauses. These clauses are assumed to contain no
* conflicts.
*
* If no key is supplied all instances will be loaded.
*/
public PreparedStatement createQuery (Connection conn)
throws SQLException
{
if (_mainType == null) { // internal error
throw new RuntimeException("createQuery() called with _mainClass == null");
}
DepotMarshaller<?> mainMarshaller = _classMap.get(_mainType);
String[] fields = mainMarshaller._allFields;
StringBuilder query = new StringBuilder("select ");
boolean skip = true;
for (int ii = 0; ii < fields.length; ii ++) {
if (!skip) {
query.append(", ");
}
skip = false;
FieldOverride clause = _disMap.get(fields[ii]);
if (clause != null) {
clause.appendClause(this, query);
continue;
}
Computed computed = mainMarshaller._fields.get(fields[ii]).getComputed();
if (computed == null) {
// make sure the object corresponds to a table, otherwise the whole thing is
// computed
if (mainMarshaller.getTableName() != null) {
// if it's neither overridden nor computed, it's a standard field
query.append(getTableAbbreviation(_mainType)).append(".").append(fields[ii]);
continue;
}
throw new SQLException(
"@Computed entity field without definition [field=" + fields[ii] + "]");
}
// check if the computed field has a literal SQL definition
if (computed.fieldDefinition().length() > 0) {
query.append(computed.fieldDefinition() + " as " + fields[ii]);
} else if (!computed.required()) {
// or if we can simply ignore the field
skip = true;
} else {
throw new SQLException(
"@Computed(required) field without definition [field=" + fields[ii] + "]");
}
}
if (_fromOverride != null) {
_fromOverride.appendClause(this, query);
} else if (mainMarshaller.getTableName() != null) {
query.append(" from ").append(mainMarshaller.getTableName());
query.append(" as ").append(getTableAbbreviation(_mainType));
} else {
throw new SQLException("Query on @Computed entity with no FromOverrideClause.");
}
for (Join clause : _joinClauses) {
clause.appendClause(this, query);
}
if (_where != null) {
_where.appendClause(this, query);
}
if (_groupBy != null) {
_groupBy.appendClause(this, query);
}
if (_orderBy != null) {
_orderBy.appendClause(this, query);
}
if (_limit != null) {
_limit.appendClause(this, query);
}
if (_forUpdate != null) {
_forUpdate.appendClause(this, query);
}
PreparedStatement pstmt = conn.prepareStatement(query.toString());
int argIdx = 1;
for (Join clause : _joinClauses) {
argIdx = clause.bindArguments(pstmt, argIdx);
}
if (_where != null) {
argIdx = _where.bindArguments(pstmt, argIdx);
}
if (_groupBy != null) {
argIdx = _groupBy.bindArguments(pstmt, argIdx);
}
if (_orderBy != null) {
argIdx = _orderBy.bindArguments(pstmt, argIdx);
}
if (_limit != null) {
argIdx = _limit.bindArguments(pstmt, argIdx);
}
if (_forUpdate != null) {
argIdx = _forUpdate.bindArguments(pstmt, argIdx);
}
return pstmt;
}
/**
* Creates a new Query object to generate one or more instances of the specified persistent
* class, as dictated by the key and query clauses. A persistence context is supplied for
* instantiation of marshallers, which may trigger table creations and schema migrations.
*/
protected ConstructedQuery (PersistenceContext ctx, Class type, QueryClause... clauses)
throws PersistenceException
{
_mainType = type;
Set<Class> classSet = new HashSet<Class>();
if (type != null) {
classSet.add(type);
}
for (QueryClause clause : clauses) {
if (clause instanceof Where) {
if (_where != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Where clauses.");
}
_where = (Where) clause;
} else if (clause instanceof FromOverride) {
if (_fromOverride != null) {
throw new IllegalArgumentException(
"Query can't contain multiple FromOverride clauses.");
}
_fromOverride = (FromOverride) clause;
classSet.addAll(_fromOverride.getClassSet());
} else if (clause instanceof Join) {
_joinClauses.add((Join) clause);
classSet.addAll(((Join) clause).getClassSet());
} else if (clause instanceof FieldOverride) {
_disMap.put(((FieldOverride) clause).getField(),
((FieldOverride) clause));
} else if (clause instanceof OrderBy) {
if (_orderBy != null) {
throw new IllegalArgumentException(
"Query can't contain multiple OrderBy clauses.");
}
_orderBy = (OrderBy) clause;
} else if (clause instanceof GroupBy) {
if (_groupBy != null) {
throw new IllegalArgumentException(
"Query can't contain multiple GroupBy clauses.");
}
_groupBy = (GroupBy) clause;
} else if (clause instanceof Limit) {
if (_limit != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Limit clauses.");
}
_limit = (Limit) clause;
} else if (clause instanceof ForUpdate) {
if (_forUpdate != null) {
throw new IllegalArgumentException(
"Query can't contain multiple For Update clauses.");
}
_forUpdate = (ForUpdate) clause;
}
}
_classMap = new HashMap<Class, DepotMarshaller>();
for (Class<?> c : classSet) {
_classMap.put(c, ctx.getMarshaller(c));
}
_classList = new ArrayList<Class>(classSet);
}
/** The persistent class to instantiate for the results. */
protected Class _mainType;
/** A list of referenced classes, used to generate table abbreviations. */
protected List<Class> _classList;
/** Classes mapped to marshallers, used for table names and field lists. */
protected Map<Class, DepotMarshaller> _classMap;
/** Persistent class fields mapped to field override clauses. */
protected Map<String, FieldOverride> _disMap = new HashMap<String, FieldOverride>();
/** The from override clause, if any. */
protected FromOverride _fromOverride;
/** The where clause. */
protected Where _where;
/** A list of join clauses, each potentially referencing a new class. */
protected List<Join> _joinClauses = new ArrayList<Join>();
/** The order by clause, if any. */
protected OrderBy _orderBy;
/** The group by clause, if any. */
protected GroupBy _groupBy;
/** The limit clause, if any. */
protected Limit _limit;
/** The For Update clause, if any. */
protected ForUpdate _forUpdate;
}
@@ -47,7 +47,6 @@ import com.samskivert.jdbc.depot.annotation.Transient;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;
@@ -255,11 +254,11 @@ public class DepotMarshaller<T>
* Returns a key configured with the primary key of the supplied object. Throws an exception
* if the persistent object did not declare a primary key.
*/
public Key getPrimaryKey (Object object)
public Key<T> getPrimaryKey (Object object)
{
if (!hasPrimaryKey()) {
throw new UnsupportedOperationException(
getClass().getName() + " does not define a primary key");
_pclass.getName() + " does not define a primary key");
}
try {
Comparable[] values = new Comparable[_pkColumns.size()];
@@ -277,23 +276,17 @@ public class DepotMarshaller<T>
* Creates a primary key record for the type of object handled by this marshaller, using the
* supplied primary key value.
*/
public Key makePrimaryKey (Comparable... values)
public Key<T> makePrimaryKey (Comparable... values)
{
if (!hasPrimaryKey()) {
throw new UnsupportedOperationException(
getClass().getName() + " does not define a primary key");
}
if (values.length != _pkColumns.size()) {
throw new IllegalArgumentException(
"Argument count (" + values.length + ") must match primary key size (" +
_pkColumns.size() + ")");
}
ColumnExp[] columns = new ColumnExp[_pkColumns.size()];
String[] columns = new String[_pkColumns.size()];
for (int ii = 0; ii < _pkColumns.size(); ii++) {
FieldMarshaller field = _pkColumns.get(ii);
columns[ii] = new ColumnExp(_pclass, field.getColumnName());
columns[ii] = _pkColumns.get(ii).getColumnName();
}
return new Key(columns, values);
return new Key<T>(_pclass, columns, values);
}
/**
@@ -552,7 +545,7 @@ public class DepotMarshaller<T>
/**
* Initializes the table used by this marshaller. This is called automatically by the {@link
* PersistenceContext} the first time an entity is used. If the table does not exist, it will
* PersistenceContext} the first time an entity is used. If the table does not exist, it will
* be created. If the schema version specified by the persistent object is newer than the
* database schema, it will be migrated.
*/
@@ -571,7 +564,7 @@ public class DepotMarshaller<T>
}
// check to see if our schema version table exists, create it if not
ctx.invoke(new Modifier(null) {
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
JDBCUtil.createTableIfMissing(
conn, SCHEMA_VERSION_TABLE,
@@ -582,7 +575,7 @@ public class DepotMarshaller<T>
});
// now create the table for our persistent class if it does not exist
ctx.invoke(new Modifier(null) {
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
if (!JDBCUtil.tableExists(conn, getTableName())) {
log.info("Creating table " + getTableName() + " (" + _declarations + ") " +
@@ -597,7 +590,7 @@ public class DepotMarshaller<T>
// if we have a key generator, initialize that too
if (_keyGenerator != null) {
ctx.invoke(new Modifier(null) {
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
_keyGenerator.init(conn);
return 0;
@@ -611,7 +604,7 @@ public class DepotMarshaller<T>
}
// make sure the versions match
int currentVersion = ctx.invoke(new Modifier(null) {
int currentVersion = ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
String query = "select version from " + SCHEMA_VERSION_TABLE +
" where persistentClass = '" + getTableName() + "'";
@@ -643,7 +636,7 @@ public class DepotMarshaller<T>
// enumerate all of the columns now that we've run our pre-migrations
final HashSet<String> columns = new HashSet<String>();
final HashSet<String> indices = new HashSet<String>();
ctx.invoke(new Modifier(null) {
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
DatabaseMetaData meta = conn.getMetaData();
ResultSet rs = meta.getColumns(null, null, getTableName(), "%");
@@ -730,7 +723,7 @@ public class DepotMarshaller<T>
}
// record our new version in the database
ctx.invoke(new Modifier(null) {
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
updateVersion(conn, _schemaVersion);
return 0;
@@ -29,8 +29,13 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.ConnectionProvider;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.Modifier.*;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.util.ArrayUtil;
@@ -68,6 +73,38 @@ public class DepotRepository
return load(type, clauses);
}
/**
* Loads the persistent object that matches the specified primary key.
*/
protected <T> T load (Class<T> type, String ix, Comparable val, QueryClause... clauses)
throws PersistenceException
{
clauses = ArrayUtil.append(clauses, new Key<T>(type, ix, val));
return load(type, clauses);
}
/**
* Loads the persistent object that matches the specified two-column primary key.
*/
protected <T> T load (Class<T> type, String ix1, Comparable val1, String ix2,
Comparable val2, QueryClause... clauses)
throws PersistenceException
{
clauses = ArrayUtil.append(clauses, new Key<T>(type, ix1, val1, ix2, val2));
return load(type, clauses);
}
/**
* Loads the persistent object that matches the specified three-column primary key.
*/
protected <T> T load (Class<T> type, String ix1, Comparable val1, String ix2,
Comparable val2, String ix3, Comparable val3, QueryClause... clauses)
throws PersistenceException
{
clauses = ArrayUtil.append(clauses, new Key<T>(type, ix1, val1, ix2, val2, ix3, val3));
return load(type, clauses);
}
/**
* Loads the first persistent object that matches the supplied key.
*/
@@ -75,7 +112,7 @@ public class DepotRepository
throws PersistenceException
{
final DepotMarshaller<T> marsh = _ctx.getMarshaller(type);
return _ctx.invoke(new Query<T>(_ctx, type, clauses) {
return _ctx.invoke(new ConstructedQuery<T>(_ctx, type, clauses) {
public T invoke (Connection conn) throws SQLException {
PreparedStatement stmt = createQuery(conn);
try {
@@ -89,9 +126,22 @@ public class DepotRepository
return result;
} finally {
stmt.close();
JDBCUtil.close(stmt);
}
}
public void updateCache (PersistenceContext ctx, T result) {
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);
}
});
}
@@ -103,7 +153,7 @@ public class DepotRepository
throws PersistenceException
{
final DepotMarshaller<T> marsh = _ctx.getMarshaller(type);
return _ctx.invoke(new Query<ArrayList<T>>(_ctx, type, clauses) {
return _ctx.invoke(new ConstructedQuery<ArrayList<T>>(_ctx, type, clauses) {
public ArrayList<T> invoke (Connection conn) throws SQLException {
PreparedStatement stmt = createQuery(conn);
try {
@@ -115,7 +165,15 @@ public class DepotRepository
return results;
} finally {
stmt.close();
JDBCUtil.close(stmt);
}
}
public void updateCache (PersistenceContext ctx, ArrayList<T> result) {
if (marsh.hasPrimaryKey()) {
for (T bit : result) {
ctx.cacheStore(marsh.getPrimaryKey(bit), bit);
}
}
}
});
@@ -127,11 +185,11 @@ public class DepotRepository
*
* @return the number of rows modified by this action, this should always be one.
*/
protected int insert (final Object record)
protected <T> int insert (final T record)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass());
return _ctx.invoke(new Modifier(null) {
return _ctx.invoke(new CachingModifier<T>(null, null) {
public int invoke (Connection conn) throws SQLException {
// update our modifier's key so that it can cache our results
updateKey(marsh.assignPrimaryKey(conn, record, false));
@@ -140,9 +198,10 @@ public class DepotRepository
int mods = stmt.executeUpdate();
// check again in case we have a post-factum key generator
updateKey(marsh.assignPrimaryKey(conn, record, true));
setInstance(record);
return mods;
} finally {
stmt.close();
JDBCUtil.close(stmt);
}
}
});
@@ -154,17 +213,18 @@ public class DepotRepository
*
* @return the number of rows modified by this action.
*/
protected int update (final Object record)
protected <T> int update (final T record)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass());
return _ctx.invoke(new Modifier(marsh.getPrimaryKey(record)) {
final Key key = marsh.getPrimaryKey(record);
return _ctx.invoke(new CachingModifier<T>(key, key) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = marsh.createUpdate(conn, record, _key);
PreparedStatement stmt = marsh.createUpdate(conn, record, key);
try {
return stmt.executeUpdate();
} finally {
stmt.close();
JDBCUtil.close(stmt);
}
}
});
@@ -176,17 +236,18 @@ public class DepotRepository
*
* @return the number of rows modified by this action.
*/
protected int update (final Object record, final String... modifiedFields)
protected <T> int update (final T record, final String... modifiedFields)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass());
return _ctx.invoke(new Modifier(marsh.getPrimaryKey(record)) {
final Key key = marsh.getPrimaryKey(record);
return _ctx.invoke(new CachingModifier<T>(key, key) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = marsh.createUpdate(conn, record, _key, modifiedFields);
PreparedStatement stmt = marsh.createUpdate(conn, record, key, modifiedFields);
try {
return stmt.executeUpdate();
} finally {
stmt.close();
JDBCUtil.close(stmt);
}
}
});
@@ -228,8 +289,8 @@ public class DepotRepository
protected <T> int updatePartial (Class<T> type, Comparable primaryKey, Object... fieldsValues)
throws PersistenceException
{
return updatePartial(
type, _ctx.getMarshaller(type).makePrimaryKey(primaryKey), fieldsValues);
Key<T> key = _ctx.getMarshaller(type).makePrimaryKey(primaryKey);
return updatePartial(type, key, key, fieldsValues);
}
/**
@@ -237,12 +298,15 @@ public class DepotRepository
*
* @param type the type of the persistent object to be modified.
* @param key the key to match in the update.
* @param invalidator a cache invalidator that will be prior to the update to flush the
* relevant persistent objects from the cache.
* @param fieldsValues an array containing the names of the fields/columns and the values to be
* assigned, in key, value, key, value, etc. order.
*
* @return the number of rows modified by this action.
*/
protected <T> int updatePartial (Class<T> type, Where key, Object... fieldsValues)
protected <T> int updatePartial (Class<T> type, final Where key, CacheInvalidator invalidator,
Object... fieldsValues)
throws PersistenceException
{
// separate the arguments into keys and values
@@ -254,13 +318,13 @@ public class DepotRepository
}
final DepotMarshaller marsh = _ctx.getMarshaller(type);
return _ctx.invoke(new Modifier(key) {
return _ctx.invoke(new Modifier(invalidator) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = marsh.createPartialUpdate(conn, _key, fields, values);
PreparedStatement stmt = marsh.createPartialUpdate(conn, key, fields, values);
try {
return stmt.executeUpdate();
} finally {
stmt.close();
JDBCUtil.close(stmt);
}
}
});
@@ -268,8 +332,8 @@ public class DepotRepository
/**
* Updates the specified columns for all persistent objects matching the supplied primary
* key. The values in this case must be literal SQL to be inserted into the update
* statement. In general this is used when you want to do something like the following:
* key. The values in this case must be literal SQL to be inserted into the update statement.
* In general this is used when you want to do something like the following:
*
* <pre>
* update FOO set BAR = BAR + 1;
@@ -286,14 +350,14 @@ public class DepotRepository
protected <T> int updateLiteral (Class<T> type, Comparable primaryKey, String... fieldsValues)
throws PersistenceException
{
return updateLiteral(
type, _ctx.getMarshaller(type).makePrimaryKey(primaryKey), fieldsValues);
Key<T> key = _ctx.getMarshaller(type).makePrimaryKey(primaryKey);
return updateLiteral(type, key, key, fieldsValues);
}
/**
* Updates the specified columns for all persistent objects matching the supplied primary
* key. The values in this case must be literal SQL to be inserted into the update
* statement. In general this is used when you want to do something like the following:
* key. The values in this case must be literal SQL to be inserted into the update statement.
* In general this is used when you want to do something like the following:
*
* <pre>
* update FOO set BAR = BAR + 1;
@@ -307,7 +371,8 @@ public class DepotRepository
*
* @return the number of rows modified by this action.
*/
protected <T> int updateLiteral (Class<T> type, Where key, String... fieldsValues)
protected <T> int updateLiteral (Class<T> type, final Where key, CacheInvalidator invalidator,
String... fieldsValues)
throws PersistenceException
{
// separate the arguments into keys and values
@@ -319,13 +384,13 @@ public class DepotRepository
}
final DepotMarshaller marsh = _ctx.getMarshaller(type);
return _ctx.invoke(new Modifier(key) {
return _ctx.invoke(new Modifier(invalidator) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = marsh.createLiteralUpdate(conn, _key, fields, values);
PreparedStatement stmt = marsh.createLiteralUpdate(conn, key, fields, values);
try {
return stmt.executeUpdate();
} finally {
stmt.close();
JDBCUtil.close(stmt);
}
}
});
@@ -338,24 +403,24 @@ public class DepotRepository
*
* @return the number of rows modified by this action, this should always be one.
*/
protected int store (final Object record)
protected <T> int store (final T record)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass());
Key key = marsh.hasPrimaryKey() ? marsh.getPrimaryKey(record) : null;
return _ctx.invoke(new Modifier(key) {
final Key key = marsh.hasPrimaryKey() ? marsh.getPrimaryKey(record) : null;
return _ctx.invoke(new CachingModifier<T>(key, key) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = null;
try {
// if our primary key is null or is the integer 0, assume the record has never
// before been persisted and insert
if (_key != null && !Integer.valueOf(0).equals(_key)) {
stmt = marsh.createUpdate(conn, record, _key);
if (key != null && !Integer.valueOf(0).equals(key)) {
stmt = marsh.createUpdate(conn, record, key);
int mods = stmt.executeUpdate();
if (mods > 0) {
return mods;
}
stmt.close();
JDBCUtil.close(stmt);
}
// if the update modified zero rows or the primary key was obviously unset, do
@@ -364,10 +429,11 @@ public class DepotRepository
stmt = marsh.createInsert(conn, record);
int mods = stmt.executeUpdate();
updateKey(marsh.assignPrimaryKey(conn, record, true));
setInstance(record);
return mods;
} finally {
stmt.close();
JDBCUtil.close(stmt);
}
}
});
@@ -383,8 +449,8 @@ public class DepotRepository
throws PersistenceException
{
@SuppressWarnings("unchecked") Class<T> type = (Class<T>)record.getClass();
DepotMarshaller<T> marsh = _ctx.getMarshaller(type);
return deleteAll(type, marsh.getPrimaryKey(record));
Key<T> primaryKey = _ctx.getMarshaller(type).getPrimaryKey(record);
return deleteAll(type, primaryKey, primaryKey);
}
/**
@@ -393,10 +459,11 @@ public class DepotRepository
*
* @return the number of rows deleted by this action.
*/
protected <T> int delete (Class<T> type, Comparable primaryKey)
protected <T> int delete (Class<T> type, Comparable primaryKeyValue)
throws PersistenceException
{
return deleteAll(type, _ctx.getMarshaller(type).makePrimaryKey(primaryKey));
Key<T> primaryKey = _ctx.getMarshaller(type).makePrimaryKey(primaryKeyValue);
return deleteAll(type, primaryKey, primaryKey);
}
/**
@@ -404,31 +471,41 @@ public class DepotRepository
*
* @return the number of rows deleted by this action.
*/
protected <T> int deleteAll (Class<T> type, Where key)
protected <T> int deleteAll (Class<T> type, final Where key, CacheInvalidator invalidator)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(type);
return _ctx.invoke(new Modifier(key) {
return _ctx.invoke(new Modifier(invalidator) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = marsh.createDelete(conn, _key);
PreparedStatement stmt = marsh.createDelete(conn, key);
try {
return stmt.executeUpdate();
} finally {
stmt.close();
JDBCUtil.close(stmt);
}
}
});
}
protected static abstract class CollectionQuery<T extends Collection> extends Query<T>
protected static abstract class CollectionQuery<T extends Collection> implements Query<T>
{
public CollectionQuery (PersistenceContext ctx, Class type, Key key)
public CollectionQuery (CacheKey key)
throws PersistenceException
{
super(ctx, type, key);
_key = key;
}
public abstract T invoke (Connection conn) throws SQLException;
public CacheKey getCacheKey ()
{
return _key;
}
public void updateCache (PersistenceContext ctx, T result)
{
ctx.cacheStore(_key, result);
}
protected CacheKey _key;
}
protected PersistenceContext _ctx;
@@ -113,7 +113,7 @@ public abstract class EntityMigration extends Modifier
protected EntityMigration (int targetVersion)
{
super(null);
super();
_targetVersion = targetVersion;
}
+139 -49
View File
@@ -20,71 +20,161 @@
package com.samskivert.jdbc.depot;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.ValueExp;
import com.samskivert.jdbc.depot.operator.Conditionals.Equals;
import com.samskivert.jdbc.depot.operator.Conditionals.IsNull;
import com.samskivert.jdbc.depot.operator.Logic.And;
import com.samskivert.jdbc.depot.operator.SQLOperator;
/**
* Encapsulates a key used to match persistent objects in a query. This is a special form of
* where clause that is meant to uniquely identify a specific row for caching purposes. It is
* generated by the update/insert code, as well as instantiated directly by users.
* A special form of {@link Where} clause that uniquely specifices a single database row and
* thus also a single persistent object. Because it implements both {@link CacheKey} and
* {@link CacheInvalidator} it also uniquely indexes into the cache and knows how to invalidate
* itself upon modification. This class is created by many {@link DepotMarshaller} methods as
* a convenience, and may also be instantiated explicitly.
*/
public class Key extends Where
public class Key<T> extends Where
implements CacheKey, CacheInvalidator
{
public Key (String index, Comparable value)
/**
* Constructs a new single-column {@code Key} with the given value.
*/
public Key (Class<T> pClass, String ix, Comparable val)
{
this(new ColumnExp(index), value);
this(pClass, new String[] { ix }, new Comparable[] { val });
}
public Key (ColumnExp column, Comparable value)
/**
* Constructs a new two-column {@code Key} with the given values.
*/
public Key (Class<T> pClass, String ix1, Comparable val1,
String ix2, Comparable val2)
{
this(new ColumnExp[] { column }, new Comparable[] { value });
this(pClass, new String[] { ix1, ix2 }, new Comparable[] { val1, val2 });
}
public Key (ColumnExp index1, Comparable value1,
ColumnExp index2, Comparable value2)
/**
* Constructs a new three-column {@code Key} with the given values.
*/
public Key (Class<T> pClass, String ix1, Comparable val1,
String ix2, Comparable val2, String ix3, Comparable val3)
{
this(new ColumnExp[] { index1, index2 },
new Comparable[] { value1, value2 });
this(pClass, new String[] { ix1, ix2, ix3 }, new Comparable[] { val1, val2, val3 });
}
public Key (ColumnExp index1, Comparable value1,
ColumnExp index2, Comparable value2,
ColumnExp index3, Comparable value3)
/**
* Constructs a new multi-column {@code Key} with the given values.
*
* TODO: There is no reason to store both fields and values here (and doing so probably more
* than doubles the space the key consumes in the cache). The primary key fields are known
* by the DepotMarshaller; we should simply check tha the given indices match those, and
* store the values in the order defined by the DepotMarshaller. The sanity check would be
* welcome in any case. The only problem with this is that we don't currently have access to
* a {@link PersistenceContext}. It may be that we should make this class internal to
* {@link DepotRepository} and only create it ourselves. This would require *lots* more
* convenience methods in {@link DepotRepository} though.
*/
public Key (Class<T> pClass, String[] fields, Comparable[] values)
{
this(new ColumnExp[] { index1, index2, index3 },
new Comparable[] { value1, value2, value3 });
}
// TODO: make Where an interface so we don't have to do this ugly super call
super(null);
public Key (String index1, Comparable value1, String index2, Comparable value2)
{
this(new ColumnExp(index1), value1, new ColumnExp(index2), value2);
}
public Key (String index1, Comparable value1, String index2, Comparable value2,
String index3, Comparable value3)
{
this(new ColumnExp(index1), value1, new ColumnExp(index2), value2,
new ColumnExp(index3), value3);
}
public Key (ColumnExp[] columns, Comparable[] values)
{
super(toCondition(columns, values));
}
protected static SQLOperator toCondition (ColumnExp[] columns, Comparable[] values)
{
SQLOperator[] comparisons = new SQLOperator[columns.length];
for (int ii = 0; ii < columns.length; ii ++) {
comparisons[ii] = (values[ii] == null) ?
new IsNull(columns[ii]) : new Equals(columns[ii], new ValueExp(values[ii]));
if (fields.length != values.length) {
throw new IllegalArgumentException("Field and Value arrays must be of equal length.");
}
_pClass = pClass;
_map = new HashMap<String, Comparable>();
for (int i = 0; i < fields.length; i ++) {
_map.put(fields[i], values[i]);
}
return new And(comparisons);
}
// from QueryClause
public List<Class> getClassSet()
{
return Arrays.asList(new Class[] { _pClass });
}
// from QueryClause
public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" where ");
boolean first = true;
for (Map.Entry entry : _map.entrySet()) {
if (first) {
first = false;
} else {
builder.append(" and ");
}
builder.append(entry.getKey());
builder.append(entry.getValue() == null ? " is null " : " = ? ");
}
}
// from QueryClause
public int bindArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
{
for (Map.Entry entry : _map.entrySet()) {
if (entry.getValue() != null) {
pstmt.setObject(argIdx ++, entry.getValue());
}
}
return argIdx;
}
// from CacheKey
public String getCacheId ()
{
return _pClass.getName();
}
// from CacheKey
public Serializable getCacheKey ()
{
return _map;
}
// from CacheInvalidator
public void invalidate (PersistenceContext ctx)
{
ctx.cacheInvalidate(this);
}
@Override
public int hashCode ()
{
return _pClass.hashCode() + 31 * _map.hashCode();
}
@Override
public boolean equals (Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Key other = (Key) obj;
return (_pClass == other._pClass && _map.equals(other._map));
}
@Override
public String toString ()
{
StringBuilder builder = new StringBuilder("[Key pClass=" + _pClass.getName());
for (Map.Entry entry : _map.entrySet()) {
builder.append(", ").append(entry.getKey()).append("=").append(entry.getValue());
}
builder.append("]");
return builder.toString();
}
protected Class<T> _pClass;
protected HashMap<String, Comparable> _map;
}
@@ -3,7 +3,7 @@
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne
//
//
// 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
@@ -24,15 +24,15 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import com.samskivert.jdbc.depot.clause.Where;
/**
* Encapsulates a modification of persistent objects.
*/
public abstract class Modifier
{
/** A simple modifier that executes a single SQL statement. No cache flushing is done as a
* result of this operation. */
/**
* A simple modifier that executes a single SQL statement. No cache flushing is done as a
* result of this operation.
*/
public static class Simple extends Modifier
{
public Simple (String query) {
@@ -52,19 +52,74 @@ public abstract class Modifier
protected String _query;
}
public abstract int invoke (Connection conn) throws SQLException;
protected Modifier (Where key)
/**
* A simple modifier that updates the cache with its modified object on completion. The derived
* class must call {@link #setInstance} to inform the modifier of its object at some point
* during the modification operation.
*/
public static abstract class CachingModifier<T> extends Modifier
{
_key = key;
protected CachingModifier (CacheKey key, CacheInvalidator invalidator)
{
super(invalidator);
_key = key;
}
protected void setInstance (T result)
{
_result = result;
}
protected void updateKey (CacheKey key)
{
if (key != null) {
_key = key;
}
}
@Override
public void cacheUpdate (PersistenceContext ctx)
{
super.cacheUpdate(ctx);
if (_key != null) {
ctx.cacheStore(_key, _result);
}
}
protected CacheKey _key;
protected T _result;
}
protected void updateKey (Key key)
public abstract int invoke (Connection conn) throws SQLException;
public Modifier ()
{
if (key != null) {
_key = key;
this(null);
}
public Modifier (CacheInvalidator invalidator)
{
_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)
{
if (_invalidator != null) {
_invalidator.invalidate(ctx);
}
}
protected Where _key;
/**
* Do any cache updates needed for this modification. This method is called just after
* the successful execution of the database statement.
*/
public void cacheUpdate (PersistenceContext ctx)
{
}
protected CacheInvalidator _invalidator;
}
@@ -0,0 +1,149 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne
//
// 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.jdbc.depot;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.samskivert.jdbc.depot.clause.Where;
/**
* A special form of {@link Where} clause that specifies an explicit range of database rows. It
* does not implement {@link CacheKey} but it does implement {@link CacheInvalidator} which means
* it can be sent into e.g. {@link DepotRepository#deleteAll) and have it clean up after itself.
*/
public class MultiKey<T> extends Where
implements CacheInvalidator
{
/**
* Constructs a new single-column {@code MultiKey} with the given value range.
*/
public MultiKey (Class<T> pClass, String ix, Comparable... val)
{
this(pClass, new String[0], new Comparable[0], ix, val);
}
/**
* Constructs a new two-column {@code MultiKey} with the given value range.
*/
public MultiKey (Class<T> pClass, String ix1, Comparable val1, String ix2, Comparable... val2)
{
this(pClass, new String[] { ix1 }, new Comparable[] { val1 }, ix2, val2);
}
/**
* Constructs a new three-column {@code MultiKey} with the given value range.
*/
public MultiKey (Class<T> pClass, String ix1, Comparable val1, String ix2, Comparable val2,
String ix3, Comparable... val3)
{
this(pClass, new String[] { ix1, ix2 }, new Comparable[] { val1, val2 }, ix3, val3);
}
/**
* Constructs a new multi-column {@code MultiKey} with the given value range.
* @TODO: See {@link Key#Key(Class, String[], Comparable[]) for somewhat relevant comments.
*/
public MultiKey (Class<T> pClass, String[] sFields, Comparable[] sValues,
String mField, Comparable[] mValues)
{
// TODO
super(null);
if (sFields.length != sValues.length) {
throw new IllegalArgumentException(
"Key field and values arrays must be of equal length.");
}
_pClass = pClass;
_mField = mField;
_mValues = mValues;
_map = new HashMap<String, Comparable>();
for (int i = 0; i < sFields.length; i ++) {
_map.put(sFields[i], sValues[i]);
}
}
// from QueryClause
public List<Class> getClassSet()
{
return Arrays.asList(new Class[] { _pClass });
}
// from QueryClause
public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" where ");
boolean first = true;
for (Map.Entry entry : _map.entrySet()) {
if (first) {
first = false;
} else {
builder.append(" and ");
}
builder.append(entry.getKey());
builder.append(entry.getValue() == null ? " is null " : " = ? ");
}
if (!first) {
builder.append(" and ");
}
builder.append(_mField).append(" in (");
for (int ii = 0; ii < _mValues.length; ii ++) {
if (ii > 0) {
builder.append(", ");
}
builder.append("?");
}
builder.append(")");
}
// from QueryClause
public int bindArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
{
for (Map.Entry entry : _map.entrySet()) {
if (entry.getValue() != null) {
pstmt.setObject(argIdx ++, entry.getValue());
}
}
for (int ii = 0; ii < _mValues.length; ii++) {
pstmt.setObject(argIdx ++, _mValues[ii]);
}
return argIdx;
}
// from CacheInvalidator
public void invalidate (PersistenceContext ctx)
{
HashMap<String, Comparable> newMap = new HashMap<String, Comparable>(_map);
for (int i = 0; i < _mValues.length; i ++) {
newMap.put(_mField, _mValues[i]);
ctx.cacheInvalidate(new SimpleCacheKey(_pClass, newMap));
}
}
protected Class<T> _pClass;
protected HashMap<String, Comparable> _map;
protected String _mField;
protected Comparable[] _mValues;
}
@@ -21,12 +21,20 @@
package com.samskivert.jdbc.depot;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.samskivert.jdbc.depot.annotation.TableGenerator;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.StringUtil;
@@ -44,16 +52,66 @@ public class PersistenceContext
public HashMap<String, TableGenerator> tableGenerators = new HashMap<String, TableGenerator>();
/**
* Creates a persistence context that will use the supplied provider to
* obtain JDBC connections.
* A cache listener is notified when cache entries are invalited through creation, deletion,
* or modification. Its purpose is typically to do further invalidation of dependent entries
* in other caches.
*/
public static interface CacheListener<T>
{
/**
* The given entry has just been deleted, modified or created. Do what thou wilt.
*/
public void entryModified (CacheKey key, T entry);
}
/**
* The callback for {@link #cacheTraverse}; this is called for each entry in a given cache.
*/
public static interface CacheTraverser<T extends Serializable>
{
/**
* Performs whatever cache-related tasks need doing for this cache entry. This method
* is called for each cache entry in a full-cache enumeration.
*/
public void visitCacheEntry (
PersistenceContext ctx, String cacheId, Serializable key, T record);
}
/**
* A simple implementation of {@link CacheTraverser} that selectively deletes entries in
* a cache depending on the return value of {@link #testCacheEntry}.
*/
public static abstract class CacheEvictionFilter<T extends Serializable>
implements CacheTraverser<T>
{
// from CacheTraverser
public void visitCacheEntry (
PersistenceContext ctx, String cacheId, Serializable key, T record)
{
if (testForEviction(key, record)) {
ctx.cacheInvalidate(cacheId, key);
}
}
/**
* Decides whether or not this entry should be evicted and returns true if yes, false if
* no.
*/
protected abstract boolean testForEviction (Serializable key, T record);
}
/**
* Creates a persistence context that will use the supplied provider to obtain JDBC
* connections.
*
* @param ident the identifier to provide to the connection provider when
* requesting a connection.
* @param ident the identifier to provide to the connection provider when requesting a
* connection.
*/
public PersistenceContext (String ident, ConnectionProvider conprov)
{
_ident = ident;
_conprov = conprov;
_cachemgr = CacheManager.getInstance();
}
/**
@@ -106,12 +164,28 @@ public class PersistenceContext
/**
* Invokes a non-modifying query and returns its result.
*/
@SuppressWarnings("unchecked")
public <T> T invoke (Query<T> query)
throws PersistenceException
{
// TODO: check the cache using query.getKey()
return (T) invoke(query, null, true);
CacheKey key = query.getCacheKey();
// if there is a cache key, check the cache
if (key != null) {
Cache cache = getCache(key.getCacheId());
if (cache != null) {
Element cacheHit = cache.get(key.getCacheKey());
if (cacheHit != null) {
Log.debug("invoke: cache hit [hit=" + cacheHit + "]");
@SuppressWarnings("unchecked") T value = (T) cacheHit.getValue();
return value;
}
}
Log.debug("invoke: cache miss [key=" + key + "]");
}
// otherwise, perform the query
@SuppressWarnings("unchecked") T result = (T) invoke(query, null, true);
// and let the caller figure out if it wants to cache itself somehow
query.updateCache(this, result);
return result;
}
/**
@@ -120,11 +194,143 @@ public class PersistenceContext
public int invoke (Modifier modifier)
throws PersistenceException
{
// TODO: invalidate the cache using the modifier's key
modifier.cacheInvalidation(this);
int rows = (Integer) invoke(null, modifier, true);
if (rows > 0) {
modifier.cacheUpdate(this);
}
return rows;
}
int result = (Integer) invoke(null, modifier, true);
// TODO: (optionally) cache the results of the modifier
return result;
/**
* Returns the {@link Cache} for the given cache id, or creates one if necessary.
*/
public Cache getCache (String cacheId)
{
Cache cache = _cachemgr.getCache(cacheId);
if (cache == null) {
cache = new Cache(cacheId, 5000, false, false, 600, 60);
_cachemgr.addCache(cache);
}
return cache;
}
/**
* Stores a new entry indexed by the given key.
*/
public <T> void cacheStore (CacheKey key, T value)
{
Log.debug("cacheStore: entry [key=" + key + ", value=" + value + "]");
getCache(key.getCacheId()).put(new Element(key.getCacheKey(), value));
// first do cascading invalidations
Set<CacheListener<?>> listeners = _listenerSets.get(key.getCacheId());
if (listeners != null && listeners.size() > 0) {
for (CacheListener<?> listener : listeners) {
Log.debug("cacheInvalidate: cascading [listener=" + listener + "]");
@SuppressWarnings("unchecked") CacheListener<T> casted = (CacheListener<T>)listener;
casted.entryModified(key, value);
}
}
}
/**
* Evicts the cache entry indexed under the given key, if there is one.
* The eviction may trigger further cache invalidations.
*/
public void cacheInvalidate (CacheKey key)
{
cacheInvalidate(key.getCacheId(), key.getCacheKey());
}
/**
* Evicts the cache entry indexed under the given class and cache key, if there is one.
* The eviction may trigger further cache invalidations.
*/
public void cacheInvalidate (Class pClass, Serializable cacheKey)
{
cacheInvalidate(pClass.getName(), cacheKey);
}
/**
* Evicts the cache entry indexed under the given cache id and cache key, if there is one.
* The eviction may trigger further cache invalidations.
*/
public <T extends Serializable> void cacheInvalidate (String cacheId, Serializable cacheKey)
{
Log.debug("cacheInvalidate: entry [cacheId=" + cacheId + ", cacheKey=" + cacheKey + "]");
Cache cache = getCache(cacheId);
Element element = cache.get(cacheKey);
// first do cascading invalidations
Set<CacheListener<?>> listeners = _listenerSets.get(cacheId);
if (listeners != null && listeners.size() > 0) {
CacheKey key = new SimpleCacheKey(cacheId, cacheKey);
for (CacheListener<?> listener : listeners) {
Log.debug("cacheInvalidate: cascading [listener=" + listener + "]");
@SuppressWarnings("unchecked") CacheListener<T> casted = (CacheListener<T>)listener;
@SuppressWarnings("unchecked") T value =
(element != null ? (T) element.getValue() : null);
casted.entryModified(key, value);
}
}
// then evict the keyed entry, if needed
if (element != null) {
Log.debug("cacheInvalidate: evicting [cacheKey=" + cacheKey + "]");
cache.remove(cacheKey);
}
}
/**
* Brutally iterates over the entire contents of the cache associated with the given class,
* invoking the callback for each cache entry.
*/
public <T extends Serializable> void cacheTraverse (Class pClass, CacheTraverser<T> filter)
{
cacheTraverse(pClass.getName(), filter);
}
/**
* Brutally iterates over the entire contents of the cache identified by the given cache id,
* invoking the callback for each cache entry.
*/
public <T extends Serializable> void cacheTraverse (String cacheId, CacheTraverser<T> filter)
{
Cache cache = getCache(cacheId);
if (cache != null) {
for (Object key : cache.getKeys()) {
Serializable sKey = (Serializable) key;
Element element = cache.get(sKey);
if (element != null) {
@SuppressWarnings("unchecked") T value = (T) element.getValue();
filter.visitCacheEntry(this, cacheId, sKey, value);
}
}
}
}
/**
* Registers a new cache listener with the cache associated with the given class.
*/
public <T extends Serializable> void addCacheListener (
Class<T> pClass, CacheListener<T> listener)
{
addCacheListener(pClass.getName(), listener);
}
/**
* Registers a new cache listener with the identified cache.
*/
public <T extends Serializable> void addCacheListener (
String cacheId, CacheListener<T> listener)
{
Set<CacheListener<?>> listenerSet = _listenerSets.get(cacheId);
if (listenerSet == null) {
listenerSet = new HashSet<CacheListener<?>>();
_listenerSets.put(cacheId, listenerSet);
}
listenerSet.add(listener);
}
/**
@@ -203,22 +409,24 @@ public class PersistenceContext
}
/**
* Check whether the specified exception is a transient failure
* that can be retried.
* Check whether the specified exception is a transient failure that can be retried.
*/
protected boolean isTransientException (SQLException sqe)
{
// TODO: this is MySQL specific. This was snarfed from MySQLLiaison.
String msg = sqe.getMessage();
return (msg != null &&
(msg.indexOf("Lost connection") != -1 ||
msg.indexOf("link failure") != -1 ||
msg.indexOf("Broken pipe") != -1));
return (msg != null && (msg.indexOf("Lost connection") != -1 ||
msg.indexOf("link failure") != -1 ||
msg.indexOf("Broken pipe") != -1));
}
protected String _ident;
protected ConnectionProvider _conprov;
protected CacheManager _cachemgr;
protected HashMap<Class<?>, DepotMarshaller<?>> _marshallers =
protected Map<String, Set<CacheListener<?>>> _listenerSets =
new HashMap<String, Set<CacheListener<?>>>();
protected Map<Class<?>, DepotMarshaller<?>> _marshallers =
new HashMap<Class<?>, DepotMarshaller<?>>();
}
+12 -263
View File
@@ -21,277 +21,26 @@
package com.samskivert.jdbc.depot;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.annotation.Computed;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.ForUpdate;
import com.samskivert.jdbc.depot.clause.FromOverride;
import com.samskivert.jdbc.depot.clause.GroupBy;
import com.samskivert.jdbc.depot.clause.Join;
import com.samskivert.jdbc.depot.clause.Limit;
import com.samskivert.jdbc.depot.clause.OrderBy;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.Where;
/**
* Encapsulates a non-modifying query of persistent objects.
* The base of all read-only queries.
*/
public abstract class Query<T>
public interface Query<T>
{
/**
* Returns the {@link CacheKey} associated with this query, if relevant, or null.
*/
public CacheKey getCacheKey ();
/**
* Performs the actual JDBC operations associated with this query.
*/
public abstract T invoke (Connection conn) throws SQLException;
public T invoke (Connection conn)
throws SQLException;
/**
* Maps a class referenced by this query to its associated table.
*
* This method is called by individual clauses.
* Overriden by subclasses to perform case-by-case cache updates.
*/
public String getTableName (Class cl)
{
return _classMap.get(cl).getTableName();
}
/**
* Maps a class referenced by this query to the abbreviation used for its associated table. The
* abbreviation is transientand only has meaning within the scope of this particular query.
*
* This method is called by individual clauses.
*/
public String getTableAbbreviation (Class cl)
{
int ix = _classList.indexOf(cl);
if (ix < 0) {
throw new IllegalArgumentException("Unknown persistence class: " + cl);
}
return "T" + (ix+1);
}
/**
* Translates the data in this Query object into a SQL query suited to instantiate a persistent
* object given the supplied key and clauses. These clauses are assumed to contain no
* conflicts.
*
* If no key is supplied all instances will be loaded.
*/
public <T> PreparedStatement createQuery (Connection conn)
throws SQLException
{
if (_mainType == null) { // internal error
throw new RuntimeException("createQuery() called with _mainClass == null");
}
DepotMarshaller<?> mainMarshaller = _classMap.get(_mainType);
String[] fields = mainMarshaller._allFields;
StringBuilder query = new StringBuilder("select ");
boolean skip = true;
for (int ii = 0; ii < fields.length; ii ++) {
if (!skip) {
query.append(", ");
}
skip = false;
FieldOverride clause = _disMap.get(fields[ii]);
if (clause != null) {
clause.appendClause(this, query);
continue;
}
Computed computed = mainMarshaller._fields.get(fields[ii]).getComputed();
if (computed == null) {
// make sure the object corresponds to a table, otherwise the whole thing is
// computed
if (mainMarshaller.getTableName() != null) {
// if it's neither overridden nor computed, it's a standard field
query.append(getTableAbbreviation(_mainType)).append(".").append(fields[ii]);
continue;
}
throw new SQLException(
"@Computed entity field without definition [field=" + fields[ii] + "]");
}
// check if the computed field has a literal SQL definition
if (computed.fieldDefinition().length() > 0) {
query.append(computed.fieldDefinition() + " as " + fields[ii]);
} else if (!computed.required()) {
// or if we can simply ignore the field
skip = true;
} else {
throw new SQLException(
"@Computed(required) field without definition [field=" + fields[ii] + "]");
}
}
if (_fromOverride != null) {
_fromOverride.appendClause(this, query);
} else if (mainMarshaller.getTableName() != null) {
query.append(" from ").append(mainMarshaller.getTableName());
query.append(" as ").append(getTableAbbreviation(_mainType));
} else {
throw new SQLException("Query on @Computed entity with no FromOverrideClause.");
}
for (Join clause : _joinClauses) {
clause.appendClause(this, query);
}
if (_where != null) {
_where.appendClause(this, query);
}
if (_groupBy != null) {
_groupBy.appendClause(this, query);
}
if (_orderBy != null) {
_orderBy.appendClause(this, query);
}
if (_limit != null) {
_limit.appendClause(this, query);
}
if (_forUpdate != null) {
_forUpdate.appendClause(this, query);
}
PreparedStatement pstmt = conn.prepareStatement(query.toString());
int argIdx = 1;
for (Join clause : _joinClauses) {
argIdx = clause.bindArguments(pstmt, argIdx);
}
if (_where != null) {
argIdx = _where.bindArguments(pstmt, argIdx);
}
if (_groupBy != null) {
argIdx = _groupBy.bindArguments(pstmt, argIdx);
}
if (_orderBy != null) {
argIdx = _orderBy.bindArguments(pstmt, argIdx);
}
if (_limit != null) {
argIdx = _limit.bindArguments(pstmt, argIdx);
}
if (_forUpdate != null) {
argIdx = _forUpdate.bindArguments(pstmt, argIdx);
}
return pstmt;
}
/**
* Creates a new Query object to generate one or more instances of the specified persistent
* class, as dictated by the key and query clauses. A persistence context is supplied for
* instantiation of marshallers, which may trigger table creations and schema migrations.
*/
protected Query (PersistenceContext ctx, Class type, QueryClause... clauses)
throws PersistenceException
{
_mainType = type;
Set<Class> classSet = new HashSet<Class>();
if (type != null) {
classSet.add(type);
}
for (QueryClause clause : clauses) {
if (clause instanceof Where) {
if (_where != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Where clauses.");
}
_where = (Where) clause;
} else if (clause instanceof FromOverride) {
if (_fromOverride != null) {
throw new IllegalArgumentException(
"Query can't contain multiple FromOverride clauses.");
}
_fromOverride = (FromOverride) clause;
classSet.addAll(_fromOverride.getClassSet());
} else if (clause instanceof Join) {
_joinClauses.add((Join) clause);
classSet.addAll(((Join) clause).getClassSet());
} else if (clause instanceof FieldOverride) {
_disMap.put(((FieldOverride) clause).getField(),
((FieldOverride) clause));
} else if (clause instanceof OrderBy) {
if (_orderBy != null) {
throw new IllegalArgumentException(
"Query can't contain multiple OrderBy clauses.");
}
_orderBy = (OrderBy) clause;
} else if (clause instanceof GroupBy) {
if (_groupBy != null) {
throw new IllegalArgumentException(
"Query can't contain multiple GroupBy clauses.");
}
_groupBy = (GroupBy) clause;
} else if (clause instanceof Limit) {
if (_limit != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Limit clauses.");
}
_limit = (Limit) clause;
} else if (clause instanceof ForUpdate) {
if (_forUpdate != null) {
throw new IllegalArgumentException(
"Query can't contain multiple For Update clauses.");
}
_forUpdate = (ForUpdate) clause;
}
}
_classMap = new HashMap<Class, DepotMarshaller>();
for (Class<?> c : classSet) {
_classMap.put(c, ctx.getMarshaller(c));
}
_classList = new ArrayList<Class>(classSet);
}
/** The persistent class to instantiate for the results. */
protected Class _mainType;
/** A list of referenced classes, used to generate table abbreviations. */
protected List<Class> _classList;
/** Classes mapped to marshallers, used for table names and field lists. */
protected Map<Class, DepotMarshaller> _classMap;
/** Persistent class fields mapped to field override clauses. */
protected Map<String, FieldOverride> _disMap = new HashMap<String, FieldOverride>();
/** The from override clause, if any. */
protected FromOverride _fromOverride;
/** The where clause. */
protected Where _where;
/** A list of join clauses, each potentially referencing a new class. */
protected List<Join> _joinClauses = new ArrayList<Join>();
/** The order by clause, if any. */
protected OrderBy _orderBy;
/** The group by clause, if any. */
protected GroupBy _groupBy;
/** The limit clause, if any. */
protected Limit _limit;
/** The For Update clause, if any. */
protected ForUpdate _forUpdate;
void updateCache (PersistenceContext ctx, T result);
}
@@ -0,0 +1,113 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne
//
// 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.jdbc.depot;
import java.io.Serializable;
/**
* 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,
* and needs to specify their own cache key and file it under a hand-picked cache id.
*/
public class SimpleCacheKey
implements CacheKey
{
/**
* Construct a {@link SimpleCacheKey} for a query that has no parameters whatsoever.
*/
public SimpleCacheKey (String cacheId)
{
this(cacheId, Boolean.TRUE);
}
/**
* Construct a {@link SimpleCacheKey} associated with the given persistent class with
* the given cache key.
*/
public SimpleCacheKey (Class cacheClass, Serializable cacheKey)
{
this(cacheClass.getName(), cacheKey);
}
/**
* Construct a {@link SimpleCacheKey} for the given cache id with the given cache key.
*/
public SimpleCacheKey (String cacheId, Serializable value)
{
_cacheId = cacheId;
_cacheKey = value;
}
// from CacheKey
public String getCacheId ()
{
return _cacheId;
}
// from CacheKey
public Serializable getCacheKey ()
{
return _cacheKey;
}
@Override
public int hashCode ()
{
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((_cacheId == null) ? 0 : _cacheId.hashCode());
result = PRIME * result + ((_cacheKey == null) ? 0 : _cacheKey.hashCode());
return result;
}
@Override
public boolean equals (Object obj)
{
if (obj == null || obj.getClass() != getClass()) {
return false;
}
SimpleCacheKey other = (SimpleCacheKey) obj;
if (_cacheId == null) {
if (other._cacheId != null) {
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
public String toString ()
{
return "[cacheId=" + _cacheId + ", value=" + _cacheKey + "]";
}
protected String _cacheId;
protected Serializable _cacheKey;
}
@@ -25,11 +25,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Marks a field as computed, meaning it is ignored for schema purposes and it does not directly
* correspond to a column in a table, thus its value must be overridden in the {@link Query}.
* correspond to a column in a table, thus its value must be overridden in the {@link ConstructedQuery}.
*/
@Retention(value=RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
@@ -25,7 +25,7 @@ import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
@@ -75,7 +75,7 @@ public class FieldOverride
}
// from QueryClause
public void appendClause (Query query, StringBuilder builder)
public void appendClause (ConstructedQuery query, StringBuilder builder)
{
_override.appendExpression(query, builder);
builder.append(" as ").append(_field);
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Represents a FOR UPDATE clause.
@@ -39,7 +39,7 @@ public class ForUpdate
}
// from QueryClause
public void appendClause (Query query, StringBuilder builder)
public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" for update ");
}
@@ -26,7 +26,7 @@ import java.util.Arrays;
import java.util.Collection;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Completely overrides the FROM clause, if it exists.
@@ -47,7 +47,7 @@ public class FromOverride
}
// from QueryClause
public void appendClause (Query query, StringBuilder builder)
public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" from " );
for (int ii = 0; ii < _fromClasses.length; ii++) {
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
@@ -45,7 +45,7 @@ public class GroupBy
}
// from QueryClause
public void appendClause (Query query, StringBuilder builder)
public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" group by ");
for (int ii = 0; ii < _values.length; ii++) {
@@ -26,7 +26,7 @@ import java.util.Arrays;
import java.util.Collection;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.operator.SQLOperator;
import com.samskivert.jdbc.depot.operator.Conditionals.*;
@@ -64,7 +64,7 @@ public class Join
}
// from QueryClause
public void appendClause (Query query, StringBuilder builder)
public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" inner join " );
builder.append(query.getTableName(_joinClass)).append(" as ");
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Represents a LIMIT/OFFSET clause, for pagination.
@@ -45,7 +45,7 @@ public class Limit
}
// from QueryClause
public void appendClause (Query query, StringBuilder builder)
public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" limit ? offset ? ");
}
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
@@ -82,7 +82,7 @@ public class OrderBy
}
// from QueryClause
public void appendClause (Query query, StringBuilder builder)
public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" order by ");
for (int ii = 0; ii < _values.length; ii++) {
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Represents a piece or modifier of an SQL query.
@@ -41,7 +41,7 @@ public interface QueryClause
* Construct the SQL form of this query clause. The implementor is expected to call methods
* on the Query object to e.g. resolve current table abbreviations associated with classes.
*/
public void appendClause (Query query, StringBuilder builder);
public void appendClause (ConstructedQuery query, StringBuilder builder);
/**
* Bind any objects that were referenced in the generated SQL. For each ? that appears in the
@@ -25,9 +25,14 @@ import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ValueExp;
import com.samskivert.jdbc.depot.operator.SQLOperator;
import com.samskivert.jdbc.depot.operator.Conditionals.Equals;
import com.samskivert.jdbc.depot.operator.Conditionals.IsNull;
import com.samskivert.jdbc.depot.operator.Logic.And;
/**
* Represents a where clause: the condition can be any comparison operator or logical combination
@@ -36,6 +41,47 @@ import com.samskivert.jdbc.depot.operator.SQLOperator;
public class Where
implements QueryClause
{
public Where (String index, Comparable value)
{
this(new ColumnExp(index), value);
}
public Where (ColumnExp column, Comparable value)
{
this(new ColumnExp[] { column }, new Comparable[] { value });
}
public Where (ColumnExp index1, Comparable value1,
ColumnExp index2, Comparable value2)
{
this(new ColumnExp[] { index1, index2 }, new Comparable[] { value1, value2 });
}
public Where (ColumnExp index1, Comparable value1,
ColumnExp index2, Comparable value2,
ColumnExp index3, Comparable value3)
{
this(new ColumnExp[] { index1, index2, index3 },
new Comparable[] { value1, value2, value3 });
}
public Where (String index1, Comparable value1, String index2, Comparable value2)
{
this(new ColumnExp(index1), value1, new ColumnExp(index2), value2);
}
public Where (String index1, Comparable value1, String index2, Comparable value2,
String index3, Comparable value3)
{
this(new ColumnExp(index1), value1, new ColumnExp(index2), value2,
new ColumnExp(index3), value3);
}
public Where (ColumnExp[] columns, Comparable[] values)
{
this(toCondition(columns, values));
}
public Where (SQLOperator condition)
{
_condition = condition;
@@ -48,7 +94,7 @@ public class Where
}
// from QueryClause
public void appendClause (Query query, StringBuilder builder)
public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" where ");
_condition.appendExpression(query, builder);
@@ -61,5 +107,15 @@ public class Where
return _condition.bindArguments(pstmt, argIdx);
}
protected static SQLOperator toCondition (ColumnExp[] columns, Comparable[] values)
{
SQLOperator[] comparisons = new SQLOperator[columns.length];
for (int ii = 0; ii < columns.length; ii ++) {
comparisons[ii] = (values[ii] == null) ? new IsNull(columns[ii]) :
new Equals(columns[ii], new ValueExp(values[ii]));
}
return new And(comparisons);
}
protected SQLOperator _condition;
}
@@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* An expression identifying a column of a class, e.g. GameRecord.itemId. If no class is given,
@@ -51,7 +51,7 @@ public class ColumnExp
}
// from SQLExpression
public void appendExpression (Query query, StringBuilder builder)
public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
if (pClass == null || query == null) {
builder.append(pColumn);
@@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* An expression for a function, e.g. FLOOR(blah).
@@ -41,7 +41,7 @@ public class FunctionExp
}
// from SQLExpression
public void appendExpression (Query query, StringBuilder builder)
public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
builder.append(_function);
builder.append("(");
@@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* An expression for things we don't support natively, e.g. COUNT(*).
@@ -38,7 +38,7 @@ public class LiteralExp
}
// from SQLExpression
public void appendExpression (Query query, StringBuilder builder)
public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
builder.append(_text);
}
@@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Represents an SQL expression, e.g. column name, function, or constant.
@@ -34,7 +34,7 @@ public interface SQLExpression
* Construct the SQL form of this expression. The implementor is invited to call methods on the
* Query object to e.g. resolve the current table abbreviations associated with classes.
*/
public void appendExpression (Query query, StringBuilder builder);
public void appendExpression (ConstructedQuery query, StringBuilder builder);
/**
* Bind any objects that were referenced in the generated SQL. For each ? that appears in the
@@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* A Java value that is bound as a parameter to the query, e.g. 1 or 'abc'.
@@ -37,7 +37,7 @@ public class ValueExp
}
// from SQLExpression
public void appendExpression (Query query, StringBuilder builder)
public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
builder.append("?");
}
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
@@ -56,7 +56,7 @@ public abstract class Conditionals
}
// from SQLExpression
public void appendExpression (Query query, StringBuilder builder)
public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
_column.appendExpression(query, builder);
builder.append(" is null");
@@ -181,7 +181,7 @@ public abstract class Conditionals
}
// from SQLExpression
public void appendExpression (Query query, StringBuilder builder)
public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
_column.appendExpression(query, builder);
builder.append(" in (");
@@ -234,7 +234,7 @@ public abstract class Conditionals
}
// from SQLExpression
public void appendExpression (Query query, StringBuilder builder)
public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
builder.append("match(");
int idx = 0;
@@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.operator;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator;
/**
@@ -80,7 +80,7 @@ public abstract class Logic
}
// from SQLExpression
public void appendExpression (Query query, StringBuilder builder)
public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
builder.append(" not (");
_condition.appendExpression(query, builder);
@@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.operator;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.samskivert.jdbc.depot.Query;
import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
@@ -47,7 +47,7 @@ public interface SQLOperator extends SQLExpression
}
// from SQLExpression
public void appendExpression (Query query, StringBuilder builder)
public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
for (int ii = 0; ii < _conditions.length; ii++) {
if (ii > 0) {
@@ -94,7 +94,7 @@ public interface SQLOperator extends SQLExpression
}
// from SQLExpression
public void appendExpression (Query query, StringBuilder builder)
public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
_lhs.appendExpression(query, builder);
builder.append(operator());