External table primary key generation from Zell, with some rearranging.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2006 Michael Bayne
|
||||
//
|
||||
//
|
||||
// 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
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
package com.samskivert.jdbc.depot;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
@@ -32,7 +31,9 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.TableGenerator;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import com.samskivert.jdbc.JDBCUtil;
|
||||
@@ -40,6 +41,7 @@ import com.samskivert.util.StringUtil;
|
||||
|
||||
import static com.samskivert.jdbc.depot.Log.log;
|
||||
|
||||
|
||||
/**
|
||||
* Handles the marshalling and unmarshalling of persistent instances to JDBC
|
||||
* primitives ({@link PreparedStatement} and {@link ResultSet}).
|
||||
@@ -63,6 +65,13 @@ public class DepotMarshaller<T>
|
||||
_tableName = _pclass.getName();
|
||||
_tableName = _tableName.substring(_tableName.lastIndexOf(".")+1);
|
||||
|
||||
// if the entity defines a new TableGenerator, map that in our static
|
||||
// table as those are shared across all entities
|
||||
TableGenerator generator = pclass.getAnnotation(TableGenerator.class);
|
||||
if (generator != null) {
|
||||
_generators.put(generator.name(), generator);
|
||||
}
|
||||
|
||||
// introspect on the class and create marshallers for persistent fields
|
||||
ArrayList<String> fields = new ArrayList<String>();
|
||||
for (Field field : _pclass.getFields()) {
|
||||
@@ -94,6 +103,45 @@ public class DepotMarshaller<T>
|
||||
if (field.getAnnotation(Id.class) != null) {
|
||||
// TODO: handle multiple field primary keys
|
||||
_primaryKey = fm;
|
||||
|
||||
// check if this field defines a new TableGenerator
|
||||
generator = field.getAnnotation(TableGenerator.class);
|
||||
if (generator != null) {
|
||||
_generators.put(generator.name(), generator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the entity defines a primary key, figure out how we will be
|
||||
// generating values for it
|
||||
if (_primaryKey != null) {
|
||||
// the primary key must be numeric if we are to auto-assign it
|
||||
Class<?> ftype = _primaryKey.getField().getType();
|
||||
boolean isNumeric = (ftype.equals(Byte.TYPE) ||
|
||||
ftype.equals(Byte.class) || ftype.equals(Short.TYPE) ||
|
||||
ftype.equals(Short.class) || ftype.equals(Integer.TYPE) ||
|
||||
ftype.equals(Integer.class) || ftype.equals(Long.TYPE) ||
|
||||
ftype.equals(Long.class));
|
||||
|
||||
// and it will have to have some sort of annotation
|
||||
GeneratedValue gv = _primaryKey.getGeneratedValue();
|
||||
if (isNumeric && gv != null) {
|
||||
switch(gv.strategy()) {
|
||||
case AUTO:
|
||||
case IDENTITY:
|
||||
_keyGenerator = new IdentityKeyGenerator();
|
||||
break;
|
||||
|
||||
case TABLE:
|
||||
String name = gv.generator();
|
||||
generator = _generators.get(name);
|
||||
if (generator == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unknown generator [generator=" + name + "]");
|
||||
}
|
||||
_keyGenerator = new TableKeyGenerator(generator);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +152,7 @@ public class DepotMarshaller<T>
|
||||
// create the SQL used to create and migrate our table
|
||||
_columnDefinitions = new String[_allFields.length];
|
||||
for (int ii = 0; ii < _allFields.length; ii++) {
|
||||
_columnDefinitions[ii] =
|
||||
_columnDefinitions[ii] =
|
||||
_fields.get(_allFields[ii]).getColumnDefinition();
|
||||
}
|
||||
_postamble = ""; // TODO: add annotations for the postamble
|
||||
@@ -177,7 +225,10 @@ public class DepotMarshaller<T>
|
||||
JDBCUtil.createTableIfMissing(
|
||||
conn, getTableName(), _columnDefinitions, _postamble);
|
||||
// TODO: insert current version into version table
|
||||
return;
|
||||
}
|
||||
// if there is a key generator, initialize that too
|
||||
if (_keyGenerator != null) {
|
||||
_keyGenerator.init(conn);
|
||||
}
|
||||
|
||||
// if schema versioning is disabled, stop now
|
||||
@@ -280,44 +331,29 @@ public class DepotMarshaller<T>
|
||||
|
||||
/**
|
||||
* Fills in the primary key just assigned to the supplied persistence
|
||||
* object by the execution of the results of {@link #createInsert}. The
|
||||
* structure of primary key assignment will probably have to change when we
|
||||
* support other databases.
|
||||
* object by the execution of the results of {@link #createInsert}.
|
||||
*/
|
||||
public void assignPrimaryKey (Connection conn, Object po)
|
||||
public void assignPrimaryKey (
|
||||
Connection conn, Object po, boolean postFactum)
|
||||
throws SQLException
|
||||
{
|
||||
// no primary key, no problem!
|
||||
if (_primaryKey == null) {
|
||||
// if we have no primary key or no generator, then we're done
|
||||
if (_primaryKey == null || _keyGenerator == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the primary key is non-numeric, we can't auto-assign it
|
||||
Class<?> ftype = _primaryKey.getField().getType();
|
||||
if (!ftype.equals(Byte.TYPE) && !ftype.equals(Byte.class) &&
|
||||
!ftype.equals(Short.TYPE) && !ftype.equals(Short.class) &&
|
||||
!ftype.equals(Integer.TYPE) && !ftype.equals(Integer.class) &&
|
||||
!ftype.equals(Long.TYPE) && !ftype.equals(Long.class)) {
|
||||
// run this generator either before or after the actual insertion
|
||||
if (_keyGenerator.isPostFactum() != postFactum) {
|
||||
return;
|
||||
}
|
||||
|
||||
// load up the last inserted ID mysql style; eventually this will be
|
||||
// fancier and pluggable
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()");
|
||||
if (rs.next()) {
|
||||
_primaryKey.getField().set(po, rs.getInt(1));
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException iae) {
|
||||
int nextValue = _keyGenerator.nextGeneratedValue(conn);
|
||||
_primaryKey.getField().set(po, nextValue);
|
||||
} catch (Exception e) {
|
||||
String errmsg = "Failed to assign primary key " +
|
||||
"[type=" + _pclass + "]";
|
||||
throw (SQLException)new SQLException(errmsg).initCause(iae);
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
throw (SQLException) new SQLException(errmsg).initCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,6 +442,20 @@ public class DepotMarshaller<T>
|
||||
return pstmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a statement that will delete all rows matching the supplied key.
|
||||
*/
|
||||
public PreparedStatement createDelete (
|
||||
Connection conn, DepotRepository.Key key)
|
||||
throws SQLException
|
||||
{
|
||||
String query = "delete from " + getTableName() +
|
||||
" where " + key.toWhereClause();
|
||||
PreparedStatement pstmt = conn.prepareStatement(query);
|
||||
key.bindArguments(pstmt, 1);
|
||||
return pstmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a statement that will update the specified set of fields, using
|
||||
* the supplied literal SQL values, for all persistent objects that match
|
||||
@@ -432,23 +482,12 @@ public class DepotMarshaller<T>
|
||||
return pstmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a statement that will delete all rows matching the supplied key.
|
||||
*/
|
||||
public PreparedStatement createDelete (
|
||||
Connection conn, DepotRepository.Key key)
|
||||
throws SQLException
|
||||
{
|
||||
String query = "delete from " + getTableName() +
|
||||
" where " + key.toWhereClause();
|
||||
PreparedStatement pstmt = conn.prepareStatement(query);
|
||||
key.bindArguments(pstmt, 1);
|
||||
return pstmt;
|
||||
}
|
||||
|
||||
/** The persistent object class that we manage. */
|
||||
protected Class<T> _pclass;
|
||||
|
||||
/** The name of our persistent object table. */
|
||||
protected String _tableName;
|
||||
|
||||
/** A field marshaller for each persistent field in our object. */
|
||||
protected HashMap<String, FieldMarshaller> _fields =
|
||||
new HashMap<String, FieldMarshaller>();
|
||||
@@ -457,8 +496,8 @@ public class DepotMarshaller<T>
|
||||
* it did not define a primary key. */
|
||||
protected FieldMarshaller _primaryKey;
|
||||
|
||||
/** The name of our persistent object table. */
|
||||
protected String _tableName;
|
||||
/** The generator to use for auto-generating primary key values, or null. */
|
||||
protected KeyGenerator _keyGenerator;
|
||||
|
||||
/** The persisent fields of our object, in definition order, separated by
|
||||
* commas for easy use in a select statement. */
|
||||
@@ -477,6 +516,10 @@ public class DepotMarshaller<T>
|
||||
/** Used when creating and migrating our table schema. */
|
||||
protected String _postamble;
|
||||
|
||||
/** A map of name to {@link TableGenerator}, scoped over all classes. */
|
||||
protected static HashMap<String, TableGenerator> _generators =
|
||||
new HashMap<String, TableGenerator>();
|
||||
|
||||
/** The name of the table we use to track schema versions. */
|
||||
protected static final String SCHEMA_VERSION_TABLE = "DepotSchemaVersion";
|
||||
}
|
||||
|
||||
@@ -127,10 +127,11 @@ public class DepotRepository
|
||||
final DepotMarshaller marsh = getMarshaller(record.getClass());
|
||||
return invoke(new Modifier(marsh.getPrimaryKey(record)) {
|
||||
public int invoke (Connection conn) throws SQLException {
|
||||
marsh.assignPrimaryKey(conn, record, false);
|
||||
PreparedStatement stmt = marsh.createInsert(conn, record);
|
||||
try {
|
||||
int mods = stmt.executeUpdate();
|
||||
marsh.assignPrimaryKey(conn, record);
|
||||
marsh.assignPrimaryKey(conn, record, true);
|
||||
return mods;
|
||||
} finally {
|
||||
stmt.close();
|
||||
@@ -342,9 +343,10 @@ public class DepotRepository
|
||||
|
||||
// if the update modified zero rows or the primary key was
|
||||
// obviously unset, do an insertion
|
||||
marsh.assignPrimaryKey(conn, record, false);
|
||||
stmt = marsh.createInsert(conn, record);
|
||||
int mods = stmt.executeUpdate();
|
||||
marsh.assignPrimaryKey(conn, record);
|
||||
marsh.assignPrimaryKey(conn, record, true);
|
||||
return mods;
|
||||
|
||||
} finally {
|
||||
|
||||
@@ -108,6 +108,14 @@ public abstract class FieldMarshaller
|
||||
return _field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GeneratedValue annotation on this field, if any.
|
||||
*/
|
||||
public GeneratedValue getGeneratedValue ()
|
||||
{
|
||||
return _generatedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the table column used to store this field.
|
||||
*/
|
||||
@@ -195,19 +203,19 @@ public abstract class FieldMarshaller
|
||||
builder.append(" PRIMARY KEY");
|
||||
|
||||
// figure out how we're going to generate our primary key values
|
||||
GeneratedValue gv = field.getAnnotation(GeneratedValue.class);
|
||||
if (gv != null) {
|
||||
switch (gv.strategy()) {
|
||||
_generatedValue = field.getAnnotation(GeneratedValue.class);
|
||||
if (_generatedValue != null) {
|
||||
switch (_generatedValue.strategy()) {
|
||||
case AUTO:
|
||||
case IDENTITY:
|
||||
builder.append(" AUTO_INCREMENT");
|
||||
break;
|
||||
case SEQUENCE: // TODO
|
||||
throw new IllegalArgumentException(
|
||||
"TABLE key generation strategy not yet supported.");
|
||||
case TABLE: // TODO
|
||||
throw new IllegalArgumentException(
|
||||
"TABLE key generation strategy not yet supported.");
|
||||
"SEQUENCE key generation strategy not yet supported.");
|
||||
case TABLE:
|
||||
// nothing to do here, it'll be handled later
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,4 +379,5 @@ public abstract class FieldMarshaller
|
||||
|
||||
protected Field _field;
|
||||
protected String _columnName, _columnDefinition;
|
||||
protected GeneratedValue _generatedValue;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2006 Michael Bayne
|
||||
//
|
||||
// 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
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc.depot;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import com.samskivert.jdbc.JDBCUtil;
|
||||
|
||||
/**
|
||||
* Generates primary keys using an identity column.
|
||||
*/
|
||||
public class IdentityKeyGenerator implements KeyGenerator
|
||||
{
|
||||
// from interface KeyGenerator
|
||||
public boolean isPostFactum ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// from interface KeyGenerator
|
||||
public void init (Connection conn)
|
||||
throws SQLException
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
// from interface KeyGenerator
|
||||
public int nextGeneratedValue (Connection conn)
|
||||
throws SQLException
|
||||
{
|
||||
// load up the last inserted ID mysql style
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()");
|
||||
if (rs.next()) {
|
||||
return rs.getInt(1);
|
||||
}
|
||||
throw new SQLException("Failed to generate next ID");
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2006 Michael Bayne
|
||||
//
|
||||
// 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
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc.depot;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Defines the interface to our primary key generators.
|
||||
*/
|
||||
public interface KeyGenerator
|
||||
{
|
||||
/** If true, this key generator will be run after the insert statement, if
|
||||
* false, it will be run before. */
|
||||
public boolean isPostFactum ();
|
||||
|
||||
/** Prepares the generator for operation. */
|
||||
public void init (Connection conn) throws SQLException;
|
||||
|
||||
/** Fetch/generate the next primary key value. */
|
||||
public int nextGeneratedValue (Connection conn) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2006 Michael Bayne
|
||||
//
|
||||
// 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
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc.depot;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import javax.persistence.TableGenerator;
|
||||
|
||||
import com.samskivert.jdbc.JDBCUtil;
|
||||
|
||||
/**
|
||||
* Generates primary keys using an external table
|
||||
*/
|
||||
public class TableKeyGenerator implements KeyGenerator
|
||||
{
|
||||
public TableKeyGenerator (TableGenerator annotation)
|
||||
{
|
||||
_table = defStr(annotation.table(), "IdSequences");
|
||||
_pkColumnName = defStr(annotation.pkColumnName(), "sequence");
|
||||
_pkColumnValue = defStr(annotation.pkColumnValue(), "default");
|
||||
_valueColumnName = defStr(annotation.valueColumnName(), "value");
|
||||
_allocationSize = annotation.allocationSize();
|
||||
_initialValue = _allocationSize > 0 ? _allocationSize : 1;
|
||||
}
|
||||
|
||||
// from interface KeyGenerator
|
||||
public boolean isPostFactum ()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// from interface KeyGenerator
|
||||
public void init (Connection conn)
|
||||
throws SQLException
|
||||
{
|
||||
// make sure our table exists
|
||||
JDBCUtil.createTableIfMissing(conn, _table, new String[] {
|
||||
_pkColumnName + " VARCHAR(255) PRIMARY KEY",
|
||||
_valueColumnName + " INTEGER NOT NULL"
|
||||
}, "");
|
||||
|
||||
// and also that there's a row in it for us
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(
|
||||
"SELECT * FROM " + _table + " WHERE " + _pkColumnName + " = ?");
|
||||
stmt.setString(1, _pkColumnValue);
|
||||
if (stmt.executeQuery().next()) {
|
||||
return;
|
||||
}
|
||||
|
||||
JDBCUtil.close(stmt);
|
||||
stmt = null;
|
||||
stmt = conn.prepareStatement(
|
||||
"INSERT INTO " + _table + " SET " + _pkColumnName + " = ?, " +
|
||||
_valueColumnName + " = ? ");
|
||||
stmt.setString(1, _pkColumnValue);
|
||||
stmt.setInt(2, _initialValue);
|
||||
stmt.executeUpdate();
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
// from interface KeyGenerator
|
||||
public int nextGeneratedValue (Connection conn)
|
||||
throws SQLException
|
||||
{
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
String query = "SELECT " + _valueColumnName + " FROM " + _table +
|
||||
" WHERE " + _pkColumnName + "= ? FOR UPDATE";
|
||||
stmt = conn.prepareStatement(query);
|
||||
stmt.setString(1, _pkColumnValue);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (!rs.next()) {
|
||||
throw new SQLException(
|
||||
"Failed to find next primary key value [table=" + _table +
|
||||
", column=" + _valueColumnName +
|
||||
", where=" + _pkColumnName + "=" + _pkColumnValue + "]");
|
||||
}
|
||||
int val = rs.getInt(1);
|
||||
JDBCUtil.close(stmt);
|
||||
|
||||
stmt = conn.prepareStatement(
|
||||
"UPDATE " + _table + " SET " + _valueColumnName + " = ? " +
|
||||
"WHERE " + _pkColumnName + " = ?");
|
||||
stmt.setInt(1, val + _allocationSize);
|
||||
stmt.setString(2, _pkColumnValue);
|
||||
stmt.executeUpdate();
|
||||
return val;
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/** Convenience function to return a value or a default fallback. */
|
||||
protected static String defStr (String value, String def) {
|
||||
if (value == null || value.trim().length() == 0) {
|
||||
return def;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected String _table;
|
||||
protected String _pkColumnName;
|
||||
protected String _pkColumnValue;
|
||||
protected String _valueColumnName;
|
||||
protected int _initialValue;
|
||||
protected int _allocationSize;
|
||||
}
|
||||
Reference in New Issue
Block a user