Generalize the automatic key value generation code so that it works on any column, and on any number of columns within a record.

This commit is contained in:
Par Winzell
2007-08-21 15:02:12 +00:00
parent 60d63b13f1
commit 8e446fe93f
9 changed files with 118 additions and 129 deletions
@@ -22,6 +22,7 @@ package com.samskivert.jdbc.depot;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.Set;
import com.samskivert.jdbc.depot.Key.WhereCondition;
import com.samskivert.jdbc.depot.clause.DeleteClause;
@@ -240,9 +241,9 @@ public class BindVisitor implements ExpressionVisitor
DepotMarshaller marsh = _types.getMarshaller(insertClause.getPersistentClass());
Object pojo = insertClause.getPojo();
String ixField = insertClause.getIndexField();
Set<String> generatedFields = insertClause.getIdentityFields();
for (String field : marsh.getColumnFieldNames()) {
if (ixField == null || !ixField.equals(field)) {
if (!generatedFields.contains(field)) {
marsh.getFieldMarshaller(field).readFromObject(pojo, _stmt, _argIdx ++);
}
}
@@ -24,6 +24,7 @@ import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.samskivert.jdbc.depot.Key.WhereCondition;
import com.samskivert.jdbc.depot.annotation.Computed;
@@ -450,12 +451,12 @@ public abstract class BuildVisitor implements ExpressionVisitor
}
_builder.append(") values(");
String ixField = insertClause.getIndexField();
Set<String> generatedFields = insertClause.getIdentityFields();
for (int ii = 0; ii < fields.length; ii++) {
if (ii > 0) {
_builder.append(", ");
}
if (ixField != null && ixField.equals(fields[ii])) {
if (generatedFields.contains(fields[ii])) {
_builder.append("DEFAULT");
} else {
_builder.append("?");
@@ -103,6 +103,8 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
boolean seenIdentityGenerator = false;
// introspect on the class and create marshallers for persistent fields
ArrayList<String> fields = new ArrayList<String>();
for (Field field : _pclass.getFields()) {
@@ -136,38 +138,19 @@ public class DepotMarshaller<T extends PersistentRecord>
_pkColumns = new ArrayList<FieldMarshaller>();
}
_pkColumns.add(fm);
}
// check if this field defines a new TableGenerator
generator = field.getAnnotation(TableGenerator.class);
if (generator != null) {
context.tableGenerators.put(generator.name(), generator);
}
}
}
// if the entity defines a single-columnar primary key, figure out if we will be generating
// values for it
if (_pkColumns != null) {
GeneratedValue gv = null;
FieldMarshaller keyField = null;
// loop over fields to see if there's a @GeneratedValue at all
for (FieldMarshaller field : _pkColumns) {
gv = field.getGeneratedValue();
// check if this field is auto-generated
GeneratedValue gv = fm.getGeneratedValue();
if (gv != null) {
keyField = field;
break;
}
}
if (keyField != null) {
// and if there is, make sure we've a single-column id
if (_pkColumns.size() > 1) {
throw new IllegalArgumentException(
"Cannot use @GeneratedValue on multiple-column @Id's");
}
// the primary key must be numeric if we are to auto-assign it
Class<?> ftype = keyField.getField().getType();
// we can only do this on numeric fields
Class<?> ftype = field.getType();
boolean isNumeric = (
ftype.equals(Byte.TYPE) || ftype.equals(Byte.class) ||
ftype.equals(Short.TYPE) || ftype.equals(Short.class) ||
@@ -175,14 +158,17 @@ public class DepotMarshaller<T extends PersistentRecord>
ftype.equals(Long.TYPE) || ftype.equals(Long.class));
if (!isNumeric) {
throw new IllegalArgumentException(
"Cannot use @GeneratedValue on non-numeric column");
"Cannot use @GeneratedValue on non-numeric column: " + field.getName());
}
switch(gv.strategy()) {
case AUTO:
case IDENTITY:
_keyGenerator = new IdentityKeyGenerator(
gv, getTableName(), keyField.getColumnName());
if (seenIdentityGenerator) {
throw new IllegalArgumentException(
"Persistent records can have at most one AUTO/IDENTITY generator.");
}
_valueGenerators.put(field.getName(), new IdentityValueGenerator(gv, this, fm));
seenIdentityGenerator = true;
break;
case TABLE:
@@ -192,11 +178,12 @@ public class DepotMarshaller<T extends PersistentRecord>
throw new IllegalArgumentException(
"Unknown generator [generator=" + name + "]");
}
_keyGenerator = new TableKeyGenerator(
generator, gv, getTableName(), keyField.getColumnName());
_valueGenerators.put(
field.getName(), new TableValueGenerator(generator, gv, this, fm));
break;
}
}
}
// generate our full list of fields/columns for use in queries
@@ -270,12 +257,12 @@ public class DepotMarshaller<T extends PersistentRecord>
}
/**
* Returns the {@link KeyGenerator} used to generate primary keys for this persistent object,
* Returns the {@link ValueGenerator} used to generate primary keys for this persistent object,
* or null if it does not use a key generator.
*/
public KeyGenerator getKeyGenerator ()
public Iterable<ValueGenerator> getValueGenerators ()
{
return _keyGenerator;
return _valueGenerators.values();
}
/**
@@ -435,36 +422,39 @@ public class DepotMarshaller<T extends PersistentRecord>
}
/**
* Fills in the primary key just assigned to the supplied persistence object by the execution
* of the results of {@link #createInsert}.
* Go through the registered {@link ValueGenerator}s for our persistent object and run the
* ones that match the current postFactum phase, filling in the fields on the supplied object
* while we go. This method used to generate a key; that is now a separate step.
*
* @return the newly assigned primary key or null if the object does not use primary keys or
* this is not the right time to assign the key.
* The return value is only non-empty for the !postFactum phase, in which case it is a set
* of field names that are associated with {@link IdentityValueGenerator}, because these need
* special handling in the INSERT (specifically, 'DEFAULT' must be supplied as a value in
* the eventual SQL).
*/
public Key assignPrimaryKey (
public Set<String> generateFieldValues (
Connection conn, DatabaseLiaison liaison, Object po, boolean postFactum)
throws SQLException
{
// if we have no primary key or no generator, then we're done
if (!hasPrimaryKey() || _keyGenerator == null) {
return null;
}
Set<String> idFields = new HashSet<String>();
// run this generator either before or after the actual insertion
if (_keyGenerator.isPostFactum() != postFactum) {
return null;
for (ValueGenerator vg : _valueGenerators.values()) {
if (!postFactum && vg instanceof IdentityValueGenerator) {
idFields.add(vg.getFieldMarshaller().getField().getName());
}
if (vg.isPostFactum() != postFactum) {
continue;
}
try {
int nextValue = _keyGenerator.nextGeneratedValue(conn, liaison);
_pkColumns.get(0).getField().set(po, nextValue);
return makePrimaryKey(nextValue);
} catch (Exception e) {
String errmsg = "Failed to assign primary key [type=" + _pclass + "]";
throw (SQLException) new SQLException(errmsg).initCause(e);
}
}
int nextValue = vg.nextGeneratedValue(conn, liaison);
vg.getFieldMarshaller().getField().set(po, nextValue);
} catch (Exception e) {
throw new IllegalStateException(
"Failed to assign primary key [type=" + _pclass + "]", e);
}
}
return idFields;
}
/**
* This is called by the persistence context to register a migration for the entity managed by
@@ -575,9 +565,9 @@ public class DepotMarshaller<T extends PersistentRecord>
getTableName() + "_" + idx.name(), idx.unique());
}
// its key generator
if (_keyGenerator != null) {
_keyGenerator.init(conn, liaison);
// its value generators
for (ValueGenerator vg : _valueGenerators.values()) {
vg.init(conn, liaison);
}
// and its full text search indexes
@@ -969,6 +959,9 @@ public class DepotMarshaller<T extends PersistentRecord>
/** The @Computed annotation of this entity, or null. */
protected Computed _computed;
/** A mapping of field names to value generators for that field. */
protected Map<String, ValueGenerator> _valueGenerators = new HashMap<String, ValueGenerator>();
/** A field marshaller for each persistent field in our object. */
protected Map<String, FieldMarshaller> _fields = new HashMap<String, FieldMarshaller>();
@@ -976,9 +969,6 @@ public class DepotMarshaller<T extends PersistentRecord>
* define a primary key. */
protected ArrayList<FieldMarshaller> _pkColumns;
/** The generator to use for auto-generating primary key values, or null. */
protected KeyGenerator _keyGenerator;
/** The persisent fields of our object, in definition order. */
protected String[] _allFields;
@@ -999,4 +989,5 @@ public class DepotMarshaller<T extends PersistentRecord>
/** The name of the table we use to track schema versions. */
protected static final String SCHEMA_VERSION_TABLE = "DepotSchemaVersion";
}
@@ -37,10 +37,7 @@ import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.Modifier.*;
import com.samskivert.jdbc.depot.clause.DeleteClause;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.FromOverride;
import com.samskivert.jdbc.depot.clause.InsertClause;
import com.samskivert.jdbc.depot.clause.Join;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.UpdateClause;
import com.samskivert.jdbc.depot.expression.SQLExpression;
@@ -175,24 +172,26 @@ public abstract class DepotRepository
// key will be null if record was supplied without a primary key
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// update our modifier's key so that it can cache our results
// set any auto-generated column values
Set<String> identityFields = marsh.generateFieldValues(conn, liaison, _result, false);
// if needed, update our modifier's key so that it can cache our results
if (_key == null) {
updateKey(marsh.assignPrimaryKey(conn, liaison, _result, false));
updateKey(marsh.getPrimaryKey(_result, false));
}
String ixField = null;
if (_key == null && marsh.hasPrimaryKey() && marsh.getKeyGenerator() != null &&
marsh.getKeyGenerator() instanceof IdentityKeyGenerator) {
ixField = ((IdentityKeyGenerator) marsh.getKeyGenerator()).getColumn();
}
builder.newQuery(new InsertClause<T>(pClass, _result, ixField));
builder.newQuery(new InsertClause<T>(pClass, _result, identityFields));
PreparedStatement stmt = builder.prepare(conn);
try {
int mods = stmt.executeUpdate();
// check again in case we have a post-factum key generator
// run any post-factum value generators
marsh.generateFieldValues(conn, liaison, _result, true);
// and check once more if a key now exists
if (_key == null) {
updateKey(marsh.assignPrimaryKey(conn, liaison, _result, true));
updateKey(marsh.getPrimaryKey(_result, false));
}
return mods;
} finally {
@@ -575,22 +574,25 @@ public abstract class DepotRepository
}
// if the update modified zero rows or the primary key was obviously unset, do
// an insertion
// an insertion: first, set any auto-generated column values
Set<String> identityFields = marsh.generateFieldValues(conn, liaison, _result, false);
// update our modifier's key so that it can cache our results
if (_key == null) {
updateKey(marsh.assignPrimaryKey(conn, liaison, _result, false));
updateKey(marsh.getPrimaryKey(_result, false));
}
String ixField = null;
if (_key == null && marsh.hasPrimaryKey() && marsh.getKeyGenerator() != null &&
marsh.getKeyGenerator() instanceof IdentityKeyGenerator) {
ixField = ((IdentityKeyGenerator) marsh.getKeyGenerator()).getColumn();
}
builder.newQuery(new InsertClause<T>(pClass, _result, ixField));
builder.newQuery(new InsertClause<T>(pClass, _result, identityFields));
stmt = builder.prepare(conn);
int mods = stmt.executeUpdate();
// run any post-factum value generators
marsh.generateFieldValues(conn, liaison, _result, true);
// and check once more if a key now exists
if (_key == null) {
updateKey(marsh.assignPrimaryKey(conn, liaison, _result, true));
updateKey(marsh.getPrimaryKey(_result, false));
}
created[0] = true;
return mods;
@@ -222,11 +222,9 @@ public abstract class FieldMarshaller<T>
return;
}
if (field.getAnnotation(Id.class) != null) {
// figure out how we're going to generate our primary key values
_generatedValue = field.getAnnotation(GeneratedValue.class);
}
}
protected static class BooleanMarshaller extends FieldMarshaller<Boolean> {
public Boolean getFromObject (Object po)
@@ -24,17 +24,16 @@ import java.sql.Connection;
import java.sql.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.LiaisonRegistry;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
/**
* Generates primary keys using an identity column.
*/
public class IdentityKeyGenerator extends KeyGenerator
public class IdentityValueGenerator extends ValueGenerator
{
public IdentityKeyGenerator (GeneratedValue gv, String table, String column)
public IdentityValueGenerator (GeneratedValue gv, DepotMarshaller dm, FieldMarshaller fm)
{
super(gv, table, column);
super(gv, dm, fm);
}
// from interface KeyGenerator
@@ -47,13 +46,14 @@ public class IdentityKeyGenerator extends KeyGenerator
public void init (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
liaison.initializeGenerator(conn, _table, _column, _initialValue, _allocationSize);
liaison.initializeGenerator(
conn, _dm.getTableName(), _fm.getColumnName(), _initialValue, _allocationSize);
}
// from interface KeyGenerator
public int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
return liaison.lastInsertedId(conn, _table, _column);
return liaison.lastInsertedId(conn, _dm.getTableName(), _fm.getColumnName());
}
}
@@ -30,16 +30,15 @@ import com.samskivert.jdbc.depot.annotation.TableGenerator;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.LiaisonRegistry;
/**
* Generates primary keys using an external table .
*/
public class TableKeyGenerator extends KeyGenerator
public class TableValueGenerator extends ValueGenerator
{
public TableKeyGenerator (TableGenerator tg, GeneratedValue gv, String table, String column)
public TableValueGenerator (TableGenerator tg, GeneratedValue gv, DepotMarshaller dm, FieldMarshaller fm)
{
super(gv, table, column);
super(gv, dm, fm);
_valueTable = defStr(tg.table(), "IdSequences");
_pkColumnName = defStr(tg.pkColumnName(), "sequence");
_pkColumnValue = defStr(tg.pkColumnValue(), "default");
@@ -27,16 +27,16 @@ import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
/**
* Defines the interface to our primary key generators.
* Defines the interface to our value generators.
*/
public abstract class KeyGenerator
public abstract class ValueGenerator
{
public KeyGenerator (GeneratedValue gv, String table, String column)
public ValueGenerator (GeneratedValue gv, DepotMarshaller dm, FieldMarshaller fm)
{
_allocationSize = gv.allocationSize();
_initialValue = gv.initialValue();
_table = table;
_column = column;
_dm = dm;
_fm = fm;
}
/**
@@ -57,24 +57,19 @@ public abstract class KeyGenerator
public abstract int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Returns the name of the table for which we're generating a key value.
*/
public String getTable ()
public DepotMarshaller getDepotMarshaller ()
{
return _table;
return _dm;
}
/**
* Returns the name of the column for which we're generating a key value.
*/
public String getColumn ()
public FieldMarshaller getFieldMarshaller ()
{
return _column;
return _fm;
}
protected int _initialValue;
protected int _allocationSize;
protected String _table;
protected String _column;
protected DepotMarshaller _dm;
protected FieldMarshaller _fm;
}
@@ -21,6 +21,7 @@
package com.samskivert.jdbc.depot.clause;
import java.util.Collection;
import java.util.Set;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
@@ -30,11 +31,12 @@ import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
*/
public class InsertClause<T extends PersistentRecord> extends QueryClause
{
public InsertClause (Class<? extends PersistentRecord> pClass, Object pojo, String ixField)
public InsertClause (
Class<? extends PersistentRecord> pClass, Object pojo, Set<String> identityFields)
{
_pClass = pClass;
_pojo = pojo;
_ixField = ixField;
_idFields = identityFields;
}
public Class<? extends PersistentRecord> getPersistentClass ()
@@ -47,9 +49,9 @@ public class InsertClause<T extends PersistentRecord> extends QueryClause
return _pojo;
}
public String getIndexField ()
public Set<String> getIdentityFields ()
{
return _ixField;
return _idFields;
}
// from SQLExpression
@@ -70,5 +72,5 @@ public class InsertClause<T extends PersistentRecord> extends QueryClause
/** The object from which to fetch values, or null. */
protected Object _pojo;
protected String _ixField;
protected Set<String> _idFields;
}