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.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<T>
// introspect on the class and create field marshallers for all of its
// fields
ArrayList<FieldMarshaller> flist = new ArrayList<FieldMarshaller>();
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<T>
}
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<T>
}
/**
* 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<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)
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<T>
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<T>
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<T> _pclass;
protected FieldMarshaller[] _fields;
protected HashMap<String, FieldMarshaller> _fields =
new HashMap<String, FieldMarshaller>();
protected String _tableName;
protected FieldMarshaller _primaryKey;
protected String _fullColumnList;
protected String[] _allFields;
}
@@ -46,23 +46,58 @@ public class DepotRepository
throws PersistenceException
{
DepotMarshaller<T> marsh = getMarshaller(type);
Key key = new Key(marsh.getPrimaryKey(), primaryKey);
return invoke(new ObjectQuery<T>(marsh, key));
return load(type, new Key(marsh.getPrimaryKeyColumn(), primaryKey));
}
protected <T> T load (Class<T> type, Key key)
throws PersistenceException
{
DepotMarshaller<T> marsh = getMarshaller(type);
return invoke(new ObjectQuery<T>(marsh, key));
final DepotMarshaller<T> marsh = getMarshaller(type);
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 (
Class<T> type, Key key)
throws PersistenceException
{
DepotMarshaller<T> marsh = getMarshaller(type);
return invoke(new ObjectCollectionQuery<T>(marsh, key));
final DepotMarshaller<T> marsh = getMarshaller(type);
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 (
@@ -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<T> extends Query<T>
protected static abstract class Modifier
{
public ObjectQuery (DepotMarshaller<T> 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<T> _marsh;
}
public abstract int invoke (Connection conn)
throws SQLException, PersistenceException;
protected static class ObjectCollectionQuery<T>
extends Query<ArrayList<T>>
{
public ObjectCollectionQuery (DepotMarshaller<T> marsh, Key key) {
super(key);
_marsh = marsh;
}
public ArrayList<T> invoke (Connection conn)
throws SQLException, PersistenceException
protected Modifier (Key key)
{
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();
}
_key = key;
}
protected DepotMarshaller<T> _marsh;
protected Key _key;
}
// 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
* 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()));
}
}