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 git-svn-id: https://svn.jboss.org/repos/hibernate/core/branches/Branch_3_2@15144 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
b32e5f9f0a
commit
202de56484
|
@ -7,6 +7,8 @@ import java.sql.PreparedStatement;
|
|||
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,12 +26,28 @@ import org.hibernate.LockMode;
|
|||
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>
|
||||
|
@ -85,6 +103,8 @@ import org.hibernate.util.CollectionHelper;
|
|||
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 class TableGenerator extends TransactionHelper implements PersistentIdent
|
|||
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 query;
|
||||
private String insert;
|
||||
private String update;
|
||||
private String selectQuery;
|
||||
private String insertQuery;
|
||||
private String updateQuery;
|
||||
|
||||
private Optimizer optimizer;
|
||||
private long accessCount = 0;
|
||||
|
||||
public String getTableName() {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Object generatorKey() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
public String getSegmentColumnName() {
|
||||
return segmentColumnName;
|
||||
}
|
||||
|
||||
public String getSegmentValue() {
|
||||
return segmentValue;
|
||||
}
|
||||
|
||||
public int getSegmentValueLength() {
|
||||
return segmentValueLength;
|
||||
}
|
||||
|
||||
public String getValueColumnName() {
|
||||
return valueColumnName;
|
||||
}
|
||||
|
||||
public Type getIdentifierType() {
|
||||
/**
|
||||
* Type mapping for the identifier.
|
||||
*
|
||||
* @return The identifier type mapping.
|
||||
*/
|
||||
public final Type getIdentifierType() {
|
||||
return identifierType;
|
||||
}
|
||||
|
||||
public int getInitialValue() {
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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 ) {
|
||||
String schemaName = params.getProperty( SCHEMA );
|
||||
String catalogName = params.getProperty( CATALOG );
|
||||
tableName = Table.qualify( catalogName, schemaName, tableName );
|
||||
}
|
||||
|
||||
segmentColumnName = PropertiesHelper.getString( SEGMENT_COLUMN_PARAM, params, DEF_SEGMENT_COLUMN );
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
String query = "select " + valueColumnName +
|
||||
" from " + tableName + " tbl" +
|
||||
" where tbl." + segmentColumnName + "=?";
|
||||
HashMap lockMap = new HashMap();
|
||||
lockMap.put( "tbl", LockMode.UPGRADE );
|
||||
this.query = dialect.applyLocksToSql( query, lockMap, CollectionHelper.EMPTY_MAP );
|
||||
tableName = determneGeneratorTableName( params );
|
||||
segmentColumnName = determineSegmentColumnName( params );
|
||||
valueColumnName = determineValueColumnName( params );
|
||||
|
||||
update = "update " + tableName +
|
||||
" set " + valueColumnName + "=? " +
|
||||
" where " + valueColumnName + "=? and " + segmentColumnName + "=?";
|
||||
segmentValue = determineSegmentValue( params );
|
||||
|
||||
insert = "insert into " + tableName + " (" + segmentColumnName + ", " + valueColumnName + ") " + " values (?,?)";
|
||||
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() {
|
||||
|
@ -214,23 +414,24 @@ public class TableGenerator extends TransactionHelper implements PersistentIdent
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 @@ public class TableGenerator extends TransactionHelper implements PersistentIdent
|
|||
}
|
||||
}
|
||||
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 @@ public class TableGenerator extends TransactionHelper implements PersistentIdent
|
|||
return new Integer( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
|
||||
return new String[] {
|
||||
new StringBuffer()
|
||||
|
@ -294,11 +497,16 @@ public class TableGenerator extends TransactionHelper implements PersistentIdent
|
|||
.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 @@ public class TableGenerator extends TransactionHelper implements PersistentIdent
|
|||
}
|
||||
return new String[] { sqlDropString.toString() };
|
||||
}
|
||||
|
||||
public Object generatorKey() {
|
||||
return tableName;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue