Author: steve.ebersole(a)jboss.com
Date: 2008-08-29 02:22:14 -0400 (Fri, 29 Aug 2008)
New Revision: 15144
Modified:
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/TableGenerator.java
Log:
HHH-2686 : enhanced.TableGenerator - generator table PK;
HHH-3231 : enhanced.TableGenerator - improper SELECT ... FOR UPDATE OF building;
HHH-3249 : enhanced.TableGenerator - extensibility;
HHH-3454 : enhanced.TableGenerator - allow segment value default to be the entity table
name
Modified: core/branches/Branch_3_2/src/org/hibernate/id/enhanced/TableGenerator.java
===================================================================
--- core/branches/Branch_3_2/src/org/hibernate/id/enhanced/TableGenerator.java 2008-08-29
06:13:12 UTC (rev 15143)
+++ core/branches/Branch_3_2/src/org/hibernate/id/enhanced/TableGenerator.java 2008-08-29
06:22:14 UTC (rev 15144)
@@ -7,6 +7,8 @@
import java.sql.ResultSet;
import java.util.Properties;
import java.util.HashMap;
+import java.util.Map;
+import java.util.Collections;
import java.io.Serializable;
import org.apache.commons.logging.Log;
@@ -24,13 +26,29 @@
import org.hibernate.mapping.Table;
import org.hibernate.util.PropertiesHelper;
import org.hibernate.util.StringHelper;
-import org.hibernate.util.CollectionHelper;
/**
- * A "segmented" version of the enhanced table generator. The term
"segmented"
- * refers to the fact that this table can hold multiple value generators,
- * segmented by a key.
+ * An enhanced version of table-based id generation.
* <p/>
+ * Unlike the simplistic legacy one (which, btw, was only ever intended for subclassing
+ * support) we "segment" the table into multiple values. Thus a single table
can
+ * actually serve as the persistent storage for multiple independent generators. One
+ * approach would be to segment the values by the name of the entity for which we are
+ * performing generation, which would mean that we would have a row in the generator
+ * table for each entity name. Or any configuration really; the setup is very flexible.
+ * <p/>
+ * In this respect it is very simliar to the legacy
+ * {@link org.hibernate.id.MultipleHiLoPerTableGenerator} in terms of the
+ * underlying storage structure (namely a single table capable of holding
+ * multiple generator values). The differentiator is, as with
+ * {@link SequenceStyleGenerator} as well, the externalized notion
+ * of an optimizer.
+ * <p/>
+ * <b>NOTE</b> that by default we use a single row for all genertators
(based
+ * on {@link #DEF_SEGMENT_VALUE}). The configuration parameter
+ * {@link #CONFIG_PREFER_SEGMENT_PER_ENTITY} can be used to change that to
+ * instead default to using a row for each entity name.
+ * <p/>
* Configuration parameters:
* <table>
* <tr>
@@ -85,6 +103,8 @@
public class TableGenerator extends TransactionHelper implements
PersistentIdentifierGenerator, Configurable {
private static final Log log = LogFactory.getLog( TableGenerator.class );
+ public static final String CONFIG_PREFER_SEGMENT_PER_ENTITY =
"hibernate.id.enhanced.table.prefer_segment_per_entity";
+
public static final String TABLE_PARAM = "table_name";
public static final String DEF_TABLE = "hibernate_sequences";
@@ -109,101 +129,281 @@
public static final String OPT_PARAM = "optimizer";
+ private Type identifierType;
+
private String tableName;
- private String valueColumnName;
+
private String segmentColumnName;
private String segmentValue;
private int segmentValueLength;
+
+ private String valueColumnName;
private int initialValue;
private int incrementSize;
- private Type identifierType;
+ private String selectQuery;
+ private String insertQuery;
+ private String updateQuery;
- private String query;
- private String insert;
- private String update;
-
private Optimizer optimizer;
private long accessCount = 0;
- public String getTableName() {
+ /**
+ * {@inheritDoc}
+ */
+ public Object generatorKey() {
return tableName;
}
- public String getSegmentColumnName() {
+ /**
+ * Type mapping for the identifier.
+ *
+ * @return The identifier type mapping.
+ */
+ public final Type getIdentifierType() {
+ return identifierType;
+ }
+
+ /**
+ * The name of the table in which we store this generator's persistent state.
+ *
+ * @return The table name.
+ */
+ public final String getTableName() {
+ return tableName;
+ }
+
+ /**
+ * The name of the column in which we store the segment to which each row
+ * belongs. The value here acts as PK.
+ *
+ * @return The segment column name
+ */
+ public final String getSegmentColumnName() {
return segmentColumnName;
}
- public String getSegmentValue() {
+ /**
+ * The value in {@link #getSegmentColumnName segment column} which
+ * corresponding to this generator instance. In other words this value
+ * indicates the row in which this generator instance will store values.
+ *
+ * @return The segment value for this generator instance.
+ */
+ public final String getSegmentValue() {
return segmentValue;
}
- public int getSegmentValueLength() {
+ /**
+ * The size of the {@link #getSegmentColumnName segment column} in the
+ * underlying table.
+ * <p/>
+ * <b>NOTE</b> : should really have been called
'segmentColumnLength' or
+ * even better 'segmentColumnSize'
+ *
+ * @return the column size.
+ */
+ public final int getSegmentValueLength() {
return segmentValueLength;
}
- public String getValueColumnName() {
+ /**
+ * The name of the column in which we store our persistent generator value.
+ *
+ * @return The name of the value column.
+ */
+ public final String getValueColumnName() {
return valueColumnName;
}
- public Type getIdentifierType() {
- return identifierType;
- }
-
- public int getInitialValue() {
+ /**
+ * The initial value to use when we find no previous state in the
+ * generator table corresponding to our sequence.
+ *
+ * @return The initial value to use.
+ */
+ public final int getInitialValue() {
return initialValue;
}
- public int getIncrementSize() {
+ /**
+ * The amount of increment to use. The exact implications of this
+ * depends on the {@link #getOptimizer() optimizer} being used.
+ *
+ * @return The increment amount.
+ */
+ public final int getIncrementSize() {
return incrementSize;
}
- public Optimizer getOptimizer() {
+ /**
+ * The optimizer being used by this generator.
+ *
+ * @return Out optimizer.
+ */
+ public final Optimizer getOptimizer() {
return optimizer;
}
- public long getTableAccessCount() {
+ /**
+ * Getter for property 'tableAccessCount'. Only really useful for unit test
+ * assertions.
+ *
+ * @return Value for property 'tableAccessCount'.
+ */
+ public final long getTableAccessCount() {
return accessCount;
}
+ /**
+ * {@inheritDoc}
+ */
public void configure(Type type, Properties params, Dialect dialect) throws
MappingException {
- tableName = PropertiesHelper.getString( TABLE_PARAM, params, DEF_TABLE );
- if ( tableName.indexOf( '.' ) < 0 ) {
+ identifierType = type;
+
+ tableName = determneGeneratorTableName( params );
+ segmentColumnName = determineSegmentColumnName( params );
+ valueColumnName = determineValueColumnName( params );
+
+ segmentValue = determineSegmentValue( params );
+
+ segmentValueLength = determineSegmentColumnSize( params );
+ initialValue = determineInitialValue( params );
+ incrementSize = determineIncrementSize( params );
+
+ this.selectQuery = buildSelectQuery( dialect );
+ this.updateQuery = buildUpdateQuery();
+ this.insertQuery = buildInsertQuery();
+
+ String defOptStrategy = incrementSize <= 1 ? OptimizerFactory.NONE :
OptimizerFactory.POOL;
+ String optimizationStrategy = PropertiesHelper.getString( OPT_PARAM, params,
defOptStrategy );
+ optimizer = OptimizerFactory.buildOptimizer( optimizationStrategy,
identifierType.getReturnedClass(), incrementSize );
+ }
+
+ /**
+ * Determine the table name to use for the generator values.
+ * <p/>
+ * Called during {@link #configure configuration}.
+ *
+ * @see #getTableName()
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @return The table name to use.
+ */
+ protected String determneGeneratorTableName(Properties params) {
+ String name = PropertiesHelper.getString( TABLE_PARAM, params, DEF_TABLE );
+ boolean isGivenNameUnqualified = name.indexOf( '.' ) < 0;
+ if ( isGivenNameUnqualified ) {
+ // if the given name is un-qualified we may neen to qualify it
String schemaName = params.getProperty( SCHEMA );
String catalogName = params.getProperty( CATALOG );
- tableName = Table.qualify( catalogName, schemaName, tableName );
+ name = Table.qualify( catalogName, schemaName, name );
}
+ return name;
+ }
- segmentColumnName = PropertiesHelper.getString( SEGMENT_COLUMN_PARAM, params,
DEF_SEGMENT_COLUMN );
- segmentValue = params.getProperty( SEGMENT_VALUE_PARAM );
+ /**
+ * Determine the name of the column used to indicate the segment for each
+ * row. This column acts as the primary key.
+ * <p/>
+ * Called during {@link #configure configuration}.
+ *
+ * @see #getSegmentColumnName()
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @return The name of the segment column
+ */
+ protected String determineSegmentColumnName(Properties params) {
+ return PropertiesHelper.getString( SEGMENT_COLUMN_PARAM, params, DEF_SEGMENT_COLUMN );
+ }
+
+ /**
+ * Determine the name of the column in which we will store the generator persistent
value.
+ * <p/>
+ * Called during {@link #configure configuration}.
+ *
+ * @see #getValueColumnName()
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @return The name of the value column
+ */
+ protected String determineValueColumnName(Properties params) {
+ return PropertiesHelper.getString( VALUE_COLUMN_PARAM, params, DEF_VALUE_COLUMN );
+ }
+
+ /**
+ * Determine the segment value corresponding to this generator instance.
+ * <p/>
+ * Called during {@link #configure configuration}.
+ *
+ * @see #getSegmentValue()
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @return The name of the value column
+ */
+ protected String determineSegmentValue(Properties params) {
+ String segmentValue = params.getProperty( SEGMENT_VALUE_PARAM );
if ( StringHelper.isEmpty( segmentValue ) ) {
- log.debug( "explicit segment value for id generator [" + tableName +
'.' + segmentColumnName + "] suggested; using default [" +
DEF_SEGMENT_VALUE + "]" );
- segmentValue = DEF_SEGMENT_VALUE;
+ segmentValue = determineDefaultSegmentValue( params );
}
- segmentValueLength = PropertiesHelper.getInt( SEGMENT_LENGTH_PARAM, params,
DEF_SEGMENT_LENGTH );
- valueColumnName = PropertiesHelper.getString( VALUE_COLUMN_PARAM, params,
DEF_VALUE_COLUMN );
- initialValue = PropertiesHelper.getInt( INITIAL_PARAM, params, DEFAULT_INITIAL_VALUE
);
- incrementSize = PropertiesHelper.getInt( INCREMENT_PARAM, params,
DEFAULT_INCREMENT_SIZE );
- identifierType = type;
+ return segmentValue;
+ }
- String query = "select " + valueColumnName +
- " from " + tableName + " tbl" +
- " where tbl." + segmentColumnName + "=?";
+ /**
+ * Used in the cases where {@link #determineSegmentValue} is unable to
+ * determine the value to use.
+ *
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @return The default segment value to use.
+ */
+ protected String determineDefaultSegmentValue(Properties params) {
+ boolean preferSegmentPerEntity = PropertiesHelper.getBoolean(
CONFIG_PREFER_SEGMENT_PER_ENTITY, params, false );
+ String defaultToUse = preferSegmentPerEntity ? params.getProperty( TABLE ) :
DEF_SEGMENT_VALUE;
+ log.info( "explicit segment value for id generator [" + tableName +
'.' + segmentColumnName + "] suggested; using default [" + defaultToUse
+ "]" );
+ return defaultToUse;
+ }
+
+ /**
+ * Determine the size of the {@link #getSegmentColumnName segment column}
+ * <p/>
+ * Called during {@link #configure configuration}.
+ *
+ * @see #getSegmentValueLength()
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @return The size of the segment column
+ */
+ protected int determineSegmentColumnSize(Properties params) {
+ return PropertiesHelper.getInt( SEGMENT_LENGTH_PARAM, params, DEF_SEGMENT_LENGTH );
+ }
+
+ protected int determineInitialValue(Properties params) {
+ return PropertiesHelper.getInt( INITIAL_PARAM, params, DEFAULT_INITIAL_VALUE );
+ }
+
+ protected int determineIncrementSize(Properties params) {
+ return PropertiesHelper.getInt( INCREMENT_PARAM, params, DEFAULT_INCREMENT_SIZE );
+ }
+
+ protected String buildSelectQuery(Dialect dialect) {
+ final String alias = "tbl";
+ String query = "select " + StringHelper.qualify( alias, valueColumnName ) +
+ " from " + tableName + ' ' + alias +
+ " where " + StringHelper.qualify( alias, segmentColumnName ) +
"=?";
HashMap lockMap = new HashMap();
- lockMap.put( "tbl", LockMode.UPGRADE );
- this.query = dialect.applyLocksToSql( query, lockMap, CollectionHelper.EMPTY_MAP );
+ lockMap.put( alias, LockMode.UPGRADE );
+ Map updateTargetColumnsMap = Collections.singletonMap( alias, new String[] {
valueColumnName } );
+ return dialect.applyLocksToSql( query, lockMap, updateTargetColumnsMap );
+ }
- update = "update " + tableName +
+ protected String buildUpdateQuery() {
+ return "update " + tableName +
" set " + valueColumnName + "=? " +
" where " + valueColumnName + "=? and " + segmentColumnName +
"=?";
+ }
- insert = "insert into " + tableName + " (" + segmentColumnName +
", " + valueColumnName + ") " + " values (?,?)";
-
- String defOptStrategy = incrementSize <= 1 ? OptimizerFactory.NONE :
OptimizerFactory.POOL;
- String optimizationStrategy = PropertiesHelper.getString( OPT_PARAM, params,
defOptStrategy );
- optimizer = OptimizerFactory.buildOptimizer( optimizationStrategy,
identifierType.getReturnedClass(), incrementSize );
+ protected String buildInsertQuery() {
+ return "insert into " + tableName + " (" + segmentColumnName +
", " + valueColumnName + ") " + " values (?,?)";
}
+ /**
+ * {@inheritDoc}
+ */
public synchronized Serializable generate(final SessionImplementor session, Object obj)
{
return optimizer.generate(
new AccessCallback() {
@@ -214,23 +414,24 @@
);
}
+ /**
+ * {@inheritDoc}
+ */
public Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws
SQLException {
int result;
int rows;
do {
- sql = query;
- SQL.debug( sql );
- PreparedStatement queryPS = conn.prepareStatement( query );
+ SQL.debug( selectQuery );
+ PreparedStatement selectPS = conn.prepareStatement( selectQuery );
try {
- queryPS.setString( 1, segmentValue );
- ResultSet queryRS = queryPS.executeQuery();
- if ( !queryRS.next() ) {
+ selectPS.setString( 1, segmentValue );
+ ResultSet selectRS = selectPS.executeQuery();
+ if ( !selectRS.next() ) {
PreparedStatement insertPS = null;
try {
result = initialValue;
- sql = insert;
- SQL.debug( sql );
- insertPS = conn.prepareStatement( insert );
+ SQL.debug( insertQuery );
+ insertPS = conn.prepareStatement( insertQuery );
insertPS.setString( 1, segmentValue );
insertPS.setLong( 2, result );
insertPS.execute();
@@ -242,21 +443,20 @@
}
}
else {
- result = queryRS.getInt( 1 );
+ result = selectRS.getInt( 1 );
}
- queryRS.close();
+ selectRS.close();
}
catch ( SQLException sqle ) {
log.error( "could not read or init a hi value", sqle );
throw sqle;
}
finally {
- queryPS.close();
+ selectPS.close();
}
- sql = update;
- SQL.debug( sql );
- PreparedStatement updatePS = conn.prepareStatement( update );
+ SQL.debug( updateQuery );
+ PreparedStatement updatePS = conn.prepareStatement( updateQuery );
try {
long newValue = optimizer.applyIncrementSizeToSourceValues()
? result + incrementSize : result + 1;
@@ -280,6 +480,9 @@
return new Integer( result );
}
+ /**
+ * {@inheritDoc}
+ */
public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
return new String[] {
new StringBuffer()
@@ -294,11 +497,16 @@
.append( valueColumnName )
.append( ' ' )
.append( dialect.getTypeName( Types.BIGINT ) )
- .append( " ) " )
+ .append( ", primary key ( " )
+ .append( segmentColumnName )
+ .append( " ) ) " )
.toString()
};
}
+ /**
+ * {@inheritDoc}
+ */
public String[] sqlDropStrings(Dialect dialect) throws HibernateException {
StringBuffer sqlDropString = new StringBuffer().append( "drop table " );
if ( dialect.supportsIfExistsBeforeTableName() ) {
@@ -310,8 +518,4 @@
}
return new String[] { sqlDropString.toString() };
}
-
- public Object generatorKey() {
- return tableName;
- }
}