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:
@@ -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();
|
||||
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");
|
||||
}
|
||||
// check if this field defines a new TableGenerator
|
||||
generator = field.getAnnotation(TableGenerator.class);
|
||||
if (generator != null) {
|
||||
context.tableGenerators.put(generator.name(), generator);
|
||||
}
|
||||
|
||||
// the primary key must be numeric if we are to auto-assign it
|
||||
Class<?> ftype = keyField.getField().getType();
|
||||
// check if this field is auto-generated
|
||||
GeneratedValue gv = fm.getGeneratedValue();
|
||||
if (gv != null) {
|
||||
// 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,37 +422,40 @@ 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);
|
||||
try {
|
||||
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
|
||||
* this marshaller.
|
||||
@@ -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,10 +222,8 @@ 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);
|
||||
}
|
||||
// figure out how we're going to generate our primary key values
|
||||
_generatedValue = field.getAnnotation(GeneratedValue.class);
|
||||
}
|
||||
|
||||
protected static class BooleanMarshaller extends FieldMarshaller<Boolean> {
|
||||
|
||||
+7
-7
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
|
||||
//
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -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");
|
||||
+12
-17
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user