diff --git a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java index 5671b2ba..f81ee0ee 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java +++ b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java @@ -28,9 +28,12 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.HashMap; import javax.persistence.Id; +import com.samskivert.util.StringUtil; + /** * Handles the marshalling and unmarshalling of persistent instances to JDBC * primitives ({@link PreparedStatement} and {@link ResultSet}). @@ -50,7 +53,6 @@ public class DepotMarshaller // introspect on the class and create field marshallers for all of its // fields - ArrayList flist = new ArrayList(); for (Field field : _pclass.getFields()) { // the field must be public, non-static and non-transient int mods = field.getModifiers(); @@ -61,25 +63,22 @@ public class DepotMarshaller } FieldMarshaller fm = FieldMarshaller.createMarshaller(field); - flist.add(fm); + _fields.put(fm.getColumnName(), fm); // check to see if this is our primary key - for (Annotation ann : field.getDeclaredAnnotations()) { - System.err.println( - "Annotation " + ann + " (" + ann.getClass() + ")"); + if (field.getAnnotation(Id.class) != null) { + // TODO: handle multiple field primary keys + _primaryKey = fm; } } - _fields = flist.toArray(new FieldMarshaller[flist.size()]); // generate our full list of columns for use in queries - StringBuilder columns = new StringBuilder(); - for (FieldMarshaller fm : _fields) { - if (columns.length() > 0) { - columns.append(","); - } - columns.append(fm.getColumnName()); + _allFields = new String[_fields.size()]; + int idx = 0; + for (FieldMarshaller fm : _fields.values()) { + _allFields[idx++] = fm.getColumnName(); } - _fullColumnList = columns.toString(); + _fullColumnList = StringUtil.join(_allFields, ","); } /** @@ -93,12 +92,38 @@ public class DepotMarshaller } /** - * Returns the field name of the primary key for this persistent object - * class or null if it did not declare a primary key. + * 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 getPrimaryKey () + public String getPrimaryKeyColumn () { - return _primaryKey == null ? null : _primaryKey.getColumnName(); + 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 + * key. + */ + public DepotRepository.Key getPrimaryKey (Object object) + { + if (_primaryKey == null) { + throw new UnsupportedOperationException( + getClass().getName() + " does not define a primary key"); + } + try { + return new DepotRepository.Key(_primaryKey.getColumnName(), + (Comparable)_primaryKey.getField().get(object)); + } catch (IllegalAccessException iae) { + throw new RuntimeException(iae); + } } /** @@ -119,17 +144,23 @@ public class DepotMarshaller } /** - * Creates a persistent object from the supplied result set. + * Creates a persistent object from the supplied result set. The result set + * must have come from a query provided by {@link #createQuery}. */ public T createObject (ResultSet rs) throws SQLException { try { T po = (T)_pclass.newInstance(); - for (int ii = 0; ii < _fields.length; ii++) { - _fields[ii].getValue(rs, ii+1, po); + for (FieldMarshaller fm : _fields.values()) { + fm.getValue(rs, po); } return po; + + } catch (SQLException sqe) { + // pass this on through + throw sqe; + } catch (Exception e) { String errmsg = "Failed to unmarshall persistent object " + "[pclass=" + _pclass.getName() + "]"; @@ -144,7 +175,36 @@ public class DepotMarshaller public PreparedStatement createInsert (Connection conn, Object po) throws SQLException { - return null; + try { + StringBuilder insert = new StringBuilder(); + insert.append("insert into ").append(getTableName()); + insert.append(" (").append(_fullColumnList).append(")"); + insert.append(" values("); + for (int ii = 0; ii < _allFields.length; ii++) { + if (ii > 0) { + insert.append(", "); + } + insert.append("?"); + } + insert.append(")"); + + // TODO: handle primary key, nullable fields specially? + PreparedStatement pstmt = conn.prepareStatement(insert.toString()); + int idx = 0; + for (String field : _allFields) { + _fields.get(field).setValue(po, pstmt, ++idx); + } + 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); + } } /** @@ -155,20 +215,54 @@ public class DepotMarshaller Connection conn, Object po, DepotRepository.Key key) throws SQLException { - return null; + return createUpdate(conn, po, key, _allFields); } /** - * Binds the specified object to the specified prepared statement. + * Creates a statement that will update the supplied persistent object + * using the supplied key. */ - public void bindObject (PreparedStatement stmt, Object po) + public PreparedStatement createUpdate ( + Connection conn, Object po, DepotRepository.Key key, + String[] modifiedFields) + throws SQLException { + StringBuilder update = new StringBuilder(); + update.append("update ").append(getTableName()).append(" set "); + int idx = 0; + for (String field : modifiedFields) { + if (idx++ > 0) { + update.append(", "); + } + update.append(field).append(" = ?"); + } + update.append(" where ").append(key.toWhereClause()); + + try { + PreparedStatement pstmt = conn.prepareStatement(update.toString()); + idx = 0; + for (String field : modifiedFields) { + _fields.get(field).setValue(po, pstmt, ++idx); + } + 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); + } } protected Class _pclass; - protected FieldMarshaller[] _fields; + protected HashMap _fields = + new HashMap(); protected String _tableName; protected FieldMarshaller _primaryKey; protected String _fullColumnList; + protected String[] _allFields; } diff --git a/src/java/com/samskivert/jdbc/depot/DepotRepository.java b/src/java/com/samskivert/jdbc/depot/DepotRepository.java index f96a48b7..2abfa54d 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotRepository.java +++ b/src/java/com/samskivert/jdbc/depot/DepotRepository.java @@ -46,23 +46,58 @@ public class DepotRepository throws PersistenceException { DepotMarshaller marsh = getMarshaller(type); - Key key = new Key(marsh.getPrimaryKey(), primaryKey); - return invoke(new ObjectQuery(marsh, key)); + return load(type, new Key(marsh.getPrimaryKeyColumn(), primaryKey)); } protected T load (Class type, Key key) throws PersistenceException { - DepotMarshaller marsh = getMarshaller(type); - return invoke(new ObjectQuery(marsh, key)); + final DepotMarshaller marsh = getMarshaller(type); + return invoke(new Query(key) { + public T invoke (Connection conn) + throws SQLException, PersistenceException + { + PreparedStatement stmt = marsh.createQuery(conn, _key); + try { + T result = null; + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + result = marsh.createObject(rs); + } + // TODO: if (rs.next()) issue warning? + rs.close(); + return result; + + } finally { + stmt.close(); + } + } + }); } protected > Collection findAll ( Class type, Key key) throws PersistenceException { - DepotMarshaller marsh = getMarshaller(type); - return invoke(new ObjectCollectionQuery(marsh, key)); + final DepotMarshaller marsh = getMarshaller(type); + return invoke(new Query>(key) { + public ArrayList invoke (Connection conn) + throws SQLException, PersistenceException + { + PreparedStatement stmt = marsh.createQuery(conn, _key); + try { + ArrayList results = new ArrayList(); + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + results.add(marsh.createObject(rs)); + } + return results; + + } finally { + stmt.close(); + } + } + }); } protected T findAll ( @@ -72,14 +107,41 @@ public class DepotRepository return null; } - protected void insert (Object record) + protected void insert (final Object record) throws PersistenceException { + final DepotMarshaller marsh = getMarshaller(record.getClass()); + invoke(new Modifier(marsh.getPrimaryKey(record)) { + public int invoke (Connection conn) + throws SQLException + { + PreparedStatement stmt = marsh.createInsert(conn, record); + try { + return stmt.executeUpdate(); + } finally { + stmt.close(); + } + } + }); } - protected int update (Object record) + protected int update (final Object record) throws PersistenceException { + final DepotMarshaller marsh = getMarshaller(record.getClass()); + invoke(new Modifier(marsh.getPrimaryKey(record)) { + public int invoke (Connection conn) + throws SQLException + { + PreparedStatement stmt = marsh.createUpdate( + conn, record, marsh.getPrimaryKey(record)); + try { + return stmt.executeUpdate(); + } finally { + stmt.close(); + } + } + }); return 0; } @@ -164,6 +226,24 @@ public class DepotRepository } } + protected int invoke (Modifier modifier) + throws PersistenceException + { + // TODO: invalidate the cache using the modifier's key + + // TODO: retry query on transient failure + Connection conn = _conprov.getConnection(getIdent(), false); + try { + return modifier.invoke(conn); + + } catch (SQLException sqe) { + throw new PersistenceException("Modifier failure " + modifier, sqe); + + } finally { + _conprov.releaseConnection(getIdent(), false, conn); + } + } + /** * Returns the identifier to be used when obtaining JDBC connections from * our connection provider. The default implementation uses the class name @@ -237,61 +317,22 @@ public class DepotRepository throws SQLException, PersistenceException; } - protected static class ObjectQuery extends Query + protected static abstract class Modifier { - public ObjectQuery (DepotMarshaller marsh, Key key) { - super(key); - _marsh = marsh; - } - - public T invoke (Connection conn) - throws SQLException, PersistenceException + public Key getKey () { - PreparedStatement stmt = _marsh.createQuery(conn, _key); - try { - T result = null; - ResultSet rs = stmt.executeQuery(); - if (rs.next()) { - result = _marsh.createObject(rs); - } - // TODO: if (rs.next()) issue warning? - rs.close(); - return result; - - } finally { - stmt.close(); - } + return _key; } - protected DepotMarshaller _marsh; - } + public abstract int invoke (Connection conn) + throws SQLException, PersistenceException; - protected static class ObjectCollectionQuery - extends Query> - { - public ObjectCollectionQuery (DepotMarshaller marsh, Key key) { - super(key); - _marsh = marsh; - } - - public ArrayList invoke (Connection conn) - throws SQLException, PersistenceException + protected Modifier (Key key) { - PreparedStatement stmt = _marsh.createQuery(conn, _key); - try { - ArrayList results = new ArrayList(); - ResultSet rs = stmt.executeQuery(); - while (rs.next()) { - results.add(_marsh.createObject(rs)); - } - return results; - - } finally { - stmt.close(); - } + _key = key; } - protected DepotMarshaller _marsh; + protected Key _key; } // protected static abstract class InstanceUpdate diff --git a/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java b/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java index 653c668e..b34c8d01 100644 --- a/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java +++ b/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java @@ -102,7 +102,7 @@ public abstract class FieldMarshaller * Reads the specified column from the supplied result set and writes it to * the appropriate field of the persistent object. */ - public abstract void getValue (ResultSet rset, int column, Object po) + public abstract void getValue (ResultSet rset, Object po) throws SQLException, IllegalAccessException; /** @@ -113,6 +113,14 @@ public abstract class FieldMarshaller return _field.getName(); } + /** + * Returns the {@link Field} handled by this marshaller. + */ + public Field getField () + { + return _field; + } + protected void init (Field field) { _field = field; @@ -123,9 +131,9 @@ public abstract class FieldMarshaller throws SQLException, IllegalAccessException { ps.setBoolean(column, _field.getBoolean(po)); } - public void getValue (ResultSet rs, int column, Object po) + public void getValue (ResultSet rs, Object po) throws SQLException, IllegalAccessException { - _field.setBoolean(po, rs.getBoolean(column)); + _field.setBoolean(po, rs.getBoolean(getColumnName())); } } @@ -134,9 +142,9 @@ public abstract class FieldMarshaller throws SQLException, IllegalAccessException { ps.setByte(column, _field.getByte(po)); } - public void getValue (ResultSet rs, int column, Object po) + public void getValue (ResultSet rs, Object po) throws SQLException, IllegalAccessException { - _field.setByte(po, rs.getByte(column)); + _field.setByte(po, rs.getByte(getColumnName())); } } @@ -145,9 +153,9 @@ public abstract class FieldMarshaller throws SQLException, IllegalAccessException { ps.setShort(column, _field.getShort(po)); } - public void getValue (ResultSet rs, int column, Object po) + public void getValue (ResultSet rs, Object po) throws SQLException, IllegalAccessException { - _field.setShort(po, rs.getShort(column)); + _field.setShort(po, rs.getShort(getColumnName())); } } @@ -156,9 +164,9 @@ public abstract class FieldMarshaller throws SQLException, IllegalAccessException { ps.setInt(column, _field.getInt(po)); } - public void getValue (ResultSet rs, int column, Object po) + public void getValue (ResultSet rs, Object po) throws SQLException, IllegalAccessException { - _field.setInt(po, rs.getInt(column)); + _field.setInt(po, rs.getInt(getColumnName())); } } @@ -167,9 +175,9 @@ public abstract class FieldMarshaller throws SQLException, IllegalAccessException { ps.setLong(column, _field.getLong(po)); } - public void getValue (ResultSet rs, int column, Object po) + public void getValue (ResultSet rs, Object po) throws SQLException, IllegalAccessException { - _field.setLong(po, rs.getLong(column)); + _field.setLong(po, rs.getLong(getColumnName())); } } @@ -178,9 +186,9 @@ public abstract class FieldMarshaller throws SQLException, IllegalAccessException { ps.setFloat(column, _field.getFloat(po)); } - public void getValue (ResultSet rs, int column, Object po) + public void getValue (ResultSet rs, Object po) throws SQLException, IllegalAccessException { - _field.setFloat(po, rs.getFloat(column)); + _field.setFloat(po, rs.getFloat(getColumnName())); } } @@ -189,9 +197,9 @@ public abstract class FieldMarshaller throws SQLException, IllegalAccessException { ps.setDouble(column, _field.getDouble(po)); } - public void getValue (ResultSet rs, int column, Object po) + public void getValue (ResultSet rs, Object po) throws SQLException, IllegalAccessException { - _field.setDouble(po, rs.getDouble(column)); + _field.setDouble(po, rs.getDouble(getColumnName())); } } @@ -200,9 +208,9 @@ public abstract class FieldMarshaller throws SQLException, IllegalAccessException { ps.setObject(column, _field.get(po)); } - public void getValue (ResultSet rs, int column, Object po) + public void getValue (ResultSet rs, Object po) throws SQLException, IllegalAccessException { - _field.set(po, rs.getObject(column)); + _field.set(po, rs.getObject(getColumnName())); } } diff --git a/tests/src/java/com/samskivert/jdbc/depot/TestRecord.java b/tests/src/java/com/samskivert/jdbc/depot/TestRecord.java new file mode 100644 index 00000000..f3ac6f3b --- /dev/null +++ b/tests/src/java/com/samskivert/jdbc/depot/TestRecord.java @@ -0,0 +1,33 @@ +// +// $Id$ + +package com.samskivert.jdbc.depot; + +import java.sql.Date; +import java.sql.Timestamp; + +import javax.persistence.Id; + +import com.samskivert.util.StringUtil; + +/** + * A test persistent object. + */ +public class TestRecord +{ + @Id + public int recordId; + + public String name; + + public int age; + + public Date created; + + public Timestamp lastModified; + + public String toString () + { + return StringUtil.fieldsToString(this); + } +} diff --git a/tests/src/java/com/samskivert/jdbc/depot/TestRepository.java b/tests/src/java/com/samskivert/jdbc/depot/TestRepository.java new file mode 100644 index 00000000..a589579c --- /dev/null +++ b/tests/src/java/com/samskivert/jdbc/depot/TestRepository.java @@ -0,0 +1,38 @@ +// +// $Id$ + +package com.samskivert.jdbc.depot; + +import java.sql.Date; +import java.sql.Timestamp; + +import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.jdbc.StaticConnectionProvider; + +/** + * A test tool for the Depot repository services. + */ +public class TestRepository extends DepotRepository +{ + public static void main (String[] args) + throws Exception + { + TestRepository repo = new TestRepository( + new StaticConnectionProvider("depot.properties")); + + TestRecord record = new TestRecord(); + record.name = "Elvis"; + record.age = 99; + record.created = new Date(System.currentTimeMillis()); + record.lastModified = new Timestamp(System.currentTimeMillis()); + + repo.insert(record); + + System.out.println(repo.load(TestRecord.class, record.recordId)); + } + + public TestRepository (ConnectionProvider conprov) + { + super(conprov); + } +}