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.
This commit is contained in:
Michael Bayne
2006-09-15 01:48:29 +00:00
parent bfa092b7d9
commit 5639410725
3 changed files with 240 additions and 97 deletions
@@ -28,9 +28,12 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import javax.persistence.Id; import javax.persistence.Id;
import com.samskivert.util.StringUtil;
/** /**
* Handles the marshalling and unmarshalling of persistent instances to JDBC * Handles the marshalling and unmarshalling of persistent instances to JDBC
* primitives ({@link PreparedStatement} and {@link ResultSet}). * primitives ({@link PreparedStatement} and {@link ResultSet}).
@@ -50,7 +53,6 @@ public class DepotMarshaller<T>
// introspect on the class and create field marshallers for all of its // introspect on the class and create field marshallers for all of its
// fields // fields
ArrayList<FieldMarshaller> flist = new ArrayList<FieldMarshaller>();
for (Field field : _pclass.getFields()) { for (Field field : _pclass.getFields()) {
// the field must be public, non-static and non-transient // the field must be public, non-static and non-transient
int mods = field.getModifiers(); int mods = field.getModifiers();
@@ -61,25 +63,22 @@ public class DepotMarshaller<T>
} }
FieldMarshaller fm = FieldMarshaller.createMarshaller(field); FieldMarshaller fm = FieldMarshaller.createMarshaller(field);
flist.add(fm); _fields.put(fm.getColumnName(), fm);
// check to see if this is our primary key // check to see if this is our primary key
for (Annotation ann : field.getDeclaredAnnotations()) { if (field.getAnnotation(Id.class) != null) {
System.err.println( // TODO: handle multiple field primary keys
"Annotation " + ann + " (" + ann.getClass() + ")"); _primaryKey = fm;
} }
} }
_fields = flist.toArray(new FieldMarshaller[flist.size()]);
// generate our full list of columns for use in queries // generate our full list of columns for use in queries
StringBuilder columns = new StringBuilder(); _allFields = new String[_fields.size()];
for (FieldMarshaller fm : _fields) { int idx = 0;
if (columns.length() > 0) { for (FieldMarshaller fm : _fields.values()) {
columns.append(","); _allFields[idx++] = fm.getColumnName();
}
columns.append(fm.getColumnName());
} }
_fullColumnList = columns.toString(); _fullColumnList = StringUtil.join(_allFields, ",");
} }
/** /**
@@ -93,12 +92,38 @@ public class DepotMarshaller<T>
} }
/** /**
* Returns the field name of the primary key for this persistent object * Returns the field/column name of the primary key for this persistent
* class or null if it did not declare a primary key. * 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<T>
} }
/** /**
* 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) public T createObject (ResultSet rs)
throws SQLException throws SQLException
{ {
try { try {
T po = (T)_pclass.newInstance(); T po = (T)_pclass.newInstance();
for (int ii = 0; ii < _fields.length; ii++) { for (FieldMarshaller fm : _fields.values()) {
_fields[ii].getValue(rs, ii+1, po); fm.getValue(rs, po);
} }
return po; return po;
} catch (SQLException sqe) {
// pass this on through
throw sqe;
} catch (Exception e) { } catch (Exception e) {
String errmsg = "Failed to unmarshall persistent object " + String errmsg = "Failed to unmarshall persistent object " +
"[pclass=" + _pclass.getName() + "]"; "[pclass=" + _pclass.getName() + "]";
@@ -144,7 +175,36 @@ public class DepotMarshaller<T>
public PreparedStatement createInsert (Connection conn, Object po) public PreparedStatement createInsert (Connection conn, Object po)
throws SQLException 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<T>
Connection conn, Object po, DepotRepository.Key key) Connection conn, Object po, DepotRepository.Key key)
throws SQLException 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<T> _pclass; protected Class<T> _pclass;
protected FieldMarshaller[] _fields; protected HashMap<String, FieldMarshaller> _fields =
new HashMap<String, FieldMarshaller>();
protected String _tableName; protected String _tableName;
protected FieldMarshaller _primaryKey; protected FieldMarshaller _primaryKey;
protected String _fullColumnList; protected String _fullColumnList;
protected String[] _allFields;
} }
@@ -46,23 +46,58 @@ public class DepotRepository
throws PersistenceException throws PersistenceException
{ {
DepotMarshaller<T> marsh = getMarshaller(type); DepotMarshaller<T> marsh = getMarshaller(type);
Key key = new Key(marsh.getPrimaryKey(), primaryKey); return load(type, new Key(marsh.getPrimaryKeyColumn(), primaryKey));
return invoke(new ObjectQuery<T>(marsh, key));
} }
protected <T> T load (Class<T> type, Key key) protected <T> T load (Class<T> type, Key key)
throws PersistenceException throws PersistenceException
{ {
DepotMarshaller<T> marsh = getMarshaller(type); final DepotMarshaller<T> marsh = getMarshaller(type);
return invoke(new ObjectQuery<T>(marsh, key)); return invoke(new Query<T>(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 <T,C extends Collection<T>> Collection<T> findAll ( protected <T,C extends Collection<T>> Collection<T> findAll (
Class<T> type, Key key) Class<T> type, Key key)
throws PersistenceException throws PersistenceException
{ {
DepotMarshaller<T> marsh = getMarshaller(type); final DepotMarshaller<T> marsh = getMarshaller(type);
return invoke(new ObjectCollectionQuery<T>(marsh, key)); return invoke(new Query<ArrayList<T>>(key) {
public ArrayList<T> invoke (Connection conn)
throws SQLException, PersistenceException
{
PreparedStatement stmt = marsh.createQuery(conn, _key);
try {
ArrayList<T> results = new ArrayList<T>();
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
results.add(marsh.createObject(rs));
}
return results;
} finally {
stmt.close();
}
}
});
} }
protected <T extends Collection> T findAll ( protected <T extends Collection> T findAll (
@@ -72,14 +107,41 @@ public class DepotRepository
return null; return null;
} }
protected void insert (Object record) protected void insert (final Object record)
throws PersistenceException 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 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; 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 * Returns the identifier to be used when obtaining JDBC connections from
* our connection provider. The default implementation uses the class name * our connection provider. The default implementation uses the class name
@@ -237,61 +317,22 @@ public class DepotRepository
throws SQLException, PersistenceException; throws SQLException, PersistenceException;
} }
protected static class ObjectQuery<T> extends Query<T> protected static abstract class Modifier
{ {
public ObjectQuery (DepotMarshaller<T> marsh, Key key) { public Key getKey ()
super(key);
_marsh = marsh;
}
public T invoke (Connection conn)
throws SQLException, PersistenceException
{ {
PreparedStatement stmt = _marsh.createQuery(conn, _key); return _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 DepotMarshaller<T> _marsh; public abstract int invoke (Connection conn)
} throws SQLException, PersistenceException;
protected static class ObjectCollectionQuery<T> protected Modifier (Key key)
extends Query<ArrayList<T>>
{
public ObjectCollectionQuery (DepotMarshaller<T> marsh, Key key) {
super(key);
_marsh = marsh;
}
public ArrayList<T> invoke (Connection conn)
throws SQLException, PersistenceException
{ {
PreparedStatement stmt = _marsh.createQuery(conn, _key); _key = key;
try {
ArrayList<T> results = new ArrayList<T>();
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
results.add(_marsh.createObject(rs));
}
return results;
} finally {
stmt.close();
}
} }
protected DepotMarshaller<T> _marsh; protected Key _key;
} }
// protected static abstract class InstanceUpdate<T> // protected static abstract class InstanceUpdate<T>
@@ -102,7 +102,7 @@ public abstract class FieldMarshaller
* Reads the specified column from the supplied result set and writes it to * Reads the specified column from the supplied result set and writes it to
* the appropriate field of the persistent object. * 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; throws SQLException, IllegalAccessException;
/** /**
@@ -113,6 +113,14 @@ public abstract class FieldMarshaller
return _field.getName(); return _field.getName();
} }
/**
* Returns the {@link Field} handled by this marshaller.
*/
public Field getField ()
{
return _field;
}
protected void init (Field field) protected void init (Field field)
{ {
_field = field; _field = field;
@@ -123,9 +131,9 @@ public abstract class FieldMarshaller
throws SQLException, IllegalAccessException { throws SQLException, IllegalAccessException {
ps.setBoolean(column, _field.getBoolean(po)); 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 { 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 { throws SQLException, IllegalAccessException {
ps.setByte(column, _field.getByte(po)); 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 { 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 { throws SQLException, IllegalAccessException {
ps.setShort(column, _field.getShort(po)); 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 { 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 { throws SQLException, IllegalAccessException {
ps.setInt(column, _field.getInt(po)); 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 { 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 { throws SQLException, IllegalAccessException {
ps.setLong(column, _field.getLong(po)); 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 { 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 { throws SQLException, IllegalAccessException {
ps.setFloat(column, _field.getFloat(po)); 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 { 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 { throws SQLException, IllegalAccessException {
ps.setDouble(column, _field.getDouble(po)); 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 { 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 { throws SQLException, IllegalAccessException {
ps.setObject(column, _field.get(po)); 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 { throws SQLException, IllegalAccessException {
_field.set(po, rs.getObject(column)); _field.set(po, rs.getObject(getColumnName()));
} }
} }