From 563941072592c0390dbcc5240270e5b06031a83e Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 15 Sep 2006 01:48:29 +0000 Subject: [PATCH] Further progress on the ORM stuff. Miraculously, upon running this nearly thousand lines of super funny looking generics and reflection filled code the first time, it just worked. Yay for static typing or something. --- .../jdbc/depot/DepotMarshaller.java | 144 ++++++++++++++--- .../jdbc/depot/DepotRepository.java | 151 +++++++++++------- .../jdbc/depot/FieldMarshaller.java | 42 +++-- 3 files changed, 240 insertions(+), 97 deletions(-) diff --git a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java index 5671b2b..f81ee0e 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 f96a48b..2abfa54 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 653c668..b34c8d0 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())); } }