Add support for parameterized queries to Table &c.

Then fix up UserRepository to not concatenate strings. Good lord. 25 years ago
MDB was too lazy.
This commit is contained in:
Michael Bayne
2026-03-03 11:37:15 -08:00
parent 07844dfef3
commit 052f15d8ba
5 changed files with 311 additions and 241 deletions
@@ -12,16 +12,16 @@ import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.jora.*; import com.samskivert.jdbc.jora.*;
/** /**
* The JORA repository simplifies the process of building persistence * The JORA repository simplifies the process of building persistence services that make use of the
* services that make use of the JORA object relational mapping package. * JORA object relational mapping package.
* *
* @see com.samskivert.jdbc.jora.Table * @see com.samskivert.jdbc.jora.Table
*/ */
public abstract class JORARepository extends SimpleRepository public abstract class JORARepository extends SimpleRepository
{ {
/** /**
* Creates and initializes a JORA repository which will access the * Creates and initializes a JORA repository which will access the database identified by the
* database identified by the supplied database identifier. * supplied database identifier.
* *
* @param provider the connection provider which will be used to * @param provider the connection provider which will be used to
* obtain our database connection. * obtain our database connection.
@@ -73,13 +73,12 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* Updates fields specified by the supplied field mask in the supplied * Updates fields specified by the supplied field mask in the supplied object in the specified
* object in the specified table. * table.
* *
* @return the number of rows modified by the update. * @return the number of rows modified by the update.
*/ */
protected <T> int update (final Table<T> table, final T object, protected <T> int update (final Table<T> table, final T object, final FieldMask mask)
final FieldMask mask)
throws PersistenceException throws PersistenceException
{ {
return executeUpdate(new Operation<Integer>() { return executeUpdate(new Operation<Integer>() {
@@ -92,16 +91,13 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* Loads all objects from the specified table that match the supplied * Loads all objects from the specified table that match the supplied query.
* query.
*/ */
protected <T> ArrayList<T> loadAll (final Table<T> table, protected <T> ArrayList<T> loadAll (final Table<T> table, final String query)
final String query)
throws PersistenceException throws PersistenceException
{ {
return execute(new Operation<ArrayList<T>>() { return execute(new Operation<ArrayList<T>>() {
public ArrayList<T> invoke ( public ArrayList<T> invoke (Connection conn, DatabaseLiaison liaison)
Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException throws SQLException, PersistenceException
{ {
return table.select(conn, query).toArrayList(); return table.select(conn, query).toArrayList();
@@ -110,16 +106,34 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* Loads all objects from the specified table that match the supplied * Loads all objects from the specified table that match the supplied parameterized query.
* query, joining with the supplied auxiliary table(s). *
* @param query a WHERE clause using ? placeholders for parameters.
* @param params parameter values to bind to the ? placeholders.
*/ */
protected <T> ArrayList<T> loadAll ( protected <T> ArrayList<T> loadAllParams (final Table<T> table, final String query,
final Table<T> table, final String auxtable, final String query) final Object... params)
throws PersistenceException throws PersistenceException
{ {
return execute(new Operation<ArrayList<T>>() { return execute(new Operation<ArrayList<T>>() {
public ArrayList<T> invoke ( public ArrayList<T> invoke (Connection conn, DatabaseLiaison liaison)
Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException
{
return table.selectParams(conn, query, params).toArrayList();
}
});
}
/**
* Loads all objects from the specified table that match the supplied query, joining with the
* supplied auxiliary table(s).
*/
protected <T> ArrayList<T> loadAll (final Table<T> table, final String auxtable,
final String query)
throws PersistenceException
{
return execute(new Operation<ArrayList<T>>() {
public ArrayList<T> invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException throws SQLException, PersistenceException
{ {
return table.select(conn, auxtable, query).toArrayList(); return table.select(conn, auxtable, query).toArrayList();
@@ -128,16 +142,13 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* Loads all objects from the specified table that match the supplied * Loads all objects from the specified table that match the supplied example.
* example.
*/ */
protected <T> ArrayList<T> loadAllByExample ( protected <T> ArrayList<T> loadAllByExample (final Table<T> table, final T example)
final Table<T> table, final T example)
throws PersistenceException throws PersistenceException
{ {
return execute(new Operation<ArrayList<T>>() { return execute(new Operation<ArrayList<T>>() {
public ArrayList<T> invoke ( public ArrayList<T> invoke (Connection conn, DatabaseLiaison liaison)
Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException throws SQLException, PersistenceException
{ {
return table.queryByExample(conn, example).toArrayList(); return table.queryByExample(conn, example).toArrayList();
@@ -146,27 +157,24 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* Loads all objects from the specified table that match the supplied * Loads all objects from the specified table that match the supplied example.
* example.
*/ */
protected <T> ArrayList<T> loadAllByExample ( protected <T> ArrayList<T> loadAllByExample (
final Table<T> table, final T example, final FieldMask mask) final Table<T> table, final T example, final FieldMask mask)
throws PersistenceException throws PersistenceException
{ {
return execute(new Operation<ArrayList<T>>() { return execute(new Operation<ArrayList<T>>() {
public ArrayList<T> invoke ( public ArrayList<T> invoke (Connection conn, DatabaseLiaison liaison)
Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException throws SQLException, PersistenceException
{ {
return table.queryByExample(conn, example, mask).toArrayList(); return table.queryByExample(conn, example, mask).toArrayList();
} }
}); });
} }
/** /**
* Loads a single object from the specified table that matches the * Loads a single object from the specified table that matches the supplied query.
* supplied query. <em>Note:</em> the query should match one or zero * <em>Note:</em> the query should match one or zero records, not more.
* records, not more.
*/ */
protected <T> T load (final Table<T> table, final String query) protected <T> T load (final Table<T> table, final String query)
throws PersistenceException throws PersistenceException
@@ -181,12 +189,30 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* Loads a single object from the specified table that matches the supplied * Loads a single object from the specified table that matches the supplied parameterized query.
* query, joining with the supplied auxiliary table(s). <em>Note:</em> the * <em>Note:</em> the query should match one or zero records, not more.
* query should match one or zero records, not more. *
* @param query a WHERE clause using ? placeholders for parameters.
* @param params parameter values to bind to the ? placeholders.
*/ */
protected <T> T load ( protected <T> T loadParams (final Table<T> table, final String query, final Object... params)
final Table<T> table, final String auxtable, final String query) throws PersistenceException
{
return execute(new Operation<T>() {
public T invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
return table.selectParams(conn, query, params).get();
}
});
}
/**
* Loads a single object from the specified table that matches the supplied query, joining with
* the supplied auxiliary table(s). <em>Note:</em> the query should match one or zero records,
* not more.
*/
protected <T> T load (final Table<T> table, final String auxtable, final String query)
throws PersistenceException throws PersistenceException
{ {
return execute(new Operation<T>() { return execute(new Operation<T>() {
@@ -199,9 +225,29 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* Loads a single object from the specified table that matches the * Loads a single object from the specified table that matches the supplied parameterized query,
* supplied example. <em>Note:</em> the query should match one or zero * joining with the supplied auxiliary table(s). <em>Note:</em> the query should match one or
* records, not more. * zero records, not more.
*
* @param query a WHERE clause using ? placeholders for parameters.
* @param params parameter values to bind to the ? placeholders.
*/
protected <T> T loadParams (final Table<T> table, final String auxtable, final String query,
final Object... params)
throws PersistenceException
{
return execute(new Operation<T>() {
public T invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
return table.selectParams(conn, auxtable, query, params).get();
}
});
}
/**
* Loads a single object from the specified table that matches the supplied example.
* <em>Note:</em> the query should match one or zero records, not more.
*/ */
protected <T> T loadByExample (final Table<T> table, final T example) protected <T> T loadByExample (final Table<T> table, final T example)
throws PersistenceException throws PersistenceException
@@ -216,12 +262,10 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* Loads a single object from the specified table that matches the * Loads a single object from the specified table that matches the supplied example.
* supplied example. <em>Note:</em> the query should match one or zero * <em>Note:</em> the query should match one or zero records, not more.
* records, not more.
*/ */
protected <T> T loadByExample ( protected <T> T loadByExample (final Table<T> table, final T example, final FieldMask mask)
final Table<T> table, final T example, final FieldMask mask)
throws PersistenceException throws PersistenceException
{ {
return execute(new Operation<T>() { return execute(new Operation<T>() {
@@ -234,9 +278,9 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* First attempts to update the supplied object and if that modifies * First attempts to update the supplied object and if that modifies zero rows, inserts the
* zero rows, inserts the object into the specified table. The table * object into the specified table. The table must be configured to store items of the supplied
* must be configured to store items of the supplied type. * type.
* *
* @return -1 if the object was updated, the last inserted id if it was * @return -1 if the object was updated, the last inserted id if it was
* inserted. * inserted.
@@ -258,13 +302,12 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* Updates the specified field in the supplied object (which must * Updates the specified field in the supplied object (which must correspond to the supplied
* correspond to the supplied table). * table).
* *
* @return the number of rows modified by the update. * @return the number of rows modified by the update.
*/ */
protected <T> int updateField ( protected <T> int updateField (final Table<T> table, final T object, String field)
final Table<T> table, final T object, String field)
throws PersistenceException throws PersistenceException
{ {
final FieldMask mask = table.getFieldMask(); final FieldMask mask = table.getFieldMask();
@@ -279,13 +322,12 @@ public abstract class JORARepository extends SimpleRepository
} }
/** /**
* Updates the specified fields in the supplied object (which must * Updates the specified fields in the supplied object (which must correspond to the supplied
* correspond to the supplied table). * table).
* *
* @return the number of rows modified by the update. * @return the number of rows modified by the update.
*/ */
protected <T> int updateFields ( protected <T> int updateFields (final Table<T> table, final T object, String[] fields)
final Table<T> table, final T object, String[] fields)
throws PersistenceException throws PersistenceException
{ {
final FieldMask mask = table.getFieldMask(); final FieldMask mask = table.getFieldMask();
@@ -266,6 +266,34 @@ public class SimpleRepository extends Repository
}); });
} }
/**
* Executes the supplied parameterized update query in this repository, returning the number of
* rows modified.
*
* @param query the SQL update/insert/delete statement with ? placeholders.
* @param params parameter values to bind to the ? placeholders.
*/
protected int updateParams (final String query, final Object... params)
throws PersistenceException
{
return executeUpdate(new Operation<Integer>() {
public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(query);
for (int ii = 0; ii < params.length; ii++) {
stmt.setObject(ii + 1, params[ii]);
}
return stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
}
});
}
/** /**
* Executes the supplied update query in this repository, throwing an exception if the * Executes the supplied update query in this repository, throwing an exception if the
* modification count is not equal to the specified count. * modification count is not equal to the specified count.
@@ -44,6 +44,13 @@ public class Cursor<V>
_table.bindQueryVariables(qbeStmt, _qbeObject, _qbeMask); _table.bindQueryVariables(qbeStmt, _qbeObject, _qbeMask);
_result = qbeStmt.executeQuery(); _result = qbeStmt.executeQuery();
_stmt = qbeStmt; _stmt = qbeStmt;
} else if (_params != null) {
PreparedStatement pstmt = _conn.prepareStatement(_query);
for (int ii = 0; ii < _params.length; ii++) {
pstmt.setObject(ii + 1, _params[ii]);
}
_result = pstmt.executeQuery();
_stmt = pstmt;
} else { } else {
if (_stmt == null) { if (_stmt == null) {
_stmt = _conn.createStatement(); _stmt = _conn.createStatement();
@@ -194,8 +201,15 @@ public class Cursor<V>
_query = query; _query = query;
} }
protected Cursor (Table<V> table, Connection conn, V obj, protected Cursor (Table<V> table, Connection conn, String query, Object[] params)
FieldMask mask, boolean like) {
_table = table;
_conn = conn;
_query = query;
_params = params;
}
protected Cursor (Table<V> table, Connection conn, V obj, FieldMask mask, boolean like)
{ {
_table = table; _table = table;
_conn = conn; _conn = conn;
@@ -213,6 +227,7 @@ public class Cursor<V>
protected Statement _stmt; protected Statement _stmt;
protected V _currObject, _qbeObject; protected V _currObject, _qbeObject;
protected FieldMask _qbeMask; protected FieldMask _qbeMask;
protected Object[] _params;
protected boolean _like; protected boolean _like;
} }
+133 -168
View File
@@ -20,8 +20,7 @@ import com.samskivert.util.StringUtil;
public class Table<T> public class Table<T>
{ {
/** /**
* Constructor for table object. Make association between Java class and * Constructor for table object. Make association between Java class and database table.
* database table.
* *
* @param clazz the class that represents a row entry. * @param clazz the class that represents a row entry.
* @param tableName name of database table mapped on this Java class * @param tableName name of database table mapped on this Java class
@@ -30,16 +29,14 @@ public class Table<T>
* @param mixedCaseConvert whether or not to convert mixed case field * @param mixedCaseConvert whether or not to convert mixed case field
* names into underscore separated uppercase column names. * names into underscore separated uppercase column names.
*/ */
public Table (Class<T> clazz, String tableName, String key, public Table (Class<T> clazz, String tableName, String key, boolean mixedCaseConvert)
boolean mixedCaseConvert)
{ {
String[] keys = {key}; String[] keys = {key};
init(clazz, tableName, keys, mixedCaseConvert); init(clazz, tableName, keys, mixedCaseConvert);
} }
/** /**
* Constructor for table object. Make association between Java class and * Constructor for table object. Make association between Java class and database table.
* database table.
* *
* @param clazz the class that represents a row entry. * @param clazz the class that represents a row entry.
* @param tableName name of database table mapped on this Java class * @param tableName name of database table mapped on this Java class
@@ -52,8 +49,7 @@ public class Table<T>
} }
/** /**
* Constructor for table object. Make association between Java class and * Constructor for table object. Make association between Java class and database table.
* database table.
* *
* @param clazz the class that represents a row entry. * @param clazz the class that represents a row entry.
* @param tableName name of database table mapped on this Java class * @param tableName name of database table mapped on this Java class
@@ -66,8 +62,7 @@ public class Table<T>
} }
/** /**
* Constructor for table object. Make association between Java class and * Constructor for table object. Make association between Java class and database table.
* database table.
* *
* @param clazz the class that represents a row entry. * @param clazz the class that represents a row entry.
* @param tableName name of database table mapped on this Java class * @param tableName name of database table mapped on this Java class
@@ -76,8 +71,7 @@ public class Table<T>
* @param mixedCaseConvert whether or not to convert mixed case field * @param mixedCaseConvert whether or not to convert mixed case field
* names into underscore separated uppercase column names. * names into underscore separated uppercase column names.
*/ */
public Table (Class<T> clazz, String tableName, String[] keys, public Table (Class<T> clazz, String tableName, String[] keys, boolean mixedCaseConvert)
boolean mixedCaseConvert)
{ {
init(clazz, tableName, keys, mixedCaseConvert); init(clazz, tableName, keys, mixedCaseConvert);
} }
@@ -98,64 +92,85 @@ public class Table<T>
*/ */
public final Cursor<T> select (Connection conn, String condition) public final Cursor<T> select (Connection conn, String condition)
{ {
String query = "select " + listOfFields + " from " + name + String query = "select " + listOfFields + " from " + name + " " + condition;
return new Cursor<T>(this, conn, query);
}
/**
* Select records from database table according to search condition using a parameterized query.
*
* @param condition valid SQL condition expression started with WHERE, using ? placeholders for
* parameters.
* @param params parameter values to bind to the ? placeholders.
*/
public final Cursor<T> selectParams (Connection conn, String condition, Object... params)
{
String query = "select " + listOfFields + " from " + name + " " + condition;
return new Cursor<T>(this, conn, query, params);
}
/**
* Select records from database table according to search condition including the specified
* (comma separated) extra tables into the SELECT clause to facilitate a join in determining the
* key, using a parameterized query.
*
* @param tables the (comma separated) names of extra tables to include in the SELECT clause.
* @param condition valid SQL condition expression started with WHERE, using ? placeholders for
* parameters.
* @param params parameter values to bind to the ? placeholders.
*/
public final Cursor<T> selectParams (Connection conn, String tables, String condition,
Object... params)
{
String query = "select " + qualifiedListOfFields + " from " + name + "," + tables +
" " + condition;
return new Cursor<T>(this, conn, query, params);
}
/**
* Select records from database table according to search condition including the specified
* (comma separated) extra tables into the SELECT clause to facilitate a join in determining the
* key.
*
* @param tables the (comma separated) names of extra tables to include in the SELECT clause.
* @param condition valid SQL condition expression started with WHERE.
*/
public final Cursor<T> select (Connection conn, String tables, String condition)
{
String query = "select " + qualifiedListOfFields + " from " + name + "," + tables +
" " + condition; " " + condition;
return new Cursor<T>(this, conn, query); return new Cursor<T>(this, conn, query);
} }
/** /**
* Select records from database table according to search condition * Select records from database table according to search condition including the specified
* including the specified (comma separated) extra tables into the SELECT * (comma separated) extra tables into the SELECT clause to facilitate a join in determining the
* clause to facilitate a join in determining the key. * key. To facilitate situations where data from multiple tables is being combined into a single
* object, the fields will not be qualified with the primary table name.
* *
* @param tables the (comma separated) names of extra tables to include in * @param tables the (comma separated) names of extra tables to include in the SELECT clause.
* the SELECT clause.
* @param condition valid SQL condition expression started with WHERE. * @param condition valid SQL condition expression started with WHERE.
*/ */
public final Cursor<T> select (Connection conn, String tables, public final Cursor<T> join (Connection conn, String tables, String condition)
String condition)
{ {
String query = "select " + qualifiedListOfFields + String query = "select " + listOfFields + " from " + name + "," + tables + " " + condition;
" from " + name + "," + tables + " " + condition;
return new Cursor<T>(this, conn, query);
}
/**
* Select records from database table according to search condition
* including the specified (comma separated) extra tables into the SELECT
* clause to facilitate a join in determining the key. To facilitate
* situations where data from multiple tables is being combined into a
* single object, the fields will not be qualified with the primary table
* name.
*
* @param tables the (comma separated) names of extra tables to include in
* the SELECT clause.
* @param condition valid SQL condition expression started with WHERE.
*/
public final Cursor<T> join (Connection conn, String tables,
String condition)
{
String query = "select " + listOfFields +
" from " + name + "," + tables + " " + condition;
return new Cursor<T>(this, conn, query); return new Cursor<T>(this, conn, query);
} }
/** /**
* Like {@link #join} but does a straight join with the specified table. * Like {@link #join} but does a straight join with the specified table.
*/ */
public final Cursor<T> straightJoin (Connection conn, String table, public final Cursor<T> straightJoin (Connection conn, String table, String condition)
String condition)
{ {
String query = "select " + listOfFields + String query = "select " + listOfFields + " from " + name + " straight_join " + table +
" from " + name + " straight_join " + table + " " + condition; " " + condition;
return new Cursor<T>(this, conn, query); return new Cursor<T>(this, conn, query);
} }
/** /**
* Select records from database table using <I>obj</I> object as template. * Select records from database table using <I>obj</I> object as template.
* *
* @param obj example object for search: selected objects should match all * @param obj example object for search: selected objects should match all non-null fields.
* non-null fields.
*/ */
public final Cursor<T> queryByExample (Connection conn, T obj) public final Cursor<T> queryByExample (Connection conn, T obj)
{ {
@@ -163,23 +178,20 @@ public class Table<T>
} }
/** /**
* Select records from database table using <I>obj</I> object as template * Select records from database table using <I>obj</I> object as template for selection.
* for selection.
* *
* @param obj example object for search. * @param obj example object for search.
* @param mask field mask indicating which fields in the example object * @param mask field mask indicating which fields in the example object should be used when
* should be used when building the query. * building the query.
*/ */
public final Cursor<T> queryByExample (Connection conn, T obj, public final Cursor<T> queryByExample (Connection conn, T obj, FieldMask mask)
FieldMask mask)
{ {
return new Cursor<T>(this, conn, obj, mask, false); return new Cursor<T>(this, conn, obj, mask, false);
} }
/** /**
* The same as the queryByExample, but string fields for the obj are * The same as the queryByExample, but string fields for the obj are matched using 'like'
* matched using 'like' instead of equals, which allows you to send % in to * instead of equals, which allows you to send % in to do matching.
* do matching.
*/ */
public final Cursor<T> queryByLikeExample (Connection conn, T obj) public final Cursor<T> queryByLikeExample (Connection conn, T obj)
{ {
@@ -187,19 +199,17 @@ public class Table<T>
} }
/** /**
* The same as the queryByExample, but string fields for the obj are * The same as the queryByExample, but string fields for the obj are matched using 'like'
* matched using 'like' instead of equals, which allows you to send % in to * instead of equals, which allows you to send % in to do matching.
* do matching.
*/ */
public final Cursor<T> queryByLikeExample (Connection conn, T obj, public final Cursor<T> queryByLikeExample (Connection conn, T obj, FieldMask mask)
FieldMask mask)
{ {
return new Cursor<T>(this, conn, obj, mask, true); return new Cursor<T>(this, conn, obj, mask, true);
} }
/** /**
* Insert new record in the table. Values of inserted record fields are * Insert new record in the table. Values of inserted record fields are taken from specified
* taken from specified object. * object.
* *
* @param obj object specifying values of inserted record fields * @param obj object specifying values of inserted record fields
*/ */
@@ -253,11 +263,10 @@ public class Table<T>
} }
/** /**
* Insert several new records in the table. Values of inserted records * Insert several new records in the table. Values of inserted records fields are taken from
* fields are taken from objects of specified array. * objects of specified array.
* *
* @param objects array with objects specifying values of inserted record * @param objects array with objects specifying values of inserted record fields
* fields
*/ */
public synchronized void insert (Connection conn, T[] objects) public synchronized void insert (Connection conn, T[] objects)
throws SQLException throws SQLException
@@ -278,8 +287,8 @@ public class Table<T>
} }
/** /**
* Returns a field mask that can be configured and used to update subsets * Returns a field mask that can be configured and used to update subsets of entire objects via
* of entire objects via calls to {@link #update(Connection,Object,FieldMask)}. * calls to {@link #update(Connection,Object,FieldMask)}.
*/ */
public FieldMask getFieldMask () public FieldMask getFieldMask ()
{ {
@@ -287,12 +296,10 @@ public class Table<T>
} }
/** /**
* Update record in the table using table's primary key to locate record in * Update record in the table using table's primary key to locate record in the table and values
* the table and values of fields of specified object <I>obj</I> to alter * of fields of specified object <I>obj</I> to alter record fields.
* record fields.
* *
* @param obj object specifying value of primary key and new values of * @param obj object specifying value of primary key and new values of updated record fields
* updated record fields
* *
* @return number of objects actually updated * @return number of objects actually updated
*/ */
@@ -303,15 +310,13 @@ public class Table<T>
} }
/** /**
* Update record in the table using table's primary key to locate record in * Update record in the table using table's primary key to locate record in the table and values
* the table and values of fields of specified object <I>obj</I> to alter * of fields of specified object <I>obj</I> to alter record fields. Only the fields marked as
* record fields. Only the fields marked as modified in the supplied field * modified in the supplied field mask will be updated in the database.
* mask will be updated in the database.
* *
* @param obj object specifying value of primary key and new values of * @param obj object specifying value of primary key and new values of updated record fields
* updated record fields * @param mask a {@link FieldMask} instance configured to indicate which of the object's fields
* @param mask a {@link FieldMask} instance configured to indicate which of * are modified and should be written to the database.
* the object's fields are modified and should be written to the database.
* *
* @return number of objects actually updated * @return number of objects actually updated
*/ */
@@ -334,12 +339,11 @@ public class Table<T>
} }
/** /**
* Update set of records in the table using table's primary key to locate * Update set of records in the table using table's primary key to locate record in the table
* record in the table and values of fields of objects from specified array * and values of fields of objects from specified array <I>objects</I> to alter record fields.
* <I>objects</I> to alter record fields.
* *
* @param objects array of objects specifying primary keys and and new * @param objects array of objects specifying primary keys and and new values of updated record
* values of updated record fields * fields
* *
* @return number of objects actually updated * @return number of objects actually updated
*/ */
@@ -352,15 +356,13 @@ public class Table<T>
} }
int nUpdated = 0; int nUpdated = 0;
String sql = "update " + name + " set " + listOfAssignments + String sql = "update " + name + " set " + listOfAssignments + buildUpdateWhere();
buildUpdateWhere();
PreparedStatement updateStmt = conn.prepareStatement(sql); PreparedStatement updateStmt = conn.prepareStatement(sql);
for (int i = 0; i < objects.length; i++) { for (int i = 0; i < objects.length; i++) {
int column = bindUpdateVariables(updateStmt, objects[i], null); int column = bindUpdateVariables(updateStmt, objects[i], null);
for (int j = 0; j < primaryKeys.length; j++) { for (int j = 0; j < primaryKeys.length; j++) {
int fidx = primaryKeyIndices[j]; int fidx = primaryKeyIndices[j];
fields[fidx].bindVariable( fields[fidx].bindVariable(updateStmt, objects[i], column+1+j);
updateStmt, objects[i], column+1+j);
} }
updateStmt.addBatch(); updateStmt.addBatch();
} }
@@ -381,8 +383,7 @@ public class Table<T>
throws SQLException throws SQLException
{ {
if (primaryKeys == null) { if (primaryKeys == null) {
throw new IllegalStateException( throw new IllegalStateException("No primary key for table " + name + ".");
"No primary key for table " + name + ".");
} }
int nDeleted = 0; int nDeleted = 0;
StringBuilder sql = new StringBuilder( StringBuilder sql = new StringBuilder(
@@ -410,8 +411,7 @@ public class Table<T>
throws SQLException throws SQLException
{ {
if (primaryKeys == null) { if (primaryKeys == null) {
throw new IllegalStateException( throw new IllegalStateException("No primary key for table " + name + ".");
"No primary key for table " + name + ".");
} }
int nDeleted = 0; int nDeleted = 0;
StringBuilder sql = new StringBuilder( StringBuilder sql = new StringBuilder(
@@ -422,8 +422,7 @@ public class Table<T>
PreparedStatement deleteStmt = conn.prepareStatement(sql.toString()); PreparedStatement deleteStmt = conn.prepareStatement(sql.toString());
for (int i = 0; i < objects.length; i++) { for (int i = 0; i < objects.length; i++) {
for (int j = 0; j < primaryKeys.length; j++) { for (int j = 0; j < primaryKeys.length; j++) {
fields[primaryKeyIndices[j]].bindVariable( fields[primaryKeyIndices[j]].bindVariable(deleteStmt, objects[i], j+1);
deleteStmt, objects[i], j+1);
} }
deleteStmt.addBatch(); deleteStmt.addBatch();
} }
@@ -438,15 +437,13 @@ public class Table<T>
@Override @Override
public String toString () public String toString ()
{ {
return "[name=" + name + return "[name=" + name + ", primaryKeys=" + StringUtil.toString(primaryKeys) + "]";
", primaryKeys=" + StringUtil.toString(primaryKeys) + "]";
} }
/** /**
* Separator of name components of compound field. For example, if Java * Separator of name components of compound field. For example, if Java class contains component
* class contains component "location" of Point class, which has two * "location" of Point class, which has two components "x" and "y", then database table should
* components "x" and "y", then database table should have columns * have columns "location_x" and "location_y" (if '_' is used as separator).
* "location_x" and "location_y" (if '_' is used as separator).
*/ */
public static final String fieldSeparator = "_"; public static final String fieldSeparator = "_";
@@ -460,8 +457,7 @@ public class Table<T>
listOfFields = ""; listOfFields = "";
qualifiedListOfFields = ""; qualifiedListOfFields = "";
listOfAssignments = ""; listOfAssignments = "";
ArrayList<FieldDescriptor> fieldsVector = ArrayList<FieldDescriptor> fieldsVector = new ArrayList<FieldDescriptor>();
new ArrayList<FieldDescriptor>();
nFields = buildFieldsList(fieldsVector, _rowClass, ""); nFields = buildFieldsList(fieldsVector, _rowClass, "");
fields = fieldsVector.toArray(new FieldDescriptor[nFields]); fields = fieldsVector.toArray(new FieldDescriptor[nFields]);
fMask = new FieldMask(fields); fMask = new FieldMask(fields);
@@ -478,16 +474,14 @@ public class Table<T>
while (--i >= 0) { while (--i >= 0) {
if (fields[i].name.equals(keys[j])) { if (fields[i].name.equals(keys[j])) {
if (!fields[i].isAtomic()) { if (!fields[i].isAtomic()) {
throw new IllegalArgumentException( throw new IllegalArgumentException("Non-atomic primary key provided");
"Non-atomic primary key provided");
} }
primaryKeyIndices[j] = i; primaryKeyIndices[j] = i;
break; break;
} }
} }
if (i < 0) { if (i < 0) {
throw new NoSuchFieldError("No such field '" + keys[j] throw new NoSuchFieldError("No such field '" + keys[j] + "' in table " + name);
+ "' in table " + name);
} }
} }
} }
@@ -502,8 +496,8 @@ public class Table<T>
} }
} }
protected final int buildFieldsList (ArrayList<FieldDescriptor> buf, protected final int buildFieldsList (ArrayList<FieldDescriptor> buf, Class<?> _rowClass,
Class<?> _rowClass, String prefix) String prefix)
{ {
Field[] f = _rowClass.getDeclaredFields(); Field[] f = _rowClass.getDeclaredFields();
@@ -511,8 +505,7 @@ public class Table<T>
while ((superclass = superclass.getSuperclass()) != null) { while ((superclass = superclass.getSuperclass()) != null) {
Field[] inheritedFields = superclass.getDeclaredFields(); Field[] inheritedFields = superclass.getDeclaredFields();
Field[] allFields = new Field[inheritedFields.length + f.length]; Field[] allFields = new Field[inheritedFields.length + f.length];
System.arraycopy(inheritedFields, 0, allFields, 0, System.arraycopy(inheritedFields, 0, allFields, 0, inheritedFields.length);
inheritedFields.length);
System.arraycopy(f,0, allFields, inheritedFields.length, f.length); System.arraycopy(f,0, allFields, inheritedFields.length, f.length);
f = allFields; f = allFields;
} }
@@ -529,8 +522,7 @@ public class Table<T>
int n = 0; int n = 0;
for (int i = 0; i < f.length; i++) { for (int i = 0; i < f.length; i++) {
if ((f[i].getModifiers()&(Modifier.TRANSIENT|Modifier.STATIC))==0) if ((f[i].getModifiers()&(Modifier.TRANSIENT|Modifier.STATIC))==0) {
{
String name = f[i].getName(); String name = f[i].getName();
Class<?> fieldClass = f[i].getType(); Class<?> fieldClass = f[i].getType();
String fullName = prefix + convertName(name); String fullName = prefix + convertName(name);
@@ -548,49 +540,29 @@ public class Table<T>
else if (c.equals("float")) type = FieldDescriptor.t_float; else if (c.equals("float")) type = FieldDescriptor.t_float;
else if (c.equals("double")) type = FieldDescriptor.t_double; else if (c.equals("double")) type = FieldDescriptor.t_double;
else if (c.equals("boolean")) type = FieldDescriptor.t_boolean; else if (c.equals("boolean")) type = FieldDescriptor.t_boolean;
else if (c.equals("java.lang.Byte")) else if (c.equals("java.lang.Byte")) type = FieldDescriptor.tByte;
type = FieldDescriptor.tByte; else if (c.equals("java.lang.Short")) type = FieldDescriptor.tShort;
else if (c.equals("java.lang.Short")) else if (c.equals("java.lang.Integer")) type = FieldDescriptor.tInteger;
type = FieldDescriptor.tShort; else if (c.equals("java.lang.Long")) type = FieldDescriptor.tLong;
else if (c.equals("java.lang.Integer")) else if (c.equals("java.lang.Float")) type = FieldDescriptor.tFloat;
type = FieldDescriptor.tInteger; else if (c.equals("java.lang.Double")) type = FieldDescriptor.tDouble;
else if (c.equals("java.lang.Long")) else if (c.equals("java.lang.Boolean")) type = FieldDescriptor.tBoolean;
type = FieldDescriptor.tLong; else if (c.equals("java.math.BigDecimal")) type = FieldDescriptor.tDecimal;
else if (c.equals("java.lang.Float")) else if (c.equals("java.lang.String")) type = FieldDescriptor.tString;
type = FieldDescriptor.tFloat; else if (fieldClass.equals(BYTE_PROTO.getClass())) type = FieldDescriptor.tBytes;
else if (c.equals("java.lang.Double")) else if (c.equals("java.sql.Date")) type = FieldDescriptor.tDate;
type = FieldDescriptor.tDouble; else if (c.equals("java.sql.Time")) type = FieldDescriptor.tTime;
else if (c.equals("java.lang.Boolean")) else if (c.equals("java.sql.Timestamp")) type = FieldDescriptor.tTimestamp;
type = FieldDescriptor.tBoolean; else if (c.equals("java.lang.InputStream")) type = FieldDescriptor.tStream;
else if (c.equals("java.math.BigDecimal")) else if (c.equals("java.sql.BlobLocator")) type = FieldDescriptor.tBlob;
type = FieldDescriptor.tDecimal; else if (c.equals("java.sql.ClobLocator")) type = FieldDescriptor.tClob;
else if (c.equals("java.lang.String")) else if (serializableClass.isAssignableFrom(fieldClass)) type = FieldDescriptor.tClosure;
type = FieldDescriptor.tString;
else if (fieldClass.equals(BYTE_PROTO.getClass()))
type = FieldDescriptor.tBytes;
else if (c.equals("java.sql.Date"))
type = FieldDescriptor.tDate;
else if (c.equals("java.sql.Time"))
type = FieldDescriptor.tTime;
else if (c.equals("java.sql.Timestamp"))
type = FieldDescriptor.tTimestamp;
else if (c.equals("java.lang.InputStream"))
type = FieldDescriptor.tStream;
else if (c.equals("java.sql.BlobLocator"))
type = FieldDescriptor.tBlob;
else if (c.equals("java.sql.ClobLocator"))
type = FieldDescriptor.tClob;
else if (serializableClass.isAssignableFrom(fieldClass))
type = FieldDescriptor.tClosure;
else { else {
int nComponents = buildFieldsList(buf, fieldClass, int nComponents = buildFieldsList(buf, fieldClass, fd.name+fieldSeparator);
fd.name+fieldSeparator); fd.inType = fd.outType = FieldDescriptor.tCompound + nComponents;
fd.inType = fd.outType =
FieldDescriptor.tCompound + nComponents;
try { try {
fd.constructor = fd.constructor = fieldClass.getDeclaredConstructor(new Class<?>[0]);
fieldClass.getDeclaredConstructor(new Class<?>[0]);
setBypass.invoke(fd.constructor, bypassFlag); setBypass.invoke(fd.constructor, bypassFlag);
} catch(Exception ex) {} } catch(Exception ex) {}
@@ -647,20 +619,17 @@ public class Table<T>
return obj; return obj;
} }
protected final int load ( protected final int load (Object obj, int i, int end, int column, ResultSet result)
Object obj, int i, int end, int column, ResultSet result)
throws SQLException throws SQLException
{ {
try { try {
while (i < end) { while (i < end) {
FieldDescriptor fd = fields[i++]; FieldDescriptor fd = fields[i++];
if (!fd.loadVariable(result, obj, ++column)) { if (!fd.loadVariable(result, obj, ++column)) {
Object component = Object component = fd.constructor.newInstance(constructorArgs);
fd.constructor.newInstance(constructorArgs);
fd.field.set(obj, component); fd.field.set(obj, component);
int nComponents = fd.inType - FieldDescriptor.tCompound; int nComponents = fd.inType - FieldDescriptor.tCompound;
column = load(component, i, i + nComponents, column = load(component, i, i + nComponents, column-1, result);
column-1, result);
i += nComponents; i += nComponents;
} }
} }
@@ -673,17 +642,13 @@ public class Table<T>
return column; return column;
} }
protected final int bindUpdateVariables(PreparedStatement pstmt, protected final int bindUpdateVariables(PreparedStatement pstmt, T obj, FieldMask mask)
T obj,
FieldMask mask)
throws SQLException throws SQLException
{ {
return bindUpdateVariables(pstmt, obj, 0, nFields, 0, mask); return bindUpdateVariables(pstmt, obj, 0, nFields, 0, mask);
} }
protected final void bindQueryVariables(PreparedStatement pstmt, protected final void bindQueryVariables(PreparedStatement pstmt, T obj, FieldMask mask)
T obj,
FieldMask mask)
throws SQLException throws SQLException
{ {
bindQueryVariables(pstmt, obj, 0, nFields, 0, mask); bindQueryVariables(pstmt, obj, 0, nFields, 0, mask);
@@ -7,6 +7,7 @@ package com.samskivert.servlet.user;
import java.sql.Connection; import java.sql.Connection;
import java.sql.Date; import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
@@ -86,7 +87,7 @@ public class UserRepository extends JORARepository
public User loadUser (String username) public User loadUser (String username)
throws PersistenceException throws PersistenceException
{ {
return loadUserWhere("where username = " + JDBCUtil.escape(username)); return loadUserWhereParams("where username = ?", username);
} }
/** /**
@@ -109,8 +110,9 @@ public class UserRepository extends JORARepository
public User loadUserBySession (String sessionKey) public User loadUserBySession (String sessionKey)
throws PersistenceException throws PersistenceException
{ {
User user = load(_utable, "sessions", "where authcode = '" + sessionKey + "' " + User user = loadParams(_utable, "sessions",
"AND sessions.userId = users.userId"); "where authcode = ? AND sessions.userId = users.userId",
sessionKey);
if (user != null) { if (user != null) {
user.setDirtyMask(_utable.getFieldMask()); user.setDirtyMask(_utable.getFieldMask());
} }
@@ -145,7 +147,7 @@ public class UserRepository extends JORARepository
public ArrayList<User> lookupUsersByEmail (String email) public ArrayList<User> lookupUsersByEmail (String email)
throws PersistenceException throws PersistenceException
{ {
return loadAll(_utable, "where email = " + JDBCUtil.escape(email)); return loadAllParams(_utable, "where email = ?", email);
} }
/** /**
@@ -251,14 +253,16 @@ public class UserRepository extends JORARepository
throws PersistenceException throws PersistenceException
{ {
// look for an existing session for this user // look for an existing session for this user
final String query = "select authcode from sessions where userId = " + user.userId; final int userId = user.userId;
String authcode = execute(new Operation<String>() { String authcode = execute(new Operation<String>() {
public String invoke (Connection conn, DatabaseLiaison liaison) public String invoke (Connection conn, DatabaseLiaison liaison)
throws PersistenceException, SQLException throws PersistenceException, SQLException
{ {
Statement stmt = conn.createStatement(); PreparedStatement stmt = conn.prepareStatement(
"select authcode from sessions where userId = ?");
try { try {
ResultSet rs = stmt.executeQuery(query); stmt.setInt(1, userId);
ResultSet rs = stmt.executeQuery();
while (rs.next()) { while (rs.next()) {
return rs.getString(1); return rs.getString(1);
} }
@@ -276,13 +280,12 @@ public class UserRepository extends JORARepository
// if we found one, update its expires time and reuse it // if we found one, update its expires time and reuse it
if (authcode != null) { if (authcode != null) {
update("update sessions set expires = '" + expires + "' where " + updateParams("update sessions set expires = ? where authcode = ?", expires, authcode);
"authcode = '" + authcode + "'");
} else { } else {
// otherwise create a new one and insert it into the table // otherwise create a new one and insert it into the table
authcode = UserUtil.genAuthCode(user); authcode = UserUtil.genAuthCode(user);
update("insert into sessions (authcode, userId, expires) values('" + authcode + "', " + updateParams("insert into sessions (authcode, userId, expires) values(?, ?, ?)",
user.userId + ", '" + expires + "')"); authcode, user.userId, expires);
} }
return authcode; return authcode;
@@ -302,8 +305,8 @@ public class UserRepository extends JORARepository
Date expires = new Date(cal.getTime().getTime()); Date expires = new Date(cal.getTime().getTime());
// attempt to update an existing session row, returning true if we found and updated it // attempt to update an existing session row, returning true if we found and updated it
return (update("update sessions set expires = '" + expires + "' " + return (updateParams("update sessions set expires = ? where authcode = ?",
"where authcode = " + JDBCUtil.escape(sessionKey)) == 1); expires, sessionKey) == 1);
} }
/** /**
@@ -429,6 +432,23 @@ public class UserRepository extends JORARepository
return user; return user;
} }
/**
* Loads up a user record that matches the specified parameterized where clause. Returns null
* if no record matches.
*
* @param where a WHERE clause using ? placeholders for parameters.
* @param params parameter values to bind to the ? placeholders.
*/
protected User loadUserWhereParams (String where, Object... params)
throws PersistenceException
{
User user = loadParams(_utable, where, params);
if (user != null) {
user.setDirtyMask(_utable.getFieldMask());
}
return user;
}
protected String[] loadNames (int[] userIds, final String column) protected String[] loadNames (int[] userIds, final String column)
throws PersistenceException throws PersistenceException
{ {