[hibernate-commits] Hibernate SVN: r15144 - core/branches/Branch_3_2/src/org/hibernate/id/enhanced.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Fri Aug 29 02:22:14 EDT 2008


Author: steve.ebersole at 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;
-	}
 }




More information about the hibernate-commits mailing list