From 052f15d8ba21395b1d20eb9b49410045e59b4113 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Tue, 3 Mar 2026 11:37:15 -0800 Subject: [PATCH] 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. --- .../com/samskivert/jdbc/JORARepository.java | 158 +++++---- .../com/samskivert/jdbc/SimpleRepository.java | 28 ++ .../java/com/samskivert/jdbc/jora/Cursor.java | 19 +- .../java/com/samskivert/jdbc/jora/Table.java | 301 ++++++++---------- .../servlet/user/UserRepository.java | 46 ++- 5 files changed, 311 insertions(+), 241 deletions(-) diff --git a/src/main/java/com/samskivert/jdbc/JORARepository.java b/src/main/java/com/samskivert/jdbc/JORARepository.java index da74a0f5..a4d6e721 100644 --- a/src/main/java/com/samskivert/jdbc/JORARepository.java +++ b/src/main/java/com/samskivert/jdbc/JORARepository.java @@ -12,16 +12,16 @@ import com.samskivert.io.PersistenceException; import com.samskivert.jdbc.jora.*; /** - * The JORA repository simplifies the process of building persistence - * services that make use of the JORA object relational mapping package. + * The JORA repository simplifies the process of building persistence services that make use of the + * JORA object relational mapping package. * * @see com.samskivert.jdbc.jora.Table */ public abstract class JORARepository extends SimpleRepository { /** - * Creates and initializes a JORA repository which will access the - * database identified by the supplied database identifier. + * Creates and initializes a JORA repository which will access the database identified by the + * supplied database identifier. * * @param provider the connection provider which will be used to * 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 - * object in the specified table. + * Updates fields specified by the supplied field mask in the supplied object in the specified + * table. * * @return the number of rows modified by the update. */ - protected int update (final Table table, final T object, - final FieldMask mask) + protected int update (final Table table, final T object, final FieldMask mask) throws PersistenceException { return executeUpdate(new Operation() { @@ -92,16 +91,13 @@ public abstract class JORARepository extends SimpleRepository } /** - * Loads all objects from the specified table that match the supplied - * query. + * Loads all objects from the specified table that match the supplied query. */ - protected ArrayList loadAll (final Table table, - final String query) + protected ArrayList loadAll (final Table table, final String query) throws PersistenceException { return execute(new Operation>() { - public ArrayList invoke ( - Connection conn, DatabaseLiaison liaison) + public ArrayList invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { 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 - * query, joining with the supplied auxiliary table(s). + * Loads all objects from the specified table that match the supplied parameterized query. + * + * @param query a WHERE clause using ? placeholders for parameters. + * @param params parameter values to bind to the ? placeholders. */ - protected ArrayList loadAll ( - final Table table, final String auxtable, final String query) + protected ArrayList loadAllParams (final Table table, final String query, + final Object... params) throws PersistenceException { return execute(new Operation>() { - public ArrayList invoke ( - Connection conn, DatabaseLiaison liaison) + public ArrayList invoke (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 ArrayList loadAll (final Table table, final String auxtable, + final String query) + throws PersistenceException + { + return execute(new Operation>() { + public ArrayList invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { 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 - * example. + * Loads all objects from the specified table that match the supplied example. */ - protected ArrayList loadAllByExample ( - final Table table, final T example) + protected ArrayList loadAllByExample (final Table table, final T example) throws PersistenceException { return execute(new Operation>() { - public ArrayList invoke ( - Connection conn, DatabaseLiaison liaison) + public ArrayList invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { 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 - * example. + * Loads all objects from the specified table that match the supplied example. */ protected ArrayList loadAllByExample ( final Table table, final T example, final FieldMask mask) throws PersistenceException { return execute(new Operation>() { - public ArrayList invoke ( - Connection conn, DatabaseLiaison liaison) + public ArrayList invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException - { + { return table.queryByExample(conn, example, mask).toArrayList(); } }); } /** - * Loads a single object from the specified table that matches the - * supplied query. Note: the query should match one or zero - * records, not more. + * Loads a single object from the specified table that matches the supplied query. + * Note: the query should match one or zero records, not more. */ protected T load (final Table table, final String query) throws PersistenceException @@ -181,12 +189,30 @@ public abstract class JORARepository extends SimpleRepository } /** - * Loads a single object from the specified table that matches the supplied - * query, joining with the supplied auxiliary table(s). Note: the - * query should match one or zero records, not more. + * Loads a single object from the specified table that matches the supplied parameterized query. + * Note: the 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 load ( - final Table table, final String auxtable, final String query) + protected T loadParams (final Table table, final String query, final Object... params) + throws PersistenceException + { + return execute(new Operation() { + 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). Note: the query should match one or zero records, + * not more. + */ + protected T load (final Table table, final String auxtable, final String query) throws PersistenceException { return execute(new Operation() { @@ -199,9 +225,29 @@ public abstract class JORARepository extends SimpleRepository } /** - * Loads a single object from the specified table that matches the - * supplied example. Note: the query should match one or zero - * records, not more. + * Loads a single object from the specified table that matches the supplied parameterized query, + * joining with the supplied auxiliary table(s). Note: the 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 loadParams (final Table table, final String auxtable, final String query, + final Object... params) + throws PersistenceException + { + return execute(new Operation() { + 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. + * Note: the query should match one or zero records, not more. */ protected T loadByExample (final Table table, final T example) throws PersistenceException @@ -216,12 +262,10 @@ public abstract class JORARepository extends SimpleRepository } /** - * Loads a single object from the specified table that matches the - * supplied example. Note: the query should match one or zero - * records, not more. + * Loads a single object from the specified table that matches the supplied example. + * Note: the query should match one or zero records, not more. */ - protected T loadByExample ( - final Table table, final T example, final FieldMask mask) + protected T loadByExample (final Table table, final T example, final FieldMask mask) throws PersistenceException { return execute(new Operation() { @@ -234,9 +278,9 @@ public abstract class JORARepository extends SimpleRepository } /** - * First attempts to update the supplied object and if that modifies - * zero rows, inserts the object into the specified table. The table - * must be configured to store items of the supplied type. + * First attempts to update the supplied object and if that modifies zero rows, inserts the + * object into the specified table. The table must be configured to store items of the supplied + * type. * * @return -1 if the object was updated, the last inserted id if it was * inserted. @@ -258,13 +302,12 @@ public abstract class JORARepository extends SimpleRepository } /** - * Updates the specified field in the supplied object (which must - * correspond to the supplied table). + * Updates the specified field in the supplied object (which must correspond to the supplied + * table). * * @return the number of rows modified by the update. */ - protected int updateField ( - final Table table, final T object, String field) + protected int updateField (final Table table, final T object, String field) throws PersistenceException { 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 - * correspond to the supplied table). + * Updates the specified fields in the supplied object (which must correspond to the supplied + * table). * * @return the number of rows modified by the update. */ - protected int updateFields ( - final Table table, final T object, String[] fields) + protected int updateFields (final Table table, final T object, String[] fields) throws PersistenceException { final FieldMask mask = table.getFieldMask(); diff --git a/src/main/java/com/samskivert/jdbc/SimpleRepository.java b/src/main/java/com/samskivert/jdbc/SimpleRepository.java index fab1312b..4c65b6cb 100644 --- a/src/main/java/com/samskivert/jdbc/SimpleRepository.java +++ b/src/main/java/com/samskivert/jdbc/SimpleRepository.java @@ -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() { + 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 * modification count is not equal to the specified count. diff --git a/src/main/java/com/samskivert/jdbc/jora/Cursor.java b/src/main/java/com/samskivert/jdbc/jora/Cursor.java index db1dfef3..72ae092a 100644 --- a/src/main/java/com/samskivert/jdbc/jora/Cursor.java +++ b/src/main/java/com/samskivert/jdbc/jora/Cursor.java @@ -44,6 +44,13 @@ public class Cursor _table.bindQueryVariables(qbeStmt, _qbeObject, _qbeMask); _result = qbeStmt.executeQuery(); _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 { if (_stmt == null) { _stmt = _conn.createStatement(); @@ -194,8 +201,15 @@ public class Cursor _query = query; } - protected Cursor (Table table, Connection conn, V obj, - FieldMask mask, boolean like) + protected Cursor (Table table, Connection conn, String query, Object[] params) + { + _table = table; + _conn = conn; + _query = query; + _params = params; + } + + protected Cursor (Table table, Connection conn, V obj, FieldMask mask, boolean like) { _table = table; _conn = conn; @@ -213,6 +227,7 @@ public class Cursor protected Statement _stmt; protected V _currObject, _qbeObject; protected FieldMask _qbeMask; + protected Object[] _params; protected boolean _like; } diff --git a/src/main/java/com/samskivert/jdbc/jora/Table.java b/src/main/java/com/samskivert/jdbc/jora/Table.java index bcab713c..cc3c1a09 100644 --- a/src/main/java/com/samskivert/jdbc/jora/Table.java +++ b/src/main/java/com/samskivert/jdbc/jora/Table.java @@ -20,8 +20,7 @@ import com.samskivert.util.StringUtil; public class Table { /** - * Constructor for table object. Make association between Java class and - * database table. + * Constructor for table object. Make association between Java class and database table. * * @param clazz the class that represents a row entry. * @param tableName name of database table mapped on this Java class @@ -30,16 +29,14 @@ public class Table * @param mixedCaseConvert whether or not to convert mixed case field * names into underscore separated uppercase column names. */ - public Table (Class clazz, String tableName, String key, - boolean mixedCaseConvert) + public Table (Class clazz, String tableName, String key, boolean mixedCaseConvert) { String[] keys = {key}; init(clazz, tableName, keys, mixedCaseConvert); } /** - * Constructor for table object. Make association between Java class and - * database table. + * Constructor for table object. Make association between Java class and database table. * * @param clazz the class that represents a row entry. * @param tableName name of database table mapped on this Java class @@ -52,8 +49,7 @@ public class Table } /** - * Constructor for table object. Make association between Java class and - * database table. + * Constructor for table object. Make association between Java class and database table. * * @param clazz the class that represents a row entry. * @param tableName name of database table mapped on this Java class @@ -66,8 +62,7 @@ public class Table } /** - * Constructor for table object. Make association between Java class and - * database table. + * Constructor for table object. Make association between Java class and database table. * * @param clazz the class that represents a row entry. * @param tableName name of database table mapped on this Java class @@ -76,8 +71,7 @@ public class Table * @param mixedCaseConvert whether or not to convert mixed case field * names into underscore separated uppercase column names. */ - public Table (Class clazz, String tableName, String[] keys, - boolean mixedCaseConvert) + public Table (Class clazz, String tableName, String[] keys, boolean mixedCaseConvert) { init(clazz, tableName, keys, mixedCaseConvert); } @@ -98,64 +92,85 @@ public class Table */ public final Cursor select (Connection conn, String condition) { - String query = "select " + listOfFields + " from " + name + + String query = "select " + listOfFields + " from " + name + " " + condition; + return new Cursor(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 selectParams (Connection conn, String condition, Object... params) + { + String query = "select " + listOfFields + " from " + name + " " + condition; + return new Cursor(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 selectParams (Connection conn, String tables, String condition, + Object... params) + { + String query = "select " + qualifiedListOfFields + " from " + name + "," + tables + + " " + condition; + return new Cursor(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 select (Connection conn, String tables, String condition) + { + String query = "select " + qualifiedListOfFields + " from " + name + "," + tables + " " + condition; return new Cursor(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. + * 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 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 select (Connection conn, String tables, - String condition) + public final Cursor join (Connection conn, String tables, String condition) { - String query = "select " + qualifiedListOfFields + - " from " + name + "," + tables + " " + condition; - return new Cursor(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 join (Connection conn, String tables, - String condition) - { - String query = "select " + listOfFields + - " from " + name + "," + tables + " " + condition; + String query = "select " + listOfFields + " from " + name + "," + tables + " " + condition; return new Cursor(this, conn, query); } /** * Like {@link #join} but does a straight join with the specified table. */ - public final Cursor straightJoin (Connection conn, String table, - String condition) + public final Cursor straightJoin (Connection conn, String table, String condition) { - String query = "select " + listOfFields + - " from " + name + " straight_join " + table + " " + condition; + String query = "select " + listOfFields + " from " + name + " straight_join " + table + + " " + condition; return new Cursor(this, conn, query); } /** * Select records from database table using obj object as template. * - * @param obj example object for search: selected objects should match all - * non-null fields. + * @param obj example object for search: selected objects should match all non-null fields. */ public final Cursor queryByExample (Connection conn, T obj) { @@ -163,23 +178,20 @@ public class Table } /** - * Select records from database table using obj object as template - * for selection. + * Select records from database table using obj object as template for selection. * * @param obj example object for search. - * @param mask field mask indicating which fields in the example object - * should be used when building the query. + * @param mask field mask indicating which fields in the example object should be used when + * building the query. */ - public final Cursor queryByExample (Connection conn, T obj, - FieldMask mask) + public final Cursor queryByExample (Connection conn, T obj, FieldMask mask) { return new Cursor(this, conn, obj, mask, false); } /** - * The same as the queryByExample, but string fields for the obj are - * matched using 'like' instead of equals, which allows you to send % in to - * do matching. + * The same as the queryByExample, but string fields for the obj are matched using 'like' + * instead of equals, which allows you to send % in to do matching. */ public final Cursor queryByLikeExample (Connection conn, T obj) { @@ -187,19 +199,17 @@ public class Table } /** - * The same as the queryByExample, but string fields for the obj are - * matched using 'like' instead of equals, which allows you to send % in to - * do matching. + * The same as the queryByExample, but string fields for the obj are matched using 'like' + * instead of equals, which allows you to send % in to do matching. */ - public final Cursor queryByLikeExample (Connection conn, T obj, - FieldMask mask) + public final Cursor queryByLikeExample (Connection conn, T obj, FieldMask mask) { return new Cursor(this, conn, obj, mask, true); } /** - * Insert new record in the table. Values of inserted record fields are - * taken from specified object. + * Insert new record in the table. Values of inserted record fields are taken from specified + * object. * * @param obj object specifying values of inserted record fields */ @@ -253,11 +263,10 @@ public class Table } /** - * Insert several new records in the table. Values of inserted records - * fields are taken from objects of specified array. + * Insert several new records in the table. Values of inserted records fields are taken from + * objects of specified array. * - * @param objects array with objects specifying values of inserted record - * fields + * @param objects array with objects specifying values of inserted record fields */ public synchronized void insert (Connection conn, T[] objects) throws SQLException @@ -278,8 +287,8 @@ public class Table } /** - * Returns a field mask that can be configured and used to update subsets - * of entire objects via calls to {@link #update(Connection,Object,FieldMask)}. + * Returns a field mask that can be configured and used to update subsets of entire objects via + * calls to {@link #update(Connection,Object,FieldMask)}. */ public FieldMask getFieldMask () { @@ -287,12 +296,10 @@ public class Table } /** - * Update record in the table using table's primary key to locate record in - * the table and values of fields of specified object obj to alter - * record fields. + * Update record in the table using table's primary key to locate record in the table and values + * of fields of specified object obj to alter record fields. * - * @param obj object specifying value of primary key and new values of - * updated record fields + * @param obj object specifying value of primary key and new values of updated record fields * * @return number of objects actually updated */ @@ -303,15 +310,13 @@ public class Table } /** - * Update record in the table using table's primary key to locate record in - * the table and values of fields of specified object obj to alter - * record fields. Only the fields marked as modified in the supplied field - * mask will be updated in the database. + * Update record in the table using table's primary key to locate record in the table and values + * of fields of specified object obj to alter record fields. Only the fields marked as + * modified in the supplied field mask will be updated in the database. * - * @param obj object specifying value of primary key and new values of - * updated record fields - * @param mask a {@link FieldMask} instance configured to indicate which of - * the object's fields are modified and should be written to the database. + * @param obj object specifying value of primary key and new values of updated record fields + * @param mask a {@link FieldMask} instance configured to indicate which of the object's fields + * are modified and should be written to the database. * * @return number of objects actually updated */ @@ -334,12 +339,11 @@ public class Table } /** - * Update set of records in the table using table's primary key to locate - * record in the table and values of fields of objects from specified array - * objects to alter record fields. + * Update set of records in the table using table's primary key to locate record in the table + * and values of fields of objects from specified array objects to alter record fields. * - * @param objects array of objects specifying primary keys and and new - * values of updated record fields + * @param objects array of objects specifying primary keys and and new values of updated record + * fields * * @return number of objects actually updated */ @@ -352,15 +356,13 @@ public class Table } int nUpdated = 0; - String sql = "update " + name + " set " + listOfAssignments + - buildUpdateWhere(); + String sql = "update " + name + " set " + listOfAssignments + buildUpdateWhere(); PreparedStatement updateStmt = conn.prepareStatement(sql); for (int i = 0; i < objects.length; i++) { int column = bindUpdateVariables(updateStmt, objects[i], null); for (int j = 0; j < primaryKeys.length; j++) { int fidx = primaryKeyIndices[j]; - fields[fidx].bindVariable( - updateStmt, objects[i], column+1+j); + fields[fidx].bindVariable(updateStmt, objects[i], column+1+j); } updateStmt.addBatch(); } @@ -381,8 +383,7 @@ public class Table throws SQLException { if (primaryKeys == null) { - throw new IllegalStateException( - "No primary key for table " + name + "."); + throw new IllegalStateException("No primary key for table " + name + "."); } int nDeleted = 0; StringBuilder sql = new StringBuilder( @@ -410,8 +411,7 @@ public class Table throws SQLException { if (primaryKeys == null) { - throw new IllegalStateException( - "No primary key for table " + name + "."); + throw new IllegalStateException("No primary key for table " + name + "."); } int nDeleted = 0; StringBuilder sql = new StringBuilder( @@ -422,8 +422,7 @@ public class Table PreparedStatement deleteStmt = conn.prepareStatement(sql.toString()); for (int i = 0; i < objects.length; i++) { for (int j = 0; j < primaryKeys.length; j++) { - fields[primaryKeyIndices[j]].bindVariable( - deleteStmt, objects[i], j+1); + fields[primaryKeyIndices[j]].bindVariable(deleteStmt, objects[i], j+1); } deleteStmt.addBatch(); } @@ -438,15 +437,13 @@ public class Table @Override public String toString () { - return "[name=" + name + - ", primaryKeys=" + StringUtil.toString(primaryKeys) + "]"; + return "[name=" + name + ", primaryKeys=" + StringUtil.toString(primaryKeys) + "]"; } /** - * Separator of name components of compound field. For example, if Java - * class contains component "location" of Point class, which has two - * components "x" and "y", then database table should have columns - * "location_x" and "location_y" (if '_' is used as separator). + * Separator of name components of compound field. For example, if Java class contains component + * "location" of Point class, which has two components "x" and "y", then database table should + * have columns "location_x" and "location_y" (if '_' is used as separator). */ public static final String fieldSeparator = "_"; @@ -460,8 +457,7 @@ public class Table listOfFields = ""; qualifiedListOfFields = ""; listOfAssignments = ""; - ArrayList fieldsVector = - new ArrayList(); + ArrayList fieldsVector = new ArrayList(); nFields = buildFieldsList(fieldsVector, _rowClass, ""); fields = fieldsVector.toArray(new FieldDescriptor[nFields]); fMask = new FieldMask(fields); @@ -478,16 +474,14 @@ public class Table while (--i >= 0) { if (fields[i].name.equals(keys[j])) { if (!fields[i].isAtomic()) { - throw new IllegalArgumentException( - "Non-atomic primary key provided"); + throw new IllegalArgumentException("Non-atomic primary key provided"); } primaryKeyIndices[j] = i; break; } } if (i < 0) { - throw new NoSuchFieldError("No such field '" + keys[j] - + "' in table " + name); + throw new NoSuchFieldError("No such field '" + keys[j] + "' in table " + name); } } } @@ -502,8 +496,8 @@ public class Table } } - protected final int buildFieldsList (ArrayList buf, - Class _rowClass, String prefix) + protected final int buildFieldsList (ArrayList buf, Class _rowClass, + String prefix) { Field[] f = _rowClass.getDeclaredFields(); @@ -511,8 +505,7 @@ public class Table while ((superclass = superclass.getSuperclass()) != null) { Field[] inheritedFields = superclass.getDeclaredFields(); Field[] allFields = new Field[inheritedFields.length + f.length]; - System.arraycopy(inheritedFields, 0, allFields, 0, - inheritedFields.length); + System.arraycopy(inheritedFields, 0, allFields, 0, inheritedFields.length); System.arraycopy(f,0, allFields, inheritedFields.length, f.length); f = allFields; } @@ -529,8 +522,7 @@ public class Table int n = 0; 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(); Class fieldClass = f[i].getType(); String fullName = prefix + convertName(name); @@ -548,49 +540,29 @@ public class Table else if (c.equals("float")) type = FieldDescriptor.t_float; else if (c.equals("double")) type = FieldDescriptor.t_double; else if (c.equals("boolean")) type = FieldDescriptor.t_boolean; - else if (c.equals("java.lang.Byte")) - type = FieldDescriptor.tByte; - else if (c.equals("java.lang.Short")) - type = FieldDescriptor.tShort; - else if (c.equals("java.lang.Integer")) - type = FieldDescriptor.tInteger; - else if (c.equals("java.lang.Long")) - type = FieldDescriptor.tLong; - else if (c.equals("java.lang.Float")) - type = FieldDescriptor.tFloat; - else if (c.equals("java.lang.Double")) - type = FieldDescriptor.tDouble; - else if (c.equals("java.lang.Boolean")) - type = FieldDescriptor.tBoolean; - else if (c.equals("java.math.BigDecimal")) - type = FieldDescriptor.tDecimal; - else if (c.equals("java.lang.String")) - 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 if (c.equals("java.lang.Byte")) type = FieldDescriptor.tByte; + else if (c.equals("java.lang.Short")) type = FieldDescriptor.tShort; + else if (c.equals("java.lang.Integer")) type = FieldDescriptor.tInteger; + else if (c.equals("java.lang.Long")) type = FieldDescriptor.tLong; + else if (c.equals("java.lang.Float")) type = FieldDescriptor.tFloat; + else if (c.equals("java.lang.Double")) type = FieldDescriptor.tDouble; + else if (c.equals("java.lang.Boolean")) type = FieldDescriptor.tBoolean; + else if (c.equals("java.math.BigDecimal")) type = FieldDescriptor.tDecimal; + else if (c.equals("java.lang.String")) 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 { - int nComponents = buildFieldsList(buf, fieldClass, - fd.name+fieldSeparator); - fd.inType = fd.outType = - FieldDescriptor.tCompound + nComponents; + int nComponents = buildFieldsList(buf, fieldClass, fd.name+fieldSeparator); + fd.inType = fd.outType = FieldDescriptor.tCompound + nComponents; try { - fd.constructor = - fieldClass.getDeclaredConstructor(new Class[0]); + fd.constructor = fieldClass.getDeclaredConstructor(new Class[0]); setBypass.invoke(fd.constructor, bypassFlag); } catch(Exception ex) {} @@ -647,20 +619,17 @@ public class Table return obj; } - protected final int load ( - Object obj, int i, int end, int column, ResultSet result) + protected final int load (Object obj, int i, int end, int column, ResultSet result) throws SQLException { try { while (i < end) { FieldDescriptor fd = fields[i++]; if (!fd.loadVariable(result, obj, ++column)) { - Object component = - fd.constructor.newInstance(constructorArgs); + Object component = fd.constructor.newInstance(constructorArgs); fd.field.set(obj, component); int nComponents = fd.inType - FieldDescriptor.tCompound; - column = load(component, i, i + nComponents, - column-1, result); + column = load(component, i, i + nComponents, column-1, result); i += nComponents; } } @@ -673,17 +642,13 @@ public class Table return column; } - protected final int bindUpdateVariables(PreparedStatement pstmt, - T obj, - FieldMask mask) + protected final int bindUpdateVariables(PreparedStatement pstmt, T obj, FieldMask mask) throws SQLException { return bindUpdateVariables(pstmt, obj, 0, nFields, 0, mask); } - protected final void bindQueryVariables(PreparedStatement pstmt, - T obj, - FieldMask mask) + protected final void bindQueryVariables(PreparedStatement pstmt, T obj, FieldMask mask) throws SQLException { bindQueryVariables(pstmt, obj, 0, nFields, 0, mask); diff --git a/src/main/java/com/samskivert/servlet/user/UserRepository.java b/src/main/java/com/samskivert/servlet/user/UserRepository.java index a2d7af0b..e029e36d 100644 --- a/src/main/java/com/samskivert/servlet/user/UserRepository.java +++ b/src/main/java/com/samskivert/servlet/user/UserRepository.java @@ -7,6 +7,7 @@ package com.samskivert.servlet.user; import java.sql.Connection; import java.sql.Date; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; @@ -86,7 +87,7 @@ public class UserRepository extends JORARepository public User loadUser (String username) 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) throws PersistenceException { - User user = load(_utable, "sessions", "where authcode = '" + sessionKey + "' " + - "AND sessions.userId = users.userId"); + User user = loadParams(_utable, "sessions", + "where authcode = ? AND sessions.userId = users.userId", + sessionKey); if (user != null) { user.setDirtyMask(_utable.getFieldMask()); } @@ -145,7 +147,7 @@ public class UserRepository extends JORARepository public ArrayList lookupUsersByEmail (String email) 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 { // 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() { public String invoke (Connection conn, DatabaseLiaison liaison) throws PersistenceException, SQLException { - Statement stmt = conn.createStatement(); + PreparedStatement stmt = conn.prepareStatement( + "select authcode from sessions where userId = ?"); try { - ResultSet rs = stmt.executeQuery(query); + stmt.setInt(1, userId); + ResultSet rs = stmt.executeQuery(); while (rs.next()) { 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 (authcode != null) { - update("update sessions set expires = '" + expires + "' where " + - "authcode = '" + authcode + "'"); + updateParams("update sessions set expires = ? where authcode = ?", expires, authcode); } else { // otherwise create a new one and insert it into the table authcode = UserUtil.genAuthCode(user); - update("insert into sessions (authcode, userId, expires) values('" + authcode + "', " + - user.userId + ", '" + expires + "')"); + updateParams("insert into sessions (authcode, userId, expires) values(?, ?, ?)", + authcode, user.userId, expires); } return authcode; @@ -302,8 +305,8 @@ public class UserRepository extends JORARepository Date expires = new Date(cal.getTime().getTime()); // attempt to update an existing session row, returning true if we found and updated it - return (update("update sessions set expires = '" + expires + "' " + - "where authcode = " + JDBCUtil.escape(sessionKey)) == 1); + return (updateParams("update sessions set expires = ? where authcode = ?", + expires, sessionKey) == 1); } /** @@ -429,6 +432,23 @@ public class UserRepository extends JORARepository 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) throws PersistenceException {