Author: steve.ebersole(a)jboss.com
Date: 2008-08-29 14:39:39 -0400 (Fri, 29 Aug 2008)
New Revision: 15149
Modified:
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/OptimizerFactory.java
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/SequenceStructure.java
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/SequenceStyleGenerator.java
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/TableStructure.java
Log:
HHH-3456 : enhanced.SequenceStyleGenerator extensibility
Modified: core/branches/Branch_3_2/src/org/hibernate/id/enhanced/OptimizerFactory.java
===================================================================
---
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/OptimizerFactory.java 2008-08-29
14:49:34 UTC (rev 15148)
+++
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/OptimizerFactory.java 2008-08-29
18:39:39 UTC (rev 15149)
@@ -52,10 +52,19 @@
return new NoopOptimizer( returnClass, incrementSize );
}
+ /**
+ * Common support for optimizer implementations.
+ */
public static abstract class OptimizerSupport implements Optimizer {
protected final Class returnClass;
protected final int incrementSize;
+ /**
+ * Construct an optimizer
+ *
+ * @param returnClass The expected id class.
+ * @param incrementSize The increment size
+ */
protected OptimizerSupport(Class returnClass, int incrementSize) {
if ( returnClass == null ) {
throw new HibernateException( "return class is required" );
@@ -64,19 +73,39 @@
this.incrementSize = incrementSize;
}
+ /**
+ * Take the primitive long value and "make" (or wrap) it into the
+ * {@link #getReturnClass id type}.
+ *
+ * @param value The primitive value to make/wrap.
+ * @return The wrapped value.
+ */
protected Serializable make(long value) {
return IdentifierGeneratorFactory.createNumber( value, returnClass );
}
+ /**
+ * Getter for property 'returnClass'. This is the Java
+ * class which is used to represent the id (e.g. {@link java.lang.Long}).
+ *
+ * @return Value for property 'returnClass'.
+ */
public Class getReturnClass() {
return returnClass;
}
+ /**
+ * {@inheritDoc}
+ */
public int getIncrementSize() {
return incrementSize;
}
}
+ /**
+ * An optimizer that performs no optimization. The database is hit for
+ * every request.
+ */
public static class NoopOptimizer extends OptimizerSupport {
private long lastSourceValue = -1;
@@ -84,6 +113,9 @@
super( returnClass, incrementSize );
}
+ /**
+ * {@inheritDoc}
+ */
public Serializable generate(AccessCallback callback) {
if ( lastSourceValue == -1 ) {
while( lastSourceValue <= 0 ) {
@@ -96,15 +128,25 @@
return make( lastSourceValue );
}
+ /**
+ * {@inheritDoc}
+ */
public long getLastSourceValue() {
return lastSourceValue;
}
+ /**
+ * {@inheritDoc}
+ */
public boolean applyIncrementSizeToSourceValues() {
return false;
}
}
+ /**
+ * Optimizer which applies a 'hilo' algorithm in memory to achieve
+ * optimization.
+ */
public static class HiLoOptimizer extends OptimizerSupport {
private long lastSourceValue = -1;
private long value;
@@ -120,6 +162,9 @@
}
}
+ /**
+ * {@inheritDoc}
+ */
public synchronized Serializable generate(AccessCallback callback) {
if ( lastSourceValue < 0 ) {
lastSourceValue = callback.getNextValue();
@@ -137,23 +182,43 @@
}
+ /**
+ * {@inheritDoc}
+ */
public long getLastSourceValue() {
return lastSourceValue;
}
+ /**
+ * {@inheritDoc}
+ */
public boolean applyIncrementSizeToSourceValues() {
return false;
}
+ /**
+ * Getter for property 'lastValue'.
+ *
+ * @return Value for property 'lastValue'.
+ */
public long getLastValue() {
return value - 1;
}
+ /**
+ * Getter for property 'hiValue'.
+ *
+ * @return Value for property 'hiValue'.
+ */
public long getHiValue() {
return hiValue;
}
}
+ /**
+ * Optimizer which uses a pool of values, storing the next low value of the
+ * range in the database.
+ */
public static class PooledOptimizer extends OptimizerSupport {
private long value;
private long hiValue = -1;
@@ -168,6 +233,9 @@
}
}
+ /**
+ * {@inheritDoc}
+ */
public synchronized Serializable generate(AccessCallback callback) {
if ( hiValue < 0 ) {
value = callback.getNextValue();
@@ -187,14 +255,25 @@
return make( value++ );
}
+ /**
+ * {@inheritDoc}
+ */
public long getLastSourceValue() {
return hiValue;
}
+ /**
+ * {@inheritDoc}
+ */
public boolean applyIncrementSizeToSourceValues() {
return true;
}
+ /**
+ * Getter for property 'lastValue'.
+ *
+ * @return Value for property 'lastValue'.
+ */
public long getLastValue() {
return value - 1;
}
Modified: core/branches/Branch_3_2/src/org/hibernate/id/enhanced/SequenceStructure.java
===================================================================
---
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/SequenceStructure.java 2008-08-29
14:49:34 UTC (rev 15148)
+++
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/SequenceStructure.java 2008-08-29
18:39:39 UTC (rev 15149)
@@ -34,18 +34,30 @@
sql = dialect.getSequenceNextValString( sequenceName );
}
+ /**
+ * {@inheritDoc}
+ */
public String getName() {
return sequenceName;
}
+ /**
+ * {@inheritDoc}
+ */
public int getIncrementSize() {
return incrementSize;
}
+ /**
+ * {@inheritDoc}
+ */
public int getTimesAccessed() {
return accessCounter;
}
+ /**
+ * {@inheritDoc}
+ */
public AccessCallback buildCallback(final SessionImplementor session) {
return new AccessCallback() {
public long getNextValue() {
@@ -88,15 +100,24 @@
};
}
+ /**
+ * {@inheritDoc}
+ */
public void prepare(Optimizer optimizer) {
applyIncrementSizeToSourceValues = optimizer.applyIncrementSizeToSourceValues();
}
+ /**
+ * {@inheritDoc}
+ */
public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
int sourceIncrementSize = applyIncrementSizeToSourceValues ? incrementSize : 1;
return dialect.getCreateSequenceStrings( sequenceName, initialValue,
sourceIncrementSize );
}
+ /**
+ * {@inheritDoc}
+ */
public String[] sqlDropStrings(Dialect dialect) throws HibernateException {
return dialect.getDropSequenceStrings( sequenceName );
}
Modified:
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/SequenceStyleGenerator.java
===================================================================
---
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/SequenceStyleGenerator.java 2008-08-29
14:49:34 UTC (rev 15148)
+++
core/branches/Branch_3_2/src/org/hibernate/id/enhanced/SequenceStyleGenerator.java 2008-08-29
18:39:39 UTC (rev 15149)
@@ -99,14 +99,29 @@
private Optimizer optimizer;
private Type identifierType;
+ /**
+ * Getter for property 'databaseStructure'.
+ *
+ * @return Value for property 'databaseStructure'.
+ */
public DatabaseStructure getDatabaseStructure() {
return databaseStructure;
}
+ /**
+ * Getter for property 'optimizer'.
+ *
+ * @return Value for property 'optimizer'.
+ */
public Optimizer getOptimizer() {
return optimizer;
}
+ /**
+ * Getter for property 'identifierType'.
+ *
+ * @return Value for property 'identifierType'.
+ */
public Type getIdentifierType() {
return identifierType;
}
@@ -114,45 +129,160 @@
// Configurable implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ /**
+ * {@inheritDoc}
+ */
public void configure(Type type, Properties params, Dialect dialect) throws
MappingException {
- identifierType = type;
+ this.identifierType = type;
boolean forceTableUse = PropertiesHelper.getBoolean( FORCE_TBL_PARAM, params, false );
+ final String sequenceName = determineSequenceName( params );
+
+ final int initialValue = determineInitialValue( params );
+ int incrementSize = determineIncrementSize( params );
+
+ final String optimizationStrategy = determineOptimizationStrategy( params,
incrementSize );
+ incrementSize = determineAdjustedIncrementSize( optimizationStrategy, incrementSize );
+
+ if ( dialect.supportsSequences() && !forceTableUse ) {
+ if ( OptimizerFactory.POOL.equals( optimizationStrategy ) &&
!dialect.supportsPooledSequences() ) {
+ forceTableUse = true;
+ log.info(
+ "Forcing table use for sequence-style generator due to pooled optimizer
selection where db does not support pooled sequences"
+ );
+ }
+ }
+
+ this.databaseStructure = buildDatabaseStructure( params, dialect, forceTableUse,
sequenceName, initialValue, incrementSize );
+
+ this.optimizer = OptimizerFactory.buildOptimizer( optimizationStrategy,
identifierType.getReturnedClass(), incrementSize );
+ this.databaseStructure.prepare( optimizer );
+ }
+
+ /**
+ * Determine the name of the sequence (or table if this resolves to a physical table)
+ * to use.
+ * <p/>
+ * Called during {@link #configure configuration}.
+ *
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @return The sequence name
+ */
+ protected String determineSequenceName(Properties params) {
String sequenceName = PropertiesHelper.getString( SEQUENCE_PARAM, params,
DEF_SEQUENCE_NAME );
if ( sequenceName.indexOf( '.' ) < 0 ) {
String schemaName = params.getProperty( SCHEMA );
String catalogName = params.getProperty( CATALOG );
sequenceName = Table.qualify( catalogName, schemaName, sequenceName );
}
- int initialValue = PropertiesHelper.getInt( INITIAL_PARAM, params,
DEFAULT_INITIAL_VALUE );
- int incrementSize = PropertiesHelper.getInt( INCREMENT_PARAM, params,
DEFAULT_INCREMENT_SIZE );
+ return sequenceName;
+ }
- String valueColumnName = PropertiesHelper.getString( VALUE_COLUMN_PARAM, params,
DEF_VALUE_COLUMN );
+ /**
+ * Determine the name of the column used to store the generator value in
+ * the db.
+ * <p/>
+ * Called during {@link #configure configuration} <b>when resolving to a
+ * physical table</b>.
+ *
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @return The value column name
+ */
+ protected String determineValueColumnName(Properties params) {
+ return PropertiesHelper.getString( VALUE_COLUMN_PARAM, params, DEF_VALUE_COLUMN );
+ }
+ /**
+ * Determine the initial sequence value to use. This value is used when
+ * initializing the {@link #getDatabaseStructure() database structure}
+ * (i.e. sequence/table).
+ * <p/>
+ * Called during {@link #configure configuration}.
+ *
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @return The initial value
+ */
+ protected int determineInitialValue(Properties params) {
+ return PropertiesHelper.getInt( INITIAL_PARAM, params, DEFAULT_INITIAL_VALUE );
+ }
+
+ /**
+ * Determine the increment size to be applied. The exact implications of
+ * this value depends on the {@link #getOptimizer() optimizer} being used.
+ * <p/>
+ * Called during {@link #configure configuration}.
+ *
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @return The increment size
+ */
+ protected int determineIncrementSize(Properties params) {
+ return PropertiesHelper.getInt( INCREMENT_PARAM, params, DEFAULT_INCREMENT_SIZE );
+ }
+
+ /**
+ * Determine the optimizer to use.
+ * <p/>
+ * Called during {@link #configure configuration}.
+ *
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @param incrementSize The {@link #determineIncrementSize determined increment size}
+ * @return The optimizer strategy (name)
+ */
+ protected String determineOptimizationStrategy(Properties params, int incrementSize) {
String defOptStrategy = incrementSize <= 1 ? OptimizerFactory.NONE :
OptimizerFactory.POOL;
- String optimizationStrategy = PropertiesHelper.getString( OPT_PARAM, params,
defOptStrategy );
+ return PropertiesHelper.getString( OPT_PARAM, params, defOptStrategy );
+ }
+
+ /**
+ * In certain cases we need to adjust the increment size based on the
+ * selected optimizer. This is the hook to achieve that.
+ *
+ * @param optimizationStrategy The optimizer strategy (name)
+ * @param incrementSize The {@link #determineIncrementSize determined increment size}
+ * @return The adjusted increment size.
+ */
+ protected int determineAdjustedIncrementSize(String optimizationStrategy, int
incrementSize) {
if ( OptimizerFactory.NONE.equals( optimizationStrategy ) && incrementSize >
1 ) {
log.warn( "config specified explicit optimizer of [" + OptimizerFactory.NONE
+ "], but [" + INCREMENT_PARAM + "=" + incrementSize + ";
honoring optimizer setting" );
incrementSize = 1;
}
- if ( dialect.supportsSequences() && !forceTableUse ) {
- if ( OptimizerFactory.POOL.equals( optimizationStrategy ) &&
!dialect.supportsPooledSequences() ) {
- // TODO : may even be better to fall back to a pooled table strategy here so that the
db stored values remain consistent...
- optimizationStrategy = OptimizerFactory.HILO;
- }
- databaseStructure = new SequenceStructure( dialect, sequenceName, initialValue,
incrementSize );
+ return incrementSize;
+ }
+
+ /**
+ * Build the database structure.
+ *
+ * @param params The params supplied in the generator config (plus some standard useful
extras).
+ * @param dialect The dialect being used.
+ * @param forceTableUse Should a table be used even if the dialect supports sequences?
+ * @param sequenceName The name to use for the sequence or table.
+ * @param initialValue The initial value.
+ * @param incrementSize the increment size to use (after any adjustments).
+ * @return The db structure representation
+ */
+ protected DatabaseStructure buildDatabaseStructure(
+ Properties params,
+ Dialect dialect,
+ boolean forceTableUse,
+ String sequenceName,
+ int initialValue,
+ int incrementSize) {
+ boolean useSequence = dialect.supportsSequences() && !forceTableUse;
+ if ( useSequence ) {
+ return new SequenceStructure( dialect, sequenceName, initialValue, incrementSize );
}
else {
- databaseStructure = new TableStructure( dialect, sequenceName, valueColumnName,
initialValue, incrementSize );
+ String valueColumnName = determineValueColumnName( params );
+ return new TableStructure( dialect, sequenceName, valueColumnName, initialValue,
incrementSize );
}
-
- optimizer = OptimizerFactory.buildOptimizer( optimizationStrategy,
identifierType.getReturnedClass(), incrementSize );
- databaseStructure.prepare( optimizer );
}
// IdentifierGenerator implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ /**
+ * {@inheritDoc}
+ */
public Serializable generate(SessionImplementor session, Object object) throws
HibernateException {
return optimizer.generate( databaseStructure.buildCallback( session ) );
}
@@ -160,14 +290,23 @@
// PersistentIdentifierGenerator implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ /**
+ * {@inheritDoc}
+ */
public Object generatorKey() {
return databaseStructure.getName();
}
+ /**
+ * {@inheritDoc}
+ */
public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
return databaseStructure.sqlCreateStrings( dialect );
}
+ /**
+ * {@inheritDoc}
+ */
public String[] sqlDropStrings(Dialect dialect) throws HibernateException {
return databaseStructure.sqlDropStrings( dialect );
}
Modified: core/branches/Branch_3_2/src/org/hibernate/id/enhanced/TableStructure.java
===================================================================
--- core/branches/Branch_3_2/src/org/hibernate/id/enhanced/TableStructure.java 2008-08-29
14:49:34 UTC (rev 15148)
+++ core/branches/Branch_3_2/src/org/hibernate/id/enhanced/TableStructure.java 2008-08-29
18:39:39 UTC (rev 15149)
@@ -30,8 +30,9 @@
private final String valueColumnName;
private final int initialValue;
private final int incrementSize;
- private final String select;
- private final String update;
+ private final String selectQuery;
+ private final String updateQuery;
+
private boolean applyIncrementSizeToSourceValues;
private int accessCounter;
@@ -41,31 +42,46 @@
this.incrementSize = incrementSize;
this.valueColumnName = valueColumnName;
- select = "select " + valueColumnName + " id_val" +
+ selectQuery = "select " + valueColumnName + " id_val" +
" from " + dialect.appendLockHint( LockMode.UPGRADE, tableName ) +
dialect.getForUpdateString();
- update = "update " + tableName +
+ updateQuery = "update " + tableName +
" set " + valueColumnName + "= ?" +
" where " + valueColumnName + "=?";
}
+ /**
+ * {@inheritDoc}
+ */
public String getName() {
return tableName;
}
+ /**
+ * {@inheritDoc}
+ */
public int getIncrementSize() {
return incrementSize;
}
+ /**
+ * {@inheritDoc}
+ */
public int getTimesAccessed() {
return accessCounter;
}
+ /**
+ * {@inheritDoc}
+ */
public void prepare(Optimizer optimizer) {
applyIncrementSizeToSourceValues = optimizer.applyIncrementSizeToSourceValues();
}
+ /**
+ * {@inheritDoc}
+ */
public AccessCallback buildCallback(final SessionImplementor session) {
return new AccessCallback() {
public long getNextValue() {
@@ -74,13 +90,19 @@
};
}
+ /**
+ * {@inheritDoc}
+ */
public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
return new String[] {
- "create table " + tableName + " ( " + valueColumnName + "
" + dialect.getTypeName( Types.BIGINT ) + " )",
+ dialect.getCreateTableString() + " " + tableName + " ( " +
valueColumnName + " " + dialect.getTypeName( Types.BIGINT ) + " )",
"insert into " + tableName + " values ( " + initialValue + "
)"
};
}
+ /**
+ * {@inheritDoc}
+ */
public String[] sqlDropStrings(Dialect dialect) throws HibernateException {
StringBuffer sqlDropString = new StringBuffer().append( "drop table " );
if ( dialect.supportsIfExistsBeforeTableName() ) {
@@ -97,42 +119,40 @@
long result;
int rows;
do {
- sql = select;
- SQL_LOG.debug( sql );
- PreparedStatement qps = conn.prepareStatement( select );
+ SQL_LOG.debug( selectQuery );
+ PreparedStatement selectPS = conn.prepareStatement( selectQuery );
try {
- ResultSet rs = qps.executeQuery();
- if ( !rs.next() ) {
+ ResultSet selectRS = selectPS.executeQuery();
+ if ( !selectRS.next() ) {
String err = "could not read a hi value - you need to populate the table:
" + tableName;
log.error( err );
throw new IdentifierGenerationException( err );
}
- result = rs.getLong( 1 );
- rs.close();
+ result = selectRS.getLong( 1 );
+ selectRS.close();
}
catch ( SQLException sqle ) {
log.error( "could not read a hi value", sqle );
throw sqle;
}
finally {
- qps.close();
+ selectPS.close();
}
- sql = update;
- SQL_LOG.debug( sql );
- PreparedStatement ups = conn.prepareStatement( update );
+ SQL_LOG.debug( updateQuery );
+ PreparedStatement updatePS = conn.prepareStatement( updateQuery );
try {
int increment = applyIncrementSizeToSourceValues ? incrementSize : 1;
- ups.setLong( 1, result + increment );
- ups.setLong( 2, result );
- rows = ups.executeUpdate();
+ updatePS.setLong( 1, result + increment );
+ updatePS.setLong( 2, result );
+ rows = updatePS.executeUpdate();
}
catch ( SQLException sqle ) {
log.error( "could not update hi value in: " + tableName, sqle );
throw sqle;
}
finally {
- ups.close();
+ updatePS.close();
}
} while ( rows == 0 );