diff --git a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java index 5cffea2..150c93d 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java +++ b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java @@ -88,22 +88,6 @@ public class DepotMarshaller return _tableName; } - /** - * Returns the field/column name of the primary key for this persistent - * object class. - * - * @exception UnsupportedOperationException thrown if this persistent - * object class did not define a primary key. - */ - public String getPrimaryKeyColumn () - { - if (_primaryKey == null) { - throw new UnsupportedOperationException( - getClass().getName() + " does not define a primary key"); - } - return _primaryKey.getColumnName(); - } - /** * Returns a key configured with the primary key of the supplied object. * Throws an exception if the persistent object did not declare a primary @@ -123,6 +107,15 @@ public class DepotMarshaller } } + /** + * Creates a primary key record for the type of object handled by this + * marshaller, using the supplied primary key vlaue. + */ + public DepotRepository.Key makePrimaryKey (Comparable value) + { + return new DepotRepository.Key(_primaryKey.getColumnName(), value); + } + /** * Creates a query for instances of this persistent object type using the * supplied key. @@ -134,9 +127,7 @@ public class DepotMarshaller String query = "select " + _fullColumnList + " from " + getTableName() + " where " + key.toWhereClause(); PreparedStatement pstmt = conn.prepareStatement(query); - for (int ii = 0; ii < key.indices.length; ii++) { - pstmt.setObject(ii+1, key.values[ii]); - } + key.bindArguments(pstmt, 1); return pstmt; } @@ -238,9 +229,12 @@ public class DepotMarshaller try { PreparedStatement pstmt = conn.prepareStatement(update.toString()); idx = 0; + // bind the update arguments for (String field : modifiedFields) { _fields.get(field).setValue(po, pstmt, ++idx); } + // now bind the key arguments + key.bindArguments(pstmt, ++idx); return pstmt; } catch (SQLException sqe) { @@ -274,24 +268,56 @@ public class DepotMarshaller } update.append(" where ").append(key.toWhereClause()); - try { - PreparedStatement pstmt = conn.prepareStatement(update.toString()); - idx = 0; - for (Object value : modifiedValues) { - // TODO: use the field marshaller? - pstmt.setObject(++idx, value); - } - return pstmt; - - } catch (SQLException sqe) { - // pass this on through - throw sqe; - - } catch (Exception e) { - String errmsg = "Failed to marshall persistent object " + - "[pclass=" + _pclass.getName() + "]"; - throw (SQLException)new SQLException(errmsg).initCause(e); + PreparedStatement pstmt = conn.prepareStatement(update.toString()); + idx = 0; + // bind the update arguments + for (Object value : modifiedValues) { + // TODO: use the field marshaller? + pstmt.setObject(++idx, value); } + // now bind the key arguments + key.bindArguments(pstmt, ++idx); + return pstmt; + } + + /** + * Creates a statement that will update the specified set of fields, using + * the supplied literal SQL values, for all persistent objects that match + * the supplied key. + */ + public PreparedStatement createLiteralUpdate ( + Connection conn, DepotRepository.Key key, + String[] modifiedFields, Object[] modifiedValues) + throws SQLException + { + StringBuilder update = new StringBuilder(); + update.append("update ").append(getTableName()).append(" set "); + for (int ii = 0; ii < modifiedFields.length; ii++) { + if (ii > 0) { + update.append(", "); + } + update.append(modifiedFields[ii]).append(" = "); + update.append(modifiedValues[ii]); + } + update.append(" where ").append(key.toWhereClause()); + + PreparedStatement pstmt = conn.prepareStatement(update.toString()); + key.bindArguments(pstmt, 1); + return pstmt; + } + + /** + * Creates a statement that will delete all rows matching the supplied key. + */ + public PreparedStatement createDelete ( + Connection conn, DepotRepository.Key key) + throws SQLException + { + String query = "delete from " + getTableName() + + " where " + key.toWhereClause(); + PreparedStatement pstmt = conn.prepareStatement(query); + key.bindArguments(pstmt, 1); + return pstmt; } protected Class _pclass; diff --git a/src/java/com/samskivert/jdbc/depot/DepotRepository.java b/src/java/com/samskivert/jdbc/depot/DepotRepository.java index 8e849e8..d326869 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotRepository.java +++ b/src/java/com/samskivert/jdbc/depot/DepotRepository.java @@ -42,13 +42,18 @@ public class DepotRepository _conprov = conprov; } + /** + * Loads the persistent object that matches the specified primary key. + */ protected T load (Class type, Comparable primaryKey) throws PersistenceException { - DepotMarshaller marsh = getMarshaller(type); - return load(type, new Key(marsh.getPrimaryKeyColumn(), primaryKey)); + return load(type, getMarshaller(type).makePrimaryKey(primaryKey)); } + /** + * Loads the first persistent object that matches the supplied key. + */ protected T load (Class type, Key key) throws PersistenceException { @@ -75,6 +80,9 @@ public class DepotRepository }); } + /** + * Loads all persistent objects that match the specified key. + */ protected > Collection findAll ( Class type, Key key) throws PersistenceException @@ -100,6 +108,10 @@ public class DepotRepository }); } + /** + * Executes and caches the supplied collection query if the supplied key is + * not in the cache, returns the existing cached values otherwise. + */ protected T findAll ( String index, Comparable key, CollectionQuery query) throws PersistenceException @@ -107,17 +119,26 @@ public class DepotRepository return null; } - protected void insert (final Object record) + /** + * Inserts the supplied persistent object into the database, assigning its + * primary key (if it has one) in the process. + * + * @return the number of rows modified by this action, this should always + * be one. + */ + protected int insert (final Object record) throws PersistenceException { final DepotMarshaller marsh = getMarshaller(record.getClass()); - invoke(new Modifier(marsh.getPrimaryKey(record)) { + return invoke(new Modifier(marsh.getPrimaryKey(record)) { public int invoke (Connection conn) throws SQLException { PreparedStatement stmt = marsh.createInsert(conn, record); try { - return stmt.executeUpdate(); + int mods = stmt.executeUpdate(); + // TODO: assign primary key + return mods; } finally { stmt.close(); } @@ -125,16 +146,21 @@ public class DepotRepository }); } + /** + * Updates all fields of the supplied persistent object, using its primary + * key to identify the row to be updated. + * + * @return the number of rows modified by this action. + */ protected int update (final Object record) throws PersistenceException { final DepotMarshaller marsh = getMarshaller(record.getClass()); - invoke(new Modifier(marsh.getPrimaryKey(record)) { + return invoke(new Modifier(marsh.getPrimaryKey(record)) { public int invoke (Connection conn) throws SQLException { - PreparedStatement stmt = marsh.createUpdate( - conn, record, marsh.getPrimaryKey(record)); + PreparedStatement stmt = marsh.createUpdate(conn, record, _key); try { return stmt.executeUpdate(); } finally { @@ -142,19 +168,24 @@ public class DepotRepository } } }); - return 0; } + /** + * Updates just the specified fields of the supplied persistent object, + * using its primary key to identify the row to be updated. + * + * @return the number of rows modified by this action. + */ protected int update (final Object record, final String ... modifiedFields) throws PersistenceException { final DepotMarshaller marsh = getMarshaller(record.getClass()); - invoke(new Modifier(marsh.getPrimaryKey(record)) { + return invoke(new Modifier(marsh.getPrimaryKey(record)) { public int invoke (Connection conn) throws SQLException { PreparedStatement stmt = marsh.createUpdate( - conn, record, marsh.getPrimaryKey(record), modifiedFields); + conn, record, _key, modifiedFields); try { return stmt.executeUpdate(); } finally { @@ -162,54 +193,231 @@ public class DepotRepository } } }); - return 0; } - protected int updatePartial (Class type, - Comparable primaryKey, Object ... fieldsValues) + /** + * Updates the specified columns for all persistent objects matching the + * supplied primary key. + * + * @param type the type of the persistent object to be modified. + * @param primaryKey the primary key to match in the update. + * @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 int updatePartial ( + Class type, Comparable primaryKey, Object ... fieldsValues) throws PersistenceException { - return 0; + return updatePartial( + type, getMarshaller(type).makePrimaryKey(primaryKey), fieldsValues); } - protected int updatePartial (Class type, - Key key, Object ... fieldsValues) + /** + * Updates the specified columns for all persistent objects matching the + * supplied key. + * + * @param type the type of the persistent object to be modified. + * @param key the key to match in the update. + * @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 int updatePartial ( + Class type, Key key, Object ... fieldsValues) throws PersistenceException { - return 0; + // separate the arguments into keys and values + final String[] fields = new String[fieldsValues.length/2]; + final Object[] values = new Object[fields.length]; + for (int ii = 0, idx = 0; ii < fields.length; ii++) { + fields[ii] = (String)fieldsValues[idx++]; + values[ii] = fieldsValues[idx++]; + } + + final DepotMarshaller marsh = getMarshaller(type); + return invoke(new Modifier(key) { + public int invoke (Connection conn) + throws SQLException + { + PreparedStatement stmt = marsh.createPartialUpdate( + conn, _key, fields, values); + try { + return stmt.executeUpdate(); + } finally { + stmt.close(); + } + } + }); } - protected int updateLiteral (Class type, - Comparable primaryKey, Object ... fieldsValues) - { - return 0; - } - - protected int updateLiteral (Class type, - Key key, Object ... fieldsValues) - { - return 0; - } - - protected int store (Object record) + /** + * 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: + * + *
+     * update FOO set BAR = BAR + 1;
+     * update BAZ set BIF = NOW();
+     * 
+ * + * @param type the type of the persistent object to be modified. + * @param primaryKey the key to match in the update. + * @param fieldsValues an array containing the names of the fields/columns + * and the values to be assigned, in key, literal value, key, literal + * value, etc. order. + * + * @return the number of rows modified by this action. + */ + protected int updateLiteral ( + Class type, Comparable primaryKey, String ... fieldsValues) throws PersistenceException { - return 0; + return updateLiteral( + type, getMarshaller(type).makePrimaryKey(primaryKey), fieldsValues); } - protected void delete (Object record) + /** + * 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: + * + *
+     * update FOO set BAR = BAR + 1;
+     * update BAZ set BIF = NOW();
+     * 
+ * + * @param type the type of the persistent object to be modified. + * @param key the key to match in the update. + * @param fieldsValues an array containing the names of the fields/columns + * and the values to be assigned, in key, literal value, key, literal + * value, etc. order. + * + * @return the number of rows modified by this action. + */ + protected int updateLiteral ( + Class type, Key key, String ... fieldsValues) throws PersistenceException { + // separate the arguments into keys and values + final String[] fields = new String[fieldsValues.length/2]; + final String[] values = new String[fields.length]; + for (int ii = 0, idx = 0; ii < fields.length; ii++) { + fields[ii] = fieldsValues[idx++]; + values[ii] = fieldsValues[idx++]; + } + + final DepotMarshaller marsh = getMarshaller(type); + return invoke(new Modifier(key) { + public int invoke (Connection conn) + throws SQLException + { + PreparedStatement stmt = marsh.createLiteralUpdate( + conn, _key, fields, values); + try { + return stmt.executeUpdate(); + } finally { + stmt.close(); + } + } + }); } - protected void delete (Class type, Comparable primaryKey) + /** + * Stores the supplied persisent object in the database. If it has no + * primary key assigned (it is null or zero), it will be inserted + * directly. Otherwise an update will first be attempted and if that + * matches zero rows, the object will be inserted. + * + * @return the number of rows modified by this action, this should always + * be one. + */ + protected int store (final Object record) throws PersistenceException { + final DepotMarshaller marsh = getMarshaller(record.getClass()); + return invoke(new Modifier(marsh.getPrimaryKey(record)) { + 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); + int mods = stmt.executeUpdate(); + if (mods > 0) { + return mods; + } + stmt.close(); + } + + // if the update modified zero rows or the primary key was + // obviously unset, do an insertion + stmt = marsh.createInsert(conn, record); + return stmt.executeUpdate(); + + } finally { + stmt.close(); + } + } + }); } - protected void deleteAll (Class type, Key key) + /** + * Deletes all persistent objects from the database with a primary key + * matching the primary key of the supplied object. + * + * @return the number of rows deleted by this action. + */ + protected int delete (T record) throws PersistenceException { + @SuppressWarnings("unchecked") Class type = + (Class)record.getClass(); + DepotMarshaller marsh = getMarshaller(type); + return deleteAll(type, marsh.getPrimaryKey(record)); + } + + /** + * Deletes all persistent objects from the database with a primary key + * matching the supplied primary key. + * + * @return the number of rows deleted by this action. + */ + protected int delete (Class type, Comparable primaryKey) + throws PersistenceException + { + return deleteAll(type, getMarshaller(type).makePrimaryKey(primaryKey)); + } + + /** + * Deletes all persistent objects from the database that match the supplied + * key. + * + * @return the number of rows deleted by this action. + */ + protected int deleteAll (Class type, Key key) + throws PersistenceException + { + final DepotMarshaller marsh = getMarshaller(type); + return invoke(new Modifier(key) { + public int invoke (Connection conn) + throws SQLException + { + PreparedStatement stmt = marsh.createDelete(conn, _key); + try { + return stmt.executeUpdate(); + } finally { + stmt.close(); + } + } + }); } protected DepotMarshaller getMarshaller (Class type) @@ -295,6 +503,14 @@ public class DepotRepository } return where.toString(); } + + public void bindArguments (PreparedStatement stmt, int startIdx) + throws SQLException + { + for (int ii = 0; ii < indices.length; ii++) { + stmt.setObject(startIdx++, values[ii]); + } + } } protected static class Key2 extends Key @@ -349,18 +565,6 @@ public class DepotRepository protected Key _key; } -// protected static abstract class InstanceUpdate -// { -// public void invoke (T object) -// throws PersistenceException; -// } - -// protected static abstract class CollectionUpdate -// { -// public void invoke (Collection collection) -// throws PersistenceException; -// } - protected ConnectionProvider _conprov; protected HashMap, DepotMarshaller> _marshallers =