[hibernate-commits] Hibernate SVN: r20916 - core/patches/JBOSS_EAP_3_3_2_CP03_JBPAPP-5939/core/src/main/java/org/hibernate/id/enhanced.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Tue Feb 15 12:55:42 EST 2011


Author: alessandrolt
Date: 2011-02-15 12:55:42 -0500 (Tue, 15 Feb 2011)
New Revision: 20916

Modified:
   core/patches/JBOSS_EAP_3_3_2_CP03_JBPAPP-5939/core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java
Log:
JBPAPP-5939: backporting the fix of JBPAPP-5763 to on-off patch

Modified: core/patches/JBOSS_EAP_3_3_2_CP03_JBPAPP-5939/core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java
===================================================================
--- core/patches/JBOSS_EAP_3_3_2_CP03_JBPAPP-5939/core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java	2011-02-15 17:55:17 UTC (rev 20915)
+++ core/patches/JBOSS_EAP_3_3_2_CP03_JBPAPP-5939/core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java	2011-02-15 17:55:42 UTC (rev 20916)
@@ -1,547 +1,546 @@
-/*
- * Hibernate, Relational Persistence for Idiomatic Java
- *
- * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
- * indicated by the @author tags or express copyright attribution
- * statements applied by the authors.  All third-party contributions are
- * distributed under license by Red Hat Middleware LLC.
- *
- * This copyrighted material is made available to anyone wishing to use, modify,
- * copy, or redistribute it subject to the terms and conditions of the GNU
- * Lesser General Public License, as published by the Free Software Foundation.
- *
- * This program 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 distribution; if not, write to:
- * Free Software Foundation, Inc.
- * 51 Franklin Street, Fifth Floor
- * Boston, MA  02110-1301  USA
- *
- */
-package org.hibernate.id.enhanced;
-
-import java.sql.Types;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.util.Properties;
-import java.util.HashMap;
-import java.util.Collections;
-import java.util.Map;
-import java.io.Serializable;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.hibernate.engine.TransactionHelper;
-import org.hibernate.engine.SessionImplementor;
-import org.hibernate.id.PersistentIdentifierGenerator;
-import org.hibernate.id.Configurable;
-import org.hibernate.type.Type;
-import org.hibernate.dialect.Dialect;
-import org.hibernate.HibernateException;
-import org.hibernate.MappingException;
-import org.hibernate.LockMode;
-import org.hibernate.jdbc.util.FormatStyle;
-import org.hibernate.mapping.Table;
-import org.hibernate.util.PropertiesHelper;
-import org.hibernate.util.StringHelper;
-
-/**
- * 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>
- *     <td><b>NAME</b></td>
- *     <td><b>DEFAULT</b></td>
- *     <td><b>DESCRIPTION</b></td>
- *   </tr>
- *   <tr>
- *     <td>{@link #TABLE_PARAM}</td>
- *     <td>{@link #DEF_TABLE}</td>
- *     <td>The name of the table to use to store/retrieve values</td>
- *   </tr>
- *   <tr>
- *     <td>{@link #VALUE_COLUMN_PARAM}</td>
- *     <td>{@link #DEF_VALUE_COLUMN}</td>
- *     <td>The name of column which holds the sequence value for the given segment</td>
- *   </tr>
- *   <tr>
- *     <td>{@link #SEGMENT_COLUMN_PARAM}</td>
- *     <td>{@link #DEF_SEGMENT_COLUMN}</td>
- *     <td>The name of the column which holds the segment key</td>
- *   </tr>
- *   <tr>
- *     <td>{@link #SEGMENT_VALUE_PARAM}</td>
- *     <td>{@link #DEF_SEGMENT_VALUE}</td>
- *     <td>The value indicating which segment is used by this generator; refers to values in the {@link #SEGMENT_COLUMN_PARAM} column</td>
- *   </tr>
- *   <tr>
- *     <td>{@link #SEGMENT_LENGTH_PARAM}</td>
- *     <td>{@link #DEF_SEGMENT_LENGTH}</td>
- *     <td>The data length of the {@link #SEGMENT_COLUMN_PARAM} column; used for schema creation</td>
- *   </tr>
- *   <tr>
- *     <td>{@link #INITIAL_PARAM}</td>
- *     <td>{@link #DEFAULT_INITIAL_VALUE}</td>
- *     <td>The initial value to be stored for the given segment</td>
- *   </tr>
- *   <tr>
- *     <td>{@link #INCREMENT_PARAM}</td>
- *     <td>{@link #DEFAULT_INCREMENT_SIZE}</td>
- *     <td>The increment size for the underlying segment; see the discussion on {@link Optimizer} for more details.</td>
- *   </tr>
- *   <tr>
- *     <td>{@link #OPT_PARAM}</td>
- *     <td><i>depends on defined increment size</i></td>
- *     <td>Allows explicit definition of which optimization strategy to use</td>
- *   </tr>
- * </table>
- *
- * @author Steve Ebersole
- */
-public class TableGenerator extends TransactionHelper implements PersistentIdentifierGenerator, Configurable {
-	private static final Logger log = LoggerFactory.getLogger( TableGenerator.class );
-
-	public static final String CONFIG_PREFER_SEGMENT_PER_ENTITY = "prefer_entity_table_as_segment_value";
-
-	public static final String TABLE_PARAM = "table_name";
-	public static final String DEF_TABLE = "hibernate_sequences";
-
-	public static final String VALUE_COLUMN_PARAM = "value_column_name";
-	public static final String DEF_VALUE_COLUMN = "next_val";
-
-	public static final String SEGMENT_COLUMN_PARAM = "segment_column_name";
-	public static final String DEF_SEGMENT_COLUMN = "sequence_name";
-
-	public static final String SEGMENT_VALUE_PARAM = "segment_value";
-	public static final String DEF_SEGMENT_VALUE = "default";
-
-	public static final String SEGMENT_LENGTH_PARAM = "segment_value_length";
-	public static final int DEF_SEGMENT_LENGTH = 255;
-
-	public static final String INITIAL_PARAM = "initial_value";
-	public static final int DEFAULT_INITIAL_VALUE = 1;
-
-	public static final String INCREMENT_PARAM = "increment_size";
-	public static final int DEFAULT_INCREMENT_SIZE = 1;
-
-	public static final String OPT_PARAM = "optimizer";
-
-
-	private Type identifierType;
-
-	private String tableName;
-
-	private String segmentColumnName;
-	private String segmentValue;
-	private int segmentValueLength;
-
-	private String valueColumnName;
-	private int initialValue;
-	private int incrementSize;
-
-	private String selectQuery;
-	private String insertQuery;
-	private String updateQuery;
-
-	private Optimizer optimizer;
-	private long accessCount = 0;
-
-	/**
-	 * 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;
-	}
-
-	/**
-	 * 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;
-	}
-
-	/**
-	 * 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;
-	}
-
-	/**
-	 * 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;
-	}
-
-	/**
-	 * 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;
-	}
-
-	/**
-	 * 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;
-	}
-
-	/**
-	 * The optimizer being used by this generator.
-	 *
-	 * @return Out optimizer.
-	 */
-	public final Optimizer getOptimizer() {
-		return optimizer;
-	}
-
-	/**
-	 * 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 {
-		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 );
-			name = Table.qualify( catalogName, schemaName, name );
-		}
-		return name;
-	}
-
-	/**
-	 * 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 ) ) {
-			segmentValue = determineDefaultSegmentValue( params );
-		}
-		return segmentValue;
-	}
-
-	/**
-	 * 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( alias, LockMode.UPGRADE );
-		Map updateTargetColumnsMap = Collections.singletonMap( alias, new String[] { valueColumnName } );
-		return dialect.applyLocksToSql( query, lockMap, updateTargetColumnsMap );
-	}
-
-	protected String buildUpdateQuery() {
-		return "update " + tableName +
-				" set " + valueColumnName + "=? " +
-				" where " + valueColumnName + "=? and " + segmentColumnName + "=?";
-	}
-
-	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() {
-					public long getNextValue() {
-						return ( ( Number ) doWorkInNewTransaction( session ) ).longValue();
-					}
-				}
-		);
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException {
-		int result;
-		int rows;
-		do {
-			SQL_STATEMENT_LOGGER.logStatement( selectQuery, FormatStyle.BASIC );
-			PreparedStatement selectPS = conn.prepareStatement( selectQuery );
-			try {
-				selectPS.setString( 1, segmentValue );
-				ResultSet selectRS = selectPS.executeQuery();
-				if ( !selectRS.next() ) {
-					PreparedStatement insertPS = null;
-					try {
-						result = initialValue;
-						SQL_STATEMENT_LOGGER.logStatement( insertQuery, FormatStyle.BASIC );
-						insertPS = conn.prepareStatement( insertQuery );
-						insertPS.setString( 1, segmentValue );
-						insertPS.setLong( 2, result );
-						insertPS.execute();
-					}
-					finally {
-						if ( insertPS != null ) {
-							insertPS.close();
-						}
-					}
-				}
-				else {
-					result = selectRS.getInt( 1 );
-				}
-				selectRS.close();
-			}
-			catch ( SQLException sqle ) {
-				log.error( "could not read or init a hi value", sqle );
-				throw sqle;
-			}
-			finally {
-				selectPS.close();
-			}
-
-			SQL_STATEMENT_LOGGER.logStatement( updateQuery, FormatStyle.BASIC );
-			PreparedStatement updatePS = conn.prepareStatement( updateQuery );
-			try {
-				long newValue = optimizer.applyIncrementSizeToSourceValues()
-						? result + incrementSize : result + 1;
-				updatePS.setLong( 1, newValue );
-				updatePS.setLong( 2, result );
-				updatePS.setString( 3, segmentValue );
-				rows = updatePS.executeUpdate();
-			}
-			catch ( SQLException sqle ) {
-				log.error( "could not updateQuery hi value in: " + tableName, sqle );
-				throw sqle;
-			}
-			finally {
-				updatePS.close();
-			}
-		}
-		while ( rows == 0 );
-
-		accessCount++;
-
-		return new Integer( result );
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
-		return new String[] {
-				new StringBuffer()
-						.append( dialect.getCreateTableString() )
-						.append( ' ' )
-						.append( tableName )
-						.append( " ( " )
-						.append( segmentColumnName )
-						.append( ' ' )
-						.append( dialect.getTypeName( Types.VARCHAR, segmentValueLength, 0, 0 ) )
-						.append( " not null " )
-						.append( ",  " )
-						.append( valueColumnName )
-						.append( ' ' )
-						.append( dialect.getTypeName( Types.BIGINT ) )
-						.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() ) {
-			sqlDropString.append( "if exists " );
-		}
-		sqlDropString.append( tableName ).append( dialect.getCascadeConstraintsString() );
-		if ( dialect.supportsIfExistsAfterTableName() ) {
-			sqlDropString.append( " if exists" );
-		}
-		return new String[] { sqlDropString.toString() };
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public Object generatorKey() {
-		return tableName;
-	}
-}
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors.  All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program 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 distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA  02110-1301  USA
+ *
+ */
+package org.hibernate.id.enhanced;
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.hibernate.HibernateException;
+import org.hibernate.LockMode;
+import org.hibernate.MappingException;
+import org.hibernate.dialect.Dialect;
+import org.hibernate.engine.SessionImplementor;
+import org.hibernate.engine.TransactionHelper;
+import org.hibernate.id.Configurable;
+import org.hibernate.id.PersistentIdentifierGenerator;
+import org.hibernate.jdbc.util.FormatStyle;
+import org.hibernate.mapping.Table;
+import org.hibernate.type.Type;
+import org.hibernate.util.PropertiesHelper;
+import org.hibernate.util.StringHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 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>
+ *     <td><b>NAME</b></td>
+ *     <td><b>DEFAULT</b></td>
+ *     <td><b>DESCRIPTION</b></td>
+ *   </tr>
+ *   <tr>
+ *     <td>{@link #TABLE_PARAM}</td>
+ *     <td>{@link #DEF_TABLE}</td>
+ *     <td>The name of the table to use to store/retrieve values</td>
+ *   </tr>
+ *   <tr>
+ *     <td>{@link #VALUE_COLUMN_PARAM}</td>
+ *     <td>{@link #DEF_VALUE_COLUMN}</td>
+ *     <td>The name of column which holds the sequence value for the given segment</td>
+ *   </tr>
+ *   <tr>
+ *     <td>{@link #SEGMENT_COLUMN_PARAM}</td>
+ *     <td>{@link #DEF_SEGMENT_COLUMN}</td>
+ *     <td>The name of the column which holds the segment key</td>
+ *   </tr>
+ *   <tr>
+ *     <td>{@link #SEGMENT_VALUE_PARAM}</td>
+ *     <td>{@link #DEF_SEGMENT_VALUE}</td>
+ *     <td>The value indicating which segment is used by this generator; refers to values in the {@link #SEGMENT_COLUMN_PARAM} column</td>
+ *   </tr>
+ *   <tr>
+ *     <td>{@link #SEGMENT_LENGTH_PARAM}</td>
+ *     <td>{@link #DEF_SEGMENT_LENGTH}</td>
+ *     <td>The data length of the {@link #SEGMENT_COLUMN_PARAM} column; used for schema creation</td>
+ *   </tr>
+ *   <tr>
+ *     <td>{@link #INITIAL_PARAM}</td>
+ *     <td>{@link #DEFAULT_INITIAL_VALUE}</td>
+ *     <td>The initial value to be stored for the given segment</td>
+ *   </tr>
+ *   <tr>
+ *     <td>{@link #INCREMENT_PARAM}</td>
+ *     <td>{@link #DEFAULT_INCREMENT_SIZE}</td>
+ *     <td>The increment size for the underlying segment; see the discussion on {@link Optimizer} for more details.</td>
+ *   </tr>
+ *   <tr>
+ *     <td>{@link #OPT_PARAM}</td>
+ *     <td><i>depends on defined increment size</i></td>
+ *     <td>Allows explicit definition of which optimization strategy to use</td>
+ *   </tr>
+ * </table>
+ *
+ * @author Steve Ebersole
+ */
+public class TableGenerator extends TransactionHelper implements PersistentIdentifierGenerator, Configurable {
+	private static final Logger log = LoggerFactory.getLogger( TableGenerator.class );
+
+	public static final String CONFIG_PREFER_SEGMENT_PER_ENTITY = "prefer_entity_table_as_segment_value";
+
+	public static final String TABLE_PARAM = "table_name";
+	public static final String DEF_TABLE = "hibernate_sequences";
+
+	public static final String VALUE_COLUMN_PARAM = "value_column_name";
+	public static final String DEF_VALUE_COLUMN = "next_val";
+
+	public static final String SEGMENT_COLUMN_PARAM = "segment_column_name";
+	public static final String DEF_SEGMENT_COLUMN = "sequence_name";
+
+	public static final String SEGMENT_VALUE_PARAM = "segment_value";
+	public static final String DEF_SEGMENT_VALUE = "default";
+
+	public static final String SEGMENT_LENGTH_PARAM = "segment_value_length";
+	public static final int DEF_SEGMENT_LENGTH = 255;
+
+	public static final String INITIAL_PARAM = "initial_value";
+	public static final int DEFAULT_INITIAL_VALUE = 1;
+
+	public static final String INCREMENT_PARAM = "increment_size";
+	public static final int DEFAULT_INCREMENT_SIZE = 1;
+
+	public static final String OPT_PARAM = "optimizer";
+
+
+	private Type identifierType;
+
+	private String tableName;
+
+	private String segmentColumnName;
+	private String segmentValue;
+	private int segmentValueLength;
+
+	private String valueColumnName;
+	private int initialValue;
+	private int incrementSize;
+
+	private String selectQuery;
+	private String insertQuery;
+	private String updateQuery;
+
+	private Optimizer optimizer;
+	private long accessCount = 0;
+
+	/**
+	 * 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;
+	}
+
+	/**
+	 * 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;
+	}
+
+	/**
+	 * 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;
+	}
+
+	/**
+	 * 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;
+	}
+
+	/**
+	 * 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;
+	}
+
+	/**
+	 * 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;
+	}
+
+	/**
+	 * The optimizer being used by this generator.
+	 *
+	 * @return Out optimizer.
+	 */
+	public final Optimizer getOptimizer() {
+		return optimizer;
+	}
+
+	/**
+	 * 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 {
+		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, -1 );
+	}
+
+	/**
+	 * 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 );
+			name = Table.qualify( catalogName, schemaName, name );
+		}
+		return name;
+	}
+
+	/**
+	 * 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 ) ) {
+			segmentValue = determineDefaultSegmentValue( params );
+		}
+		return segmentValue;
+	}
+
+	/**
+	 * 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( alias, LockMode.UPGRADE );
+		Map updateTargetColumnsMap = Collections.singletonMap( alias, new String[] { valueColumnName } );
+		return dialect.applyLocksToSql( query, lockMap, updateTargetColumnsMap );
+	}
+
+	protected String buildUpdateQuery() {
+		return "update " + tableName +
+				" set " + valueColumnName + "=? " +
+				" where " + valueColumnName + "=? and " + segmentColumnName + "=?";
+	}
+
+	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() {
+					public long getNextValue() {
+						return ( ( Number ) doWorkInNewTransaction( session ) ).longValue();
+					}
+				}
+		);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException {
+		int result;
+		int rows;
+		do {
+			SQL_STATEMENT_LOGGER.logStatement( selectQuery, FormatStyle.BASIC );
+			PreparedStatement selectPS = conn.prepareStatement( selectQuery );
+			try {
+				selectPS.setString( 1, segmentValue );
+				ResultSet selectRS = selectPS.executeQuery();
+				if ( !selectRS.next() ) {
+					PreparedStatement insertPS = null;
+					try {
+						result = initialValue;
+						SQL_STATEMENT_LOGGER.logStatement( insertQuery, FormatStyle.BASIC );
+						insertPS = conn.prepareStatement( insertQuery );
+						insertPS.setString( 1, segmentValue );
+						insertPS.setLong( 2, result );
+						insertPS.execute();
+					}
+					finally {
+						if ( insertPS != null ) {
+							insertPS.close();
+						}
+					}
+				}
+				else {
+					result = selectRS.getInt( 1 );
+				}
+				selectRS.close();
+			}
+			catch ( SQLException sqle ) {
+				log.error( "could not read or init a hi value", sqle );
+				throw sqle;
+			}
+			finally {
+				selectPS.close();
+			}
+
+			SQL_STATEMENT_LOGGER.logStatement( updateQuery, FormatStyle.BASIC );
+			PreparedStatement updatePS = conn.prepareStatement( updateQuery );
+			try {
+				long newValue = optimizer.applyIncrementSizeToSourceValues()
+						? result + incrementSize : result + 1;
+				updatePS.setLong( 1, newValue );
+				updatePS.setLong( 2, result );
+				updatePS.setString( 3, segmentValue );
+				rows = updatePS.executeUpdate();
+			}
+			catch ( SQLException sqle ) {
+				log.error( "could not updateQuery hi value in: " + tableName, sqle );
+				throw sqle;
+			}
+			finally {
+				updatePS.close();
+			}
+		}
+		while ( rows == 0 );
+
+		accessCount++;
+
+		return new Integer( result );
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
+		return new String[] {
+				new StringBuffer()
+						.append( dialect.getCreateTableString() )
+						.append( ' ' )
+						.append( tableName )
+						.append( " ( " )
+						.append( segmentColumnName )
+						.append( ' ' )
+						.append( dialect.getTypeName( Types.VARCHAR, segmentValueLength, 0, 0 ) )
+						.append( " not null " )
+						.append( ",  " )
+						.append( valueColumnName )
+						.append( ' ' )
+						.append( dialect.getTypeName( Types.BIGINT ) )
+						.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() ) {
+			sqlDropString.append( "if exists " );
+		}
+		sqlDropString.append( tableName ).append( dialect.getCascadeConstraintsString() );
+		if ( dialect.supportsIfExistsAfterTableName() ) {
+			sqlDropString.append( " if exists" );
+		}
+		return new String[] { sqlDropString.toString() };
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public Object generatorKey() {
+		return tableName;
+	}
+}
\ No newline at end of file



More information about the hibernate-commits mailing list