Caching support from Zell. Hard drives the world round rejoice!
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<?>>();
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user