diff --git a/core/src/main/java/org/hibernate/cfg/Environment.java b/core/src/main/java/org/hibernate/cfg/Environment.java index b7301a0732..9544cf9636 100644 --- a/core/src/main/java/org/hibernate/cfg/Environment.java +++ b/core/src/main/java/org/hibernate/cfg/Environment.java @@ -519,6 +519,12 @@ public final class Environment { public static final String JPAQL_STRICT_COMPLIANCE= "hibernate.query.jpaql_strict_compliance"; + /** + * When using pooled {@link org.hibernate.id.enhanced.Optimizer optimizers}, prefer interpreting the + * database value as the lower (lo) boundary. The default is to interpret it as the high boundary. + */ + public static final String PREFER_POOLED_VALUES_LO = "hibernate.id.optimizer.pooled.prefer_lo"; + private static final BytecodeProvider BYTECODE_PROVIDER_INSTANCE; private static final boolean ENABLE_BINARY_STREAMS; private static final boolean ENABLE_REFLECTION_OPTIMIZER; diff --git a/core/src/main/java/org/hibernate/id/enhanced/OptimizerFactory.java b/core/src/main/java/org/hibernate/id/enhanced/OptimizerFactory.java index 39e6b560a5..6e17e12fe7 100644 --- a/core/src/main/java/org/hibernate/id/enhanced/OptimizerFactory.java +++ b/core/src/main/java/org/hibernate/id/enhanced/OptimizerFactory.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2010, Red Hat Inc. 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. + * distributed under license by Red Hat Inc. * * 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 @@ -20,7 +20,6 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.id.enhanced; @@ -46,6 +45,7 @@ public class OptimizerFactory { public static final String HILO = "hilo"; public static final String LEGACY_HILO = "legacy-hilo"; public static final String POOL = "pooled"; + public static final String POOL_LO = "pooled-lo"; private static Class[] CTOR_SIG = new Class[] { Class.class, int.class }; @@ -93,6 +93,9 @@ public class OptimizerFactory { else if ( POOL.equals( type ) ) { optimizerClassName = PooledOptimizer.class.getName(); } + else if ( POOL_LO.equals( type ) ) { + optimizerClassName = PooledLoOptimizer.class.getName(); + } else { optimizerClassName = type; } @@ -387,6 +390,9 @@ public class OptimizerFactory { * Note that this optimizer works essentially the same as the * {@link HiLoOptimizer} except that here the bucket ranges are actually * encoded into the database structures. + *
+ * Note if you prefer that the database value be interpreted as the bottom end of our current range, + * then use the {@link PooledLoOptimizer} strategy */ public static class PooledOptimizer extends OptimizerSupport implements InitialValueAwareOptimizer { private IntegralDataTypeHolder hiValue; @@ -464,4 +470,39 @@ public class OptimizerFactory { this.initialValue = initialValue; } } + + public static class PooledLoOptimizer extends OptimizerSupport { + private IntegralDataTypeHolder lastSourceValue; // last value read from db source + private IntegralDataTypeHolder value; // the current generator value + + public PooledLoOptimizer(Class returnClass, int incrementSize) { + super( returnClass, incrementSize ); + if ( incrementSize < 1 ) { + throw new HibernateException( "increment size cannot be less than 1" ); + } + if ( log.isTraceEnabled() ) { + log.trace( "creating pooled optimizer (lo) with [incrementSize=" + incrementSize + "; returnClass=" + returnClass.getName() + "]" ); + } + } + + public Serializable generate(AccessCallback callback) { + if ( lastSourceValue == null || ! value.lt( lastSourceValue.copy().add( incrementSize ) ) ) { + lastSourceValue = callback.getNextValue(); + value = lastSourceValue.copy(); + // handle cases where initial-value is less that one (hsqldb for instance). + while ( value.lt( 1 ) ) { + value.increment(); + } + } + return value.makeValueThenIncrement(); + } + + public IntegralDataTypeHolder getLastSourceValue() { + return lastSourceValue; + } + + public boolean applyIncrementSizeToSourceValues() { + return true; + } + } } diff --git a/core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java b/core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java index 820774fb8a..7b1a9d7456 100644 --- a/core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java +++ b/core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2010, Red Hat Inc. 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. + * distributed under license by Red Hat Inc. * * 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 @@ -20,7 +20,6 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.id.enhanced; @@ -30,6 +29,7 @@ import java.io.Serializable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.hibernate.cfg.Environment; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.id.Configurable; import org.hibernate.HibernateException; @@ -280,8 +280,13 @@ public class SequenceStyleGenerator implements PersistentIdentifierGenerator, Co * @return The optimizer strategy (name) */ protected String determineOptimizationStrategy(Properties params, int incrementSize) { - String defOptStrategy = incrementSize <= 1 ? OptimizerFactory.NONE : OptimizerFactory.POOL; - return PropertiesHelper.getString( OPT_PARAM, params, defOptStrategy ); + // if the increment size is greater than one, we prefer pooled optimization; but we + // need to see if the user prefers POOL or POOL_LO... + String defaultPooledOptimizerStrategy = PropertiesHelper.getBoolean( Environment.PREFER_POOLED_VALUES_LO, params, false ) + ? OptimizerFactory.POOL_LO + : OptimizerFactory.POOL; + String defaultOptimizerStrategy = incrementSize <= 1 ? OptimizerFactory.NONE : defaultPooledOptimizerStrategy; + return PropertiesHelper.getString( OPT_PARAM, params, defaultOptimizerStrategy ); } /** diff --git a/core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java b/core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java index 84c9a81540..15518fa3f2 100644 --- a/core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java +++ b/core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2010, Red Hat Inc. 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. + * distributed under license by Red Hat Inc. * * 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 @@ -20,7 +20,6 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.id.enhanced; @@ -37,6 +36,7 @@ import java.io.Serializable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.hibernate.cfg.Environment; import org.hibernate.engine.TransactionHelper; import org.hibernate.engine.SessionImplementor; import org.hibernate.id.IdentifierGeneratorHelper; @@ -65,14 +65,14 @@ import org.hibernate.util.StringHelper; * 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. * - * In this respect it is very simliar to the legacy + * In this respect it is very similar 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. * - * NOTE that by default we use a single row for all genertators (based + * NOTE that by default we use a single row for all generators (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. @@ -303,8 +303,13 @@ public class TableGenerator extends TransactionHelper implements PersistentIdent this.updateQuery = buildUpdateQuery(); this.insertQuery = buildInsertQuery(); - final String defOptStrategy = incrementSize <= 1 ? OptimizerFactory.NONE : OptimizerFactory.POOL; - final String optimizationStrategy = PropertiesHelper.getString( OPT_PARAM, params, defOptStrategy ); + // if the increment size is greater than one, we prefer pooled optimization; but we + // need to see if the user prefers POOL or POOL_LO... + String defaultPooledOptimizerStrategy = PropertiesHelper.getBoolean( Environment.PREFER_POOLED_VALUES_LO, params, false ) + ? OptimizerFactory.POOL_LO + : OptimizerFactory.POOL; + final String defaultOptimizerStrategy = incrementSize <= 1 ? OptimizerFactory.NONE : defaultPooledOptimizerStrategy; + final String optimizationStrategy = PropertiesHelper.getString( OPT_PARAM, params, defaultOptimizerStrategy ); optimizer = OptimizerFactory.buildOptimizer( optimizationStrategy, identifierType.getReturnedClass(), diff --git a/core/src/main/java/org/hibernate/mapping/SimpleValue.java b/core/src/main/java/org/hibernate/mapping/SimpleValue.java index f35f22dac0..da302ecdc0 100644 --- a/core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -30,6 +30,7 @@ import java.util.Properties; import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.cfg.Environment; import org.hibernate.cfg.Mappings; import org.hibernate.dialect.Dialect; import org.hibernate.engine.Mapping; @@ -182,6 +183,12 @@ public class SimpleValue implements KeyValue { params.putAll(identifierGeneratorProperties); } + // TODO : we should pass along all settings once "config lifecycle" is hashed out... + params.put( + Environment.PREFER_POOLED_VALUES_LO, + mappings.getConfigurationProperties().getProperty( Environment.PREFER_POOLED_VALUES_LO, "false" ) + ); + identifierGeneratorFactory.setDialect( dialect ); return identifierGeneratorFactory.createIdentifierGenerator( identifierGeneratorStrategy, getType(), params ); diff --git a/core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java b/core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java index dfd76eec73..77a7ddc7e9 100644 --- a/core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java +++ b/core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java @@ -123,33 +123,134 @@ public class OptimizerUnitTest extends TestCase { public void testSubsequentPooledOptimizerUsage() { // test the pooled optimizer in situation where the sequence is already beyond its initial value on init. // cheat by telling the sequence to start with 1000 - final SourceMock sequence = new SourceMock( 1000, 3, 5 ); + final SourceMock sequence = new SourceMock( 1001, 3, 5 ); // but tell the optimizer the start-with is 1 final Optimizer optimizer = OptimizerFactory.buildOptimizer( OptimizerFactory.POOL, Long.class, 3, 1 ); assertEquals( 5, sequence.getTimesCalled() ); - assertEquals( 1000, sequence.getCurrentValue() ); + assertEquals( 1001, sequence.getCurrentValue() ); Long next = (Long) optimizer.generate( sequence ); - assertEquals( 1000, next.intValue() ); - assertEquals( (5+1), sequence.getTimesCalled() ); - assertEquals( (1000+3), sequence.getCurrentValue() ); - - next = (Long) optimizer.generate( sequence ); assertEquals( 1001, next.intValue() ); assertEquals( (5+1), sequence.getTimesCalled() ); - assertEquals( (1000+3), sequence.getCurrentValue() ); + assertEquals( (1001+3), sequence.getCurrentValue() ); next = (Long) optimizer.generate( sequence ); - assertEquals( 1002, next.intValue() ); + assertEquals( (1001+1), next.intValue() ); assertEquals( (5+1), sequence.getTimesCalled() ); - assertEquals( (1000+3), sequence.getCurrentValue() ); + assertEquals( (1001+3), sequence.getCurrentValue() ); + + next = (Long) optimizer.generate( sequence ); + assertEquals( (1001+2), next.intValue() ); + assertEquals( (5+1), sequence.getTimesCalled() ); + assertEquals( (1001+3), sequence.getCurrentValue() ); // force a "clock over" next = (Long) optimizer.generate( sequence ); - assertEquals( 1003, next.intValue() ); + assertEquals( (1001+3), next.intValue() ); assertEquals( (5+2), sequence.getTimesCalled() ); - assertEquals( (1000+6), sequence.getCurrentValue() ); + assertEquals( (1001+6), sequence.getCurrentValue() ); + } + + public void testBasicPooledLoOptimizerUsage() { + final SourceMock sequence = new SourceMock( 1, 3 ); + final Optimizer optimizer = OptimizerFactory.buildOptimizer( OptimizerFactory.POOL_LO, Long.class, 3 ); + + assertEquals( 0, sequence.getTimesCalled() ); + assertEquals( -1, sequence.getCurrentValue() ); + + Long next = ( Long ) optimizer.generate( sequence ); + assertEquals( 1, next.intValue() ); + assertEquals( 1, sequence.getTimesCalled() ); + assertEquals( 1, sequence.getCurrentValue() ); + + next = ( Long ) optimizer.generate( sequence ); + assertEquals( 2, next.intValue() ); + assertEquals( 1, sequence.getTimesCalled() ); + assertEquals( 1, sequence.getCurrentValue() ); + + next = ( Long ) optimizer.generate( sequence ); + assertEquals( 3, next.intValue() ); + assertEquals( 1, sequence.getTimesCalled() ); + assertEquals( 1, sequence.getCurrentValue() ); + +// // force a "clock over" + next = ( Long ) optimizer.generate( sequence ); + assertEquals( 4, next.intValue() ); + assertEquals( 2, sequence.getTimesCalled() ); + assertEquals( (1+3), sequence.getCurrentValue() ); + } + + public void testSubsequentPooledLoOptimizerUsage() { + // test the pooled optimizer in situation where the sequence is already beyond its initial value on init. + // cheat by telling the sequence to start with 1000 + final SourceMock sequence = new SourceMock( 1001, 3, 5 ); + // but tell the optimizer the start-with is 1 + final Optimizer optimizer = OptimizerFactory.buildOptimizer( OptimizerFactory.POOL, Long.class, 3, 1 ); + + assertEquals( 5, sequence.getTimesCalled() ); + assertEquals( 1001, sequence.getCurrentValue() ); + + Long next = ( Long ) optimizer.generate( sequence ); + assertEquals( (1001), next.intValue() ); + assertEquals( (5+1), sequence.getTimesCalled() ); + assertEquals( (1001+3), sequence.getCurrentValue() ); + + next = ( Long ) optimizer.generate( sequence ); + assertEquals( (1001+1), next.intValue() ); + assertEquals( (5+1), sequence.getTimesCalled() ); + assertEquals( (1001+3), sequence.getCurrentValue() ); + + next = ( Long ) optimizer.generate( sequence ); + assertEquals( (1001+2), next.intValue() ); + assertEquals( (5+1), sequence.getTimesCalled() ); + assertEquals( (1001+3), sequence.getCurrentValue() ); + +// // force a "clock over" + next = ( Long ) optimizer.generate( sequence ); + assertEquals( (1001+3), next.intValue() ); + assertEquals( (5+2), sequence.getTimesCalled() ); + assertEquals( (1001+6), sequence.getCurrentValue() ); + } + + public void testRecoveredPooledOptimizerUsage() { + final SourceMock sequence = new SourceMock( 1, 3 ); + final Optimizer optimizer = OptimizerFactory.buildOptimizer( OptimizerFactory.POOL, Long.class, 3, 1 ); + + assertEquals( 0, sequence.getTimesCalled() ); + assertEquals( -1, sequence.getCurrentValue() ); + + Long next = ( Long ) optimizer.generate( sequence ); + assertEquals( 1, next.intValue() ); + assertEquals( 2, sequence.getTimesCalled() ); + assertEquals( 4, sequence.getCurrentValue() ); + + // app ends, and starts back up (we should "lose" only 2 and 3 as id values) + final Optimizer optimizer2 = OptimizerFactory.buildOptimizer( OptimizerFactory.POOL, Long.class, 3, 1 ); + next = ( Long ) optimizer2.generate( sequence ); + assertEquals( 4, next.intValue() ); + assertEquals( 3, sequence.getTimesCalled() ); + assertEquals( 7, sequence.getCurrentValue() ); + } + + public void testRecoveredPooledLoOptimizerUsage() { + final SourceMock sequence = new SourceMock( 1, 3 ); + final Optimizer optimizer = OptimizerFactory.buildOptimizer( OptimizerFactory.POOL_LO, Long.class, 3, 1 ); + + assertEquals( 0, sequence.getTimesCalled() ); + assertEquals( -1, sequence.getCurrentValue() ); + + Long next = ( Long ) optimizer.generate( sequence ); + assertEquals( 1, next.intValue() ); + assertEquals( 1, sequence.getTimesCalled() ); + assertEquals( 1, sequence.getCurrentValue() ); + + // app ends, and starts back up (we should "lose" only 2 and 3 as id values) + final Optimizer optimizer2 = OptimizerFactory.buildOptimizer( OptimizerFactory.POOL_LO, Long.class, 3, 1 ); + next = ( Long ) optimizer2.generate( sequence ); + assertEquals( 4, next.intValue() ); + assertEquals( 2, sequence.getTimesCalled() ); + assertEquals( 4, sequence.getCurrentValue() ); } private static class SourceMock implements AccessCallback { @@ -174,6 +275,7 @@ public class OptimizerUnitTest extends TestCase { this.initialValue = 1; } else { + this.value.initialize( -1 ); this.initialValue = initialValue; } } diff --git a/core/src/test/java/org/hibernate/id/enhanced/SequenceStyleConfigUnitTest.java b/core/src/test/java/org/hibernate/id/enhanced/SequenceStyleConfigUnitTest.java index 06bfc8b6bd..9cb63c5a16 100644 --- a/core/src/test/java/org/hibernate/id/enhanced/SequenceStyleConfigUnitTest.java +++ b/core/src/test/java/org/hibernate/id/enhanced/SequenceStyleConfigUnitTest.java @@ -1,3 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2010, Red Hat Inc. 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 Inc. + * + * 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.util.Properties; @@ -6,6 +29,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.Hibernate; @@ -178,7 +202,23 @@ public class SequenceStyleConfigUnitTest extends TestCase { assertClassAssignability( OptimizerFactory.PooledOptimizer.class, generator.getOptimizer().getClass() ); assertEquals( 20, generator.getOptimizer().getIncrementSize() ); assertEquals( 20, generator.getDatabaseStructure().getIncrementSize() ); + } + public void testPreferPooledLoSettingHonored() { + final Dialect dialect = new PooledSequenceDialect(); + + Properties props = buildGeneratorPropertiesBase(); + props.setProperty( SequenceStyleGenerator.INCREMENT_PARAM, "20" ); + SequenceStyleGenerator generator = new SequenceStyleGenerator(); + generator.configure( Hibernate.LONG, props, dialect ); + assertClassAssignability( SequenceStructure.class, generator.getDatabaseStructure().getClass() ); + assertClassAssignability( OptimizerFactory.PooledOptimizer.class, generator.getOptimizer().getClass() ); + + props.setProperty( Environment.PREFER_POOLED_VALUES_LO, "true" ); + generator = new SequenceStyleGenerator(); + generator.configure( Hibernate.LONG, props, dialect ); + assertClassAssignability( SequenceStructure.class, generator.getDatabaseStructure().getClass() ); + assertClassAssignability( OptimizerFactory.PooledLoOptimizer.class, generator.getOptimizer().getClass() ); } private static class TableDialect extends Dialect {