diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java index 6f135e9d9f57..d0e51cb34de8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java @@ -187,7 +187,7 @@ public static void handleSequenceGenerator( else if ( nameFromGeneratedValue != null ) { properties.put( GENERATOR_NAME, nameFromGeneratedValue ); } - // we need to better handle default allocation-size here... + // we need to better handle the default allocation size here properties.put( INCREMENT_PARAM, fallbackAllocationSize( generatorAnnotation, buildingContext ) ); }, generatorAnnotation == null @@ -221,7 +221,7 @@ public static void handleTableGenerator( else if ( nameFromGeneratedValue != null ) { properties.put( GENERATOR_NAME, nameFromGeneratedValue ); } - // we need to better handle default allocation-size here... + // we need to better handle the default allocation size here properties.put( INCREMENT_PARAM, fallbackAllocationSize( generatorAnnotation, buildingContext ) @@ -247,7 +247,7 @@ public static void handleIdGeneratorType( final var markerAnnotation = generatorAnnotation.annotationType().getAnnotation( IdGeneratorType.class ); idValue.setCustomIdGeneratorCreator( creationContext -> { - final Generator identifierGenerator = + final var identifierGenerator = instantiateGenerator( beanContainer( buildingContext ), markerAnnotation.value() ); prepareForUse( identifierGenerator, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java index 840fefd2b2d8..4af123243fff 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java @@ -612,7 +612,7 @@ public static void callConfigure( Map configuration, Value value) { if ( generator instanceof Configurable configurable ) { - final Properties parameters = collectParameters( + final var parameters = collectParameters( value, creationContext.getDatabase().getDialect(), creationContext.getRootClass(), @@ -762,9 +762,9 @@ private static void instantiateNamedStrategyGenerator( String generatorStrategy, Map configuration, MetadataBuildingContext context) { - final BeanContainer beanContainer = beanContainer( context ); + final var beanContainer = beanContainer( context ); identifierValue.setCustomIdGeneratorCreator( creationContext -> { - final Generator identifierGenerator = + final var identifierGenerator = instantiateGenerator( beanContainer, generatorClass( generatorStrategy, identifierValue ) ); // in this code path, there's no generator annotation, // and therefore no need to call initialize() diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/InitCommand.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/InitCommand.java index 4acedf97d932..664e9dc8f1d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/InitCommand.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/InitCommand.java @@ -4,6 +4,8 @@ */ package org.hibernate.boot.model.relational; +import java.util.Arrays; + /** * A general SQL command to be used while initializing a schema. * @@ -14,4 +16,15 @@ public record InitCommand(String... initCommands) { public String[] getInitCommands() { return initCommands; } + + @Override + public boolean equals(Object object) { + return object instanceof InitCommand that + && Arrays.equals( this.initCommands, that.initCommands ); + } + + @Override + public int hashCode() { + return Arrays.hashCode( initCommands ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/MappingSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/MappingSettings.java index 561932b649aa..429e310dd036 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/MappingSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/MappingSettings.java @@ -118,7 +118,7 @@ public interface MappingSettings { String KEYWORD_AUTO_QUOTING_ENABLED = "hibernate.auto_quote_keyword"; /** - * When a generator specifies an increment-size and an optimizer was not explicitly + * When a generator specifies an increment size and an optimizer was not explicitly * specified, which of the "pooled" optimizers should be preferred? Can specify an * optimizer short name or the name of a class which implements * {@link org.hibernate.id.enhanced.Optimizer}. diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sequence/OracleSequenceSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/sequence/OracleSequenceSupport.java index 0b0e0153bc7e..d6ae54846a7d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sequence/OracleSequenceSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sequence/OracleSequenceSupport.java @@ -51,4 +51,9 @@ public boolean sometimesNeedsStartingValue() { public String getDropSequenceString(String sequenceName) throws MappingException { return "drop sequence " + (supportsIfExists ? "if exists " : "") + sequenceName; } + + @Override + public String getRestartSequenceString(String sequenceName, long startWith) { + return "alter sequence " + sequenceName + " restart start with " + startWith; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sequence/SequenceSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/sequence/SequenceSupport.java index f77f18d96549..a6ccbe2646e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sequence/SequenceSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sequence/SequenceSupport.java @@ -145,7 +145,7 @@ default String[] getCreateSequenceStrings(String sequenceName, int initialValue, } /** - * Typically dialects which support sequences can create a sequence with + * Typically, dialects which support sequences can create a sequence with * a single command. This method is a convenience making it easier to * implement {@link #getCreateSequenceStrings(String,int,int)} for these * dialects. @@ -169,7 +169,7 @@ default String getCreateSequenceString(String sequenceName) throws MappingExcept } /** - * Typically dialects which support sequences can create a sequence with + * Typically, dialects which support sequences can create a sequence with * a single command. This method is a convenience making it easier to * implement {@link #getCreateSequenceStrings(String,int,int)} for these * dialects. @@ -212,9 +212,9 @@ default String[] getDropSequenceStrings(String sequenceName) throws MappingExcep } /** - * Typically dialects which support sequences can drop a sequence - * with a single command. This is convenience form of - * {@link #getDropSequenceStrings} to help facilitate that. + * Typically, dialects which support sequences can drop a sequence + * with a single command. This is a convenience form of + * {@link #getDropSequenceStrings} which facilitates that. *

* Dialects which support sequences and can drop a sequence in a * single command need *only* override this method. Dialects @@ -229,6 +229,17 @@ default String getDropSequenceString(String sequenceName) throws MappingExceptio return "drop sequence " + sequenceName; } + /** + * A DDL statement to restart a sequence with a given value. + * + * @param sequenceName The name of the sequence + * @param startWith The value to restart at + * @return The {@code alter sequence ... restart} command + */ + default String getRestartSequenceString(String sequenceName, long startWith) { + return "alter sequence " + sequenceName + " restart with " + startWith; + } + /** * Do we need to explicitly specify {@code minvalue} or * {@code maxvalue} when the initial value doesn't have diff --git a/hibernate-core/src/main/java/org/hibernate/generator/GeneratorCreationContext.java b/hibernate-core/src/main/java/org/hibernate/generator/GeneratorCreationContext.java index 454e32e8fa92..b8b575f5b579 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/GeneratorCreationContext.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/GeneratorCreationContext.java @@ -12,6 +12,7 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.Value; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.Type; @@ -64,6 +65,11 @@ public interface GeneratorCreationContext { */ Property getProperty(); + /** + * The identifier. + */ + Value getValue(); + /** * Mapping details for the identifier type. */ diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java index 8899d51a0af7..d31ecf750da0 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java @@ -9,10 +9,11 @@ import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.mapping.Table; /** - * Encapsulates definition of the underlying data structure backing a - * sequence-style generator. + * Encapsulates definition of the underlying data structure backing + * a {@linkplain SequenceStyleGenerator sequence-style} generator. * * @author Steve Ebersole */ @@ -88,6 +89,21 @@ default void configure(Optimizer optimizer) { @Override void registerExportables(Database database); + /** + * Register additional database objects which need to be aware of the + * table for which this structure is used to generate values. Used to + * deal with automatic sequence resynchronization after data import. + * + * @param table The table for which this structure is used to generate values + * @param optimizer The {@link Optimizer} for this generator + * + * @see org.hibernate.relational.SchemaManager#resynchronizeGenerators() + * + * @since 7.2 + */ + default void registerExtraExportables(Table table, Optimizer optimizer) { + } + /** * Initializes this structure, in particular pre-generates SQL as necessary. *

diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/ExportableColumnHelper.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/ExportableColumnHelper.java index c0dba2aa18b8..aa389af5c179 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/ExportableColumnHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/ExportableColumnHelper.java @@ -24,7 +24,7 @@ class ExportableColumnHelper { static Column column(Database database, Table table, String segmentColumnName, BasicType type, String typeName) { - final Column column = new Column( segmentColumnName ); + final var column = new Column( segmentColumnName ); column.setSqlType( typeName ); column.setValue( new Value() { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/HiLoOptimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/HiLoOptimizer.java index 91675a39a0c8..f7d6591da04c 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/HiLoOptimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/HiLoOptimizer.java @@ -119,28 +119,28 @@ else if ( ! generationState.upperLimit.gt( generationState.value ) ) { private GenerationState noTenantState; private Map tenantSpecificState; - private GenerationState locateGenerationState(String tenantIdentifier) { - if ( tenantIdentifier == null ) { + private GenerationState locateGenerationState(String tenantId) { + if ( tenantId == null ) { if ( noTenantState == null ) { noTenantState = new GenerationState(); } return noTenantState; } else { - GenerationState state; if ( tenantSpecificState == null ) { tenantSpecificState = new ConcurrentHashMap<>(); - state = new GenerationState(); - tenantSpecificState.put( tenantIdentifier, state ); + final var state = new GenerationState(); + tenantSpecificState.put( tenantId, state ); + return state; } else { - state = tenantSpecificState.get( tenantIdentifier ); + var state = tenantSpecificState.get( tenantId ); if ( state == null ) { state = new GenerationState(); - tenantSpecificState.put( tenantIdentifier, state ); + tenantSpecificState.put( tenantId, state ); } + return state; } - return state; } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyHiLoAlgorithmOptimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyHiLoAlgorithmOptimizer.java index 432a300d2a78..124b8d4853ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyHiLoAlgorithmOptimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyHiLoAlgorithmOptimizer.java @@ -7,7 +7,6 @@ import org.hibernate.HibernateException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.id.IntegralDataTypeHolder; -import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.query.sqm.BinaryArithmeticOperator; import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.Expression; @@ -59,8 +58,7 @@ public LegacyHiLoAlgorithmOptimizer(Class returnClass, int incrementSize) { public Serializable generate(AccessCallback callback) { lock.lock(); try { - final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() ); - + final var generationState = locateGenerationState( callback.getTenantIdentifier() ); if ( generationState.lo > generationState.maxLo ) { generationState.lastSourceValue = callback.getNextValue(); generationState.lo = generationState.lastSourceValue.eq( 0 ) ? 1 : 0; @@ -90,25 +88,25 @@ private GenerationState locateGenerationState(String tenantIdentifier) { return noTenantState; } else { - GenerationState state; if ( tenantSpecificState == null ) { tenantSpecificState = new ConcurrentHashMap<>(); - state = createGenerationState(); + final var state = createGenerationState(); tenantSpecificState.put( tenantIdentifier, state ); + return state; } else { - state = tenantSpecificState.get( tenantIdentifier ); + var state = tenantSpecificState.get( tenantIdentifier ); if ( state == null ) { state = createGenerationState(); tenantSpecificState.put( tenantIdentifier, state ); } + return state; } - return state; } } private GenerationState createGenerationState() { - final GenerationState state = new GenerationState(); + final var state = new GenerationState(); state.maxLo = initialMaxLo; state.lo = initialMaxLo + 1; return state; @@ -156,7 +154,7 @@ public IntegralDataTypeHolder getLastValue() { @Override public Expression createLowValueExpression(Expression databaseValue, SessionFactoryImplementor sessionFactory) { - BasicValuedMapping integerType = sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Integer.class ); + final var integerType = sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Integer.class ); return new BinaryArithmeticExpression( databaseValue, BinaryArithmeticOperator.MULTIPLY, diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyNamingStrategy.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyNamingStrategy.java index bd1e6835558c..d4c22fb40caf 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyNamingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyNamingStrategy.java @@ -12,6 +12,7 @@ import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.model.relational.QualifiedSequenceName; +import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.service.ServiceRegistry; @@ -64,18 +65,16 @@ public QualifiedName determineSequenceName( Map configValues, ServiceRegistry serviceRegistry) { final String sequenceName = implicitSequenceName( configValues ); + return sequenceName.contains( "." ) + ? QualifiedNameParser.INSTANCE.parse( sequenceName ) + : new QualifiedSequenceName( + catalogName, + schemaName, + serviceRegistry.requireService( JdbcEnvironment.class ) + .getIdentifierHelper() + .toIdentifier( sequenceName ) + ); - if ( sequenceName.contains( "." ) ) { - return QualifiedNameParser.INSTANCE.parse( sequenceName ); - } - - return new QualifiedSequenceName( - catalogName, - schemaName, - serviceRegistry.requireService( JdbcEnvironment.class ) - .getIdentifierHelper() - .toIdentifier( sequenceName ) - ); } private String implicitSequenceName(Map configValues) { @@ -104,17 +103,15 @@ public QualifiedName determineTableName( ServiceRegistry serviceRegistry) { final String implicitName = implicitTableName( configValues ); - if ( implicitName.contains( "." ) ) { - return QualifiedNameParser.INSTANCE.parse( implicitName ); - } - - return new QualifiedNameParser.NameParts( - catalogName, - schemaName, - serviceRegistry.requireService( JdbcEnvironment.class ) - .getIdentifierHelper() - .toIdentifier( implicitName ) - ); + return implicitName.contains( "." ) + ? QualifiedNameParser.INSTANCE.parse( implicitName ) + : new QualifiedTableName( + catalogName, + schemaName, + serviceRegistry.requireService( JdbcEnvironment.class ) + .getIdentifierHelper() + .toIdentifier( implicitName ) + ); } private String implicitTableName(Map configValues) { diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/NoopOptimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/NoopOptimizer.java index c05b0850d1a7..46dbb30751ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/NoopOptimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/NoopOptimizer.java @@ -37,7 +37,7 @@ public Serializable generate(AccessCallback callback) { // reliable as it might be mutated by multiple threads. // The lastSourceValue field is only accessed by tests, // so this is not a concern. - IntegralDataTypeHolder value = callback.getNextValue(); + final var value = callback.getNextValue(); lastSourceValue = value; return value.makeValue(); } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/Optimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/Optimizer.java index 56cd3cd31d26..b5f18d25329e 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/Optimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/Optimizer.java @@ -58,7 +58,7 @@ public interface Optimizer { * * @return True if the values in the source are to be incremented * according to the defined increment size; false otherwise, in which - * case the increment is totally an in memory construct. + * case the increment size is a completely in-memory construct. */ boolean applyIncrementSizeToSourceValues(); @@ -75,4 +75,11 @@ public interface Optimizer { * @since 7.1 */ Expression createLowValueExpression(Expression databaseValue, SessionFactoryImplementor sessionFactory); + + /** + * @since 7.2 + */ + default int getAdjustment() { + return 1; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledLoOptimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledLoOptimizer.java index 5e92bc98e571..a773f45318d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledLoOptimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledLoOptimizer.java @@ -59,7 +59,7 @@ public Serializable generate(AccessCallback callback) { generationState.lastSourceValue = callback.getNextValue(); generationState.upperLimitValue = generationState.lastSourceValue.copy().add( incrementSize ); generationState.value = generationState.lastSourceValue.copy(); - // handle cases where initial-value is less that one (hsqldb for instance). + // handle cases where the initial value is less than one (hsqldb, for instance) while ( generationState.value.lt( 1 ) ) { generationState.value.increment(); } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java index cef1de1820f3..8e631e4fc7b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java @@ -61,8 +61,7 @@ public PooledOptimizer(Class returnClass, int incrementSize) { public Serializable generate(AccessCallback callback) { lock.lock(); try { - final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() ); - + final var generationState = locateGenerationState( callback.getTenantIdentifier() ); if ( generationState.hiValue == null ) { generationState.hiValue = callback.getNextValue(); // unfortunately not really safe to normalize this @@ -86,7 +85,6 @@ else if ( generationState.value.gt( generationState.hiValue ) ) { generationState.hiValue = callback.getNextValue(); generationState.value = generationState.hiValue.copy().subtract( incrementSize - 1 ); } - return generationState.value.makeValueThenIncrement(); } finally { @@ -175,4 +173,9 @@ public Expression createLowValueExpression(Expression databaseValue, SessionFact integerType ); } + + @Override + public int getAdjustment() { + return incrementSize; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/ResyncHelper.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/ResyncHelper.java new file mode 100644 index 000000000000..8af041683390 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/ResyncHelper.java @@ -0,0 +1,58 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.id.enhanced; + +import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; + +import java.sql.SQLException; + +/** + * @author Gavin King + */ +class ResyncHelper { + + private static long execute(DdlTransactionIsolator isolator, String sequenceCurrentValue, String message) { + isolator.getJdbcContext().getSqlStatementLogger().logStatement( sequenceCurrentValue ); + try ( var select = isolator.getIsolatedConnection().prepareStatement( sequenceCurrentValue ) ) { + try ( var resultSet = select.executeQuery() ) { + resultSet.next(); + return resultSet.getLong( 1 ); + } + } + catch (SQLException e) { + throw isolator.getJdbcContext().getSqlExceptionHelper() + .convert( e, message, sequenceCurrentValue ); + } + } + + static long getNextSequenceValue(DdlTransactionIsolator isolator, String sequenceName) { + return execute( isolator, + isolator.getJdbcContext().getDialect().getSequenceSupport() + .getSequenceNextValString( sequenceName ), + "Could not fetch the current sequence value from the database" ); + } + + static long getMaxPrimaryKey(DdlTransactionIsolator isolator, String primaryKeyColumnName, String tableName) { + return execute( isolator, + "select max(" + primaryKeyColumnName + ") from " + tableName, + "Could not fetch the max primary key from the database" ); + } + + static long getCurrentTableValue(DdlTransactionIsolator isolator, String tableName, String columnName) { + return execute( isolator, + "select " + columnName + " from " + tableName, + "Could not fetch the current table value from the database" ); + } + + static long getCurrentTableValue( + DdlTransactionIsolator isolator, + String tableName, String columnName, + String segmentColumnName, String segmentValue) { + return execute( isolator, + "select " + columnName + " from " + tableName + + " where " + segmentColumnName + " = '" + segmentValue + "'", + "Could not fetch the current table value from the database" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java index 330d57e6eabf..034ac27c2b4d 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java @@ -4,22 +4,22 @@ */ package org.hibernate.id.enhanced; -import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; import org.hibernate.AssertionFailure; import org.hibernate.boot.model.relational.Database; -import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.boot.model.relational.InitCommand; import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.Sequence; import org.hibernate.boot.model.relational.SqlStringGenerationContext; -import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IntegralDataTypeHolder; +import org.hibernate.mapping.Table; import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_LOGGER; import static org.hibernate.id.IdentifierGeneratorHelper.getIntegralDataTypeHolder; +import static org.hibernate.id.enhanced.ResyncHelper.getNextSequenceValue; +import static org.hibernate.id.enhanced.ResyncHelper.getMaxPrimaryKey; /** * Describes a sequence. @@ -107,14 +107,15 @@ public AccessCallback buildCallback(final SharedSessionContractImplementor sessi public IntegralDataTypeHolder getNextValue() { accessCounter++; try { - final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); - final PreparedStatement st = jdbcCoordinator.getStatementPreparer().prepareStatement( sql ); + final var jdbcCoordinator = session.getJdbcCoordinator(); + final var statement = jdbcCoordinator.getStatementPreparer().prepareStatement( sql ); + final var resourceRegistry = jdbcCoordinator.getLogicalConnection().getResourceRegistry(); try { - final ResultSet rs = jdbcCoordinator.getResultSetReturn().extract( st, sql ); + final var resultSet = jdbcCoordinator.getResultSetReturn().extract( statement, sql ); try { - rs.next(); - final IntegralDataTypeHolder value = getIntegralDataTypeHolder( numberType ); - value.initialize( rs, 1 ); + resultSet.next(); + final var value = getIntegralDataTypeHolder( numberType ); + value.initialize( resultSet, 1 ); if ( JDBC_LOGGER.isTraceEnabled() ) { JDBC_LOGGER.sequenceValueRetrievedFromDatabase( value.makeValue() ); } @@ -122,7 +123,7 @@ public IntegralDataTypeHolder getNextValue() { } finally { try { - jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( rs, st ); + resourceRegistry.release( resultSet, statement ); } catch( Throwable ignore ) { // intentionally empty @@ -130,7 +131,7 @@ public IntegralDataTypeHolder getNextValue() { } } finally { - jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( st ); + resourceRegistry.release( statement ); jdbcCoordinator.afterStatementExecution(); } @@ -163,10 +164,25 @@ public void registerExportables(Database database) { @Override public void initialize(SqlStringGenerationContext context) { - this.sql = context.getDialect().getSequenceSupport() + sql = context.getDialect().getSequenceSupport() .getSequenceNextValString( context.format( physicalSequenceName ) ); } + @Override + public void registerExtraExportables(Table table, Optimizer optimizer) { + table.addResyncCommand( (sqlContext, isolator) -> { + final String sequenceName = sqlContext.format( physicalSequenceName ); + final String tableName = sqlContext.format( table.getQualifiedTableName() ); + final String primaryKeyColumnName = table.getPrimaryKey().getColumn( 0 ).getName(); + final int adjustment = optimizer.getAdjustment(); + final long max = getMaxPrimaryKey( isolator, primaryKeyColumnName, tableName ); + final long current = getNextSequenceValue( isolator, sequenceName); + final long startWith = Math.max( max + adjustment, current ); + return new InitCommand( sqlContext.getDialect().getSequenceSupport() + .getRestartSequenceString( sequenceName, startWith ) ); + } ); + } + @Override public boolean isPhysicalSequence() { return true; @@ -181,20 +197,20 @@ protected QualifiedName getQualifiedName() { } protected void buildSequence(Database database) { - final int sourceIncrementSize = getSourceIncrementSize(); - - final Namespace namespace = database.locateNamespace( + final var namespace = database.locateNamespace( logicalQualifiedSequenceName.getCatalogName(), logicalQualifiedSequenceName.getSchemaName() ); - Sequence sequence = namespace.locateSequence( logicalQualifiedSequenceName.getObjectName() ); + final int sourceIncrementSize = getSourceIncrementSize(); + final var objectName = logicalQualifiedSequenceName.getObjectName(); + Sequence sequence = namespace.locateSequence( objectName ); if ( sequence != null ) { sequence.validate( initialValue, sourceIncrementSize ); } else { sequence = namespace.createSequence( - logicalQualifiedSequenceName.getObjectName(), - (physicalName) -> new Sequence( + objectName, + physicalName -> new Sequence( contributor, namespace.getPhysicalName().catalog(), namespace.getPhysicalName().schema(), @@ -205,7 +221,6 @@ protected void buildSequence(Database database) { ) ); } - physicalSequenceName = sequence.getName(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java index ce51b8a3e8e6..b25fd47a792a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java @@ -24,6 +24,7 @@ import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.id.SequenceMismatchStrategy; +import org.hibernate.mapping.Table; import org.hibernate.service.ServiceRegistry; import org.hibernate.tool.schema.Action; import org.hibernate.tool.schema.extract.spi.SequenceInformation; @@ -151,6 +152,7 @@ public class SequenceStyleGenerator private DatabaseStructure databaseStructure; private Optimizer optimizer; private Type identifierType; + private Table table; /** * Getter for property 'databaseStructure'. @@ -190,6 +192,7 @@ public void configure(GeneratorCreationContext creationContext, Properties param final var dialect = jdbcEnvironment.getDialect(); identifierType = creationContext.getType(); + table = creationContext.getValue().getTable(); final var sequenceName = determineSequenceName( parameters, jdbcEnvironment, serviceRegistry ); final int initialValue = determineInitialValue( parameters ); @@ -329,6 +332,7 @@ private boolean isSchemaToBeRecreated(String contributor, ConfigurationService c @Override public void registerExportables(Database database) { databaseStructure.registerExportables( database ); + databaseStructure.registerExtraExportables( table, optimizer ); } @Override @@ -346,7 +350,6 @@ public void initialize(SqlStringGenerationContext context) { * @param jdbcEnv The JdbcEnvironment * @return The sequence name */ - @SuppressWarnings("UnusedParameters") protected QualifiedName determineSequenceName( Properties params, JdbcEnvironment jdbcEnv, @@ -381,7 +384,7 @@ private static QualifiedName sequenceName( /** * Determine the name of the column used to store the generator value in - * the db. + * the database. *

* Called during {@linkplain #configure configuration} when resolving to a * physical table. @@ -410,8 +413,8 @@ protected int determineInitialValue(Properties params) { } /** - * Determine the increment size to be applied. The exact implications of - * this value depends on the {@linkplain #getOptimizer() optimizer} being used. + * Determine the increment size to be applied. The exact implications of + * this value depend on the {@linkplain #getOptimizer() optimizer} in use. *

* Called during {@linkplain #configure configuration}. * @@ -439,7 +442,7 @@ protected OptimizerDescriptor determineOptimizationStrategy(Properties params, i /** * In certain cases we need to adjust the increment size based on the - * selected optimizer. This is the hook to achieve that. + * selected optimizer. This is the hook to achieve that. * * @param optimizationStrategy The optimizer strategy (name) * @param incrementSize The {@link #determineIncrementSize determined increment size} diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SingleNamingStrategy.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SingleNamingStrategy.java index 90e944c78b70..0dd22e4e985b 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SingleNamingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SingleNamingStrategy.java @@ -8,8 +8,8 @@ import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.QualifiedName; -import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.model.relational.QualifiedSequenceName; +import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.service.ServiceRegistry; @@ -49,7 +49,7 @@ public QualifiedName determineTableName( Identifier schemaName, Map configValues, ServiceRegistry serviceRegistry) { - return new QualifiedNameParser.NameParts( + return new QualifiedTableName( catalogName, schemaName, serviceRegistry.requireService( JdbcEnvironment.class ) diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/StandardNamingStrategy.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/StandardNamingStrategy.java index 1c3e86755c77..75b71a18ea03 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/StandardNamingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/StandardNamingStrategy.java @@ -13,6 +13,7 @@ import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.model.relational.QualifiedSequenceName; +import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.service.ServiceRegistry; @@ -73,27 +74,13 @@ public QualifiedName determineSequenceName( Identifier schemaName, Map configValues, ServiceRegistry serviceRegistry) { - final String rootTableName = getString( TABLE, configValues ); - final String implicitName = implicitSequenceName( rootTableName, configValues, serviceRegistry ); - - if ( implicitName.contains( "." ) ) { - return QualifiedNameParser.INSTANCE.parse( implicitName ); - } + final String implicitName = implicitSequenceName( rootTableName, configValues ); + return qualifiedSequenceName( catalogName, schemaName, serviceRegistry, implicitName ); - return new QualifiedSequenceName( - catalogName, - schemaName, - serviceRegistry.requireService( JdbcEnvironment.class ) - .getIdentifierHelper() - .toIdentifier( implicitName ) - ); } - private static String implicitSequenceName( - String rootTableName, - Map configValues, - ServiceRegistry serviceRegistry) { + private static String implicitSequenceName(String rootTableName, Map configValues) { final String explicitSuffix = getString( CONFIG_SEQUENCE_PER_ENTITY_SUFFIX, configValues ); final String base = getString( IMPLICIT_NAME_BASE, configValues, rootTableName ); @@ -126,20 +113,8 @@ public QualifiedName determineTableName( Identifier schemaName, Map configValues, ServiceRegistry serviceRegistry) { - final String tableName = implicitTableName( configValues ); - - if ( tableName.contains( "." ) ) { - return QualifiedNameParser.INSTANCE.parse( tableName ); - } - else { - return new QualifiedNameParser.NameParts( - catalogName, - schemaName, - serviceRegistry.requireService( JdbcEnvironment.class ) - .getIdentifierHelper() - .toIdentifier( tableName ) - ); - } + final String implicitName = implicitTableName( configValues ); + return qualifiedTableName( catalogName, schemaName, serviceRegistry, implicitName ); } private static String implicitTableName(Map configValues) { @@ -147,4 +122,27 @@ private static String implicitTableName(Map configValues) { return isNotEmpty( generatorName ) ? generatorName : DEF_TABLE; } + private static QualifiedName qualifiedSequenceName(Identifier catalogName, Identifier schemaName, ServiceRegistry serviceRegistry, String implicitName) { + return implicitName.contains( "." ) + ? QualifiedNameParser.INSTANCE.parse( implicitName ) + : new QualifiedSequenceName( + catalogName, + schemaName, + serviceRegistry.requireService( JdbcEnvironment.class ) + .getIdentifierHelper() + .toIdentifier( implicitName ) + ); + } + + private static QualifiedName qualifiedTableName(Identifier catalogName, Identifier schemaName, ServiceRegistry serviceRegistry, String implicitName) { + return implicitName.contains( "." ) + ? QualifiedNameParser.INSTANCE.parse( implicitName ) + : new QualifiedTableName( + catalogName, + schemaName, + serviceRegistry.requireService( JdbcEnvironment.class ) + .getIdentifierHelper() + .toIdentifier( implicitName ) + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java index d043a30411b2..554ffc234bf2 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java @@ -36,20 +36,26 @@ import org.hibernate.id.IntegralDataTypeHolder; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.jdbc.AbstractReturningWork; +import org.hibernate.mapping.Column; import org.hibernate.mapping.PrimaryKey; import org.hibernate.mapping.Table; +import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; import org.hibernate.service.ServiceRegistry; import org.hibernate.sql.SimpleSelect; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; +import org.hibernate.type.spi.TypeConfiguration; import static org.hibernate.boot.model.internal.GeneratorBinder.applyIfNotEmpty; import static org.hibernate.cfg.MappingSettings.TABLE_GENERATOR_STORE_LAST_USED; import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN; +import static org.hibernate.id.enhanced.ResyncHelper.getCurrentTableValue; +import static org.hibernate.id.enhanced.ResyncHelper.getMaxPrimaryKey; import static org.hibernate.id.enhanced.TableGeneratorLogger.TABLE_GENERATOR_LOGGER; import static org.hibernate.id.IdentifierGeneratorHelper.getNamingStrategy; import static org.hibernate.id.enhanced.OptimizerFactory.determineImplicitOptimizerName; import static org.hibernate.internal.util.StringHelper.isEmpty; +import static org.hibernate.internal.util.StringHelper.isNotBlank; import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean; import static org.hibernate.internal.util.config.ConfigurationHelper.getInt; @@ -186,6 +192,7 @@ public class TableGenerator implements PersistentIdentifierGenerator { private boolean storeLastUsedValue; private Type identifierType; + private Table table; private QualifiedName qualifiedTableName; private QualifiedName physicalTableName; @@ -326,6 +333,8 @@ public void configure(GeneratorCreationContext creationContext, Properties param .getSetting( TABLE_GENERATOR_STORE_LAST_USED, BOOLEAN, true ); identifierType = creationContext.getType(); + table = creationContext.getValue().getTable(); + final var jdbcEnvironment = serviceRegistry.requireService( JdbcEnvironment.class ); qualifiedTableName = determineGeneratorTableName( parameters, jdbcEnvironment, serviceRegistry ); @@ -478,30 +487,30 @@ protected int determineIncrementSize(Properties params) { return getInt( INCREMENT_PARAM, params, DEFAULT_INCREMENT_SIZE ); } - protected String buildSelectQuery(String formattedPhysicalTableName, SqlStringGenerationContext context) { + protected String buildSelectQuery(SqlStringGenerationContext context) { return new SimpleSelect( context.getDialect() ) .addColumn( valueColumnName ) - .setTableName( formattedPhysicalTableName ) + .setTableName( context.format( physicalTableName ) ) .addRestriction( segmentColumnName ) .setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ) ) .toStatementString(); } - protected String buildUpdateQuery(String formattedPhysicalTableName, SqlStringGenerationContext context) { - return "update " + formattedPhysicalTableName + protected String buildUpdateQuery(SqlStringGenerationContext context) { + return "update " + context.format( physicalTableName ) + " set " + valueColumnName + "=? " + " where " + valueColumnName + "=? and " + segmentColumnName + "=?"; } - protected String buildInsertQuery(String formattedPhysicalTableName, SqlStringGenerationContext context) { - return "insert into " + formattedPhysicalTableName + protected String buildInsertQuery(SqlStringGenerationContext context) { + return "insert into " + context.format( physicalTableName ) + " (" + segmentColumnName + ", " + valueColumnName + ") " + " values (?,?)"; } protected InitCommand generateInsertInitCommand(SqlStringGenerationContext context) { - final String renderedTableName = context.format( physicalTableName ); + final String tableName = context.format( physicalTableName ); final int value = storeLastUsedValue ? initialValue - 1 : initialValue; - return new InitCommand( "insert into " + renderedTableName + return new InitCommand( "insert into " + tableName + "(" + segmentColumnName + ", " + valueColumnName + ")" + " values ('" + segmentValue + "'," + ( value ) + ")" ); } @@ -544,66 +553,84 @@ private IntegralDataTypeHolder nextValue( SessionEventListenerManager listener, SharedSessionContractImplementor session) throws SQLException { - final IntegralDataTypeHolder value = makeValue(); + final var value = makeValue(); int rows; do { - TABLE_GENERATOR_LOGGER.retrievingCurrentValueForSegment( segmentValue ); - try ( var prepareStatement = prepareStatement( connection, selectQuery, logger, listener, session ) ) { - prepareStatement.setString( 1, segmentValue ); - final var resultSet = executeQuery( prepareStatement, listener, selectQuery, session ); - if ( !resultSet.next() ) { - final long initializationValue = storeLastUsedValue ? initialValue - 1 : initialValue; - value.initialize( initializationValue ); - - TABLE_GENERATOR_LOGGER.insertingInitialValueForSegment( value, segmentValue ); - try ( PreparedStatement statement = prepareStatement( connection, insertQuery, logger, listener, session ) ) { - statement.setString( 1, segmentValue ); - value.bind( statement, 2 ); - executeUpdate( statement, listener, insertQuery, session); - } - } - else { - final int defaultValue = storeLastUsedValue ? 0 : 1; - value.initialize( resultSet, defaultValue ); - if ( resultSet.wasNull() ) { - throw new HibernateException( - String.format( "%s for %s '%s' is null", - valueColumnName, segmentColumnName, - segmentValue ) ); - } - } - resultSet.close(); - } - catch (SQLException e) { - TABLE_GENERATOR_LOGGER.unableToReadOrInitializeHiValue( physicalTableName.render(), e ); - throw e; - } - - final IntegralDataTypeHolder updateValue = value.copy(); + retrieveCurrentValue( connection, logger, listener, session, value ); + final var updateValue = value.copy(); if ( optimizer.applyIncrementSizeToSourceValues() ) { updateValue.add( incrementSize ); } else { updateValue.increment(); } - TABLE_GENERATOR_LOGGER.updatingCurrentValueForSegment( updateValue, segmentValue ); - try ( var statement = prepareStatement( connection, updateQuery, logger, listener, session ) ) { - updateValue.bind( statement, 1 ); - value.bind( statement, 2 ); - statement.setString( 3, segmentValue ); - rows = executeUpdate( statement, listener, updateQuery, session ); - } - catch (SQLException e) { - TABLE_GENERATOR_LOGGER.unableToUpdateHiValue( physicalTableName.render(), e ); - throw e; - } + rows = updateValue( connection, logger, listener, session, updateValue, value ); } while ( rows == 0 ); - accessCount++; return storeLastUsedValue ? value.increment() : value; } + private int updateValue( + Connection connection, + SqlStatementLogger logger, + SessionEventListenerManager listener, + SharedSessionContractImplementor session, + IntegralDataTypeHolder updateValue, + IntegralDataTypeHolder value) + throws SQLException { + TABLE_GENERATOR_LOGGER.updatingCurrentValueForSegment( updateValue, segmentValue ); + try ( var statement = prepareStatement( connection, updateQuery, logger, listener, session ) ) { + updateValue.bind( statement, 1 ); + value.bind( statement, 2 ); + statement.setString( 3, segmentValue ); + return executeUpdate( statement, listener, updateQuery, session ); + } + catch (SQLException e) { + TABLE_GENERATOR_LOGGER.unableToUpdateHiValue( physicalTableName.render(), e ); + throw e; + } + } + + private void retrieveCurrentValue( + Connection connection, + SqlStatementLogger logger, + SessionEventListenerManager listener, + SharedSessionContractImplementor session, + IntegralDataTypeHolder value) + throws SQLException { + TABLE_GENERATOR_LOGGER.retrievingCurrentValueForSegment( segmentValue ); + try ( var prepareStatement = prepareStatement( connection, selectQuery, logger, listener, session ) ) { + prepareStatement.setString( 1, segmentValue ); + final var resultSet = executeQuery( prepareStatement, listener, selectQuery, session ); + if ( !resultSet.next() ) { + final long initializationValue = storeLastUsedValue ? initialValue - 1 : initialValue; + value.initialize( initializationValue ); + TABLE_GENERATOR_LOGGER.insertingInitialValueForSegment( value, segmentValue ); + try ( var statement = prepareStatement( connection, insertQuery, logger, listener, session ) ) { + statement.setString( 1, segmentValue ); + value.bind( statement, 2 ); + executeUpdate( statement, listener, insertQuery, session ); + } + } + else { + final int defaultValue = storeLastUsedValue ? 0 : 1; + value.initialize( resultSet, defaultValue ); + if ( resultSet.wasNull() ) { + throw new HibernateException( + String.format( "%s for %s '%s' is null", + valueColumnName, segmentColumnName, + segmentValue ) ); + } + } + resultSet.close(); + } + catch (SQLException e) { + TABLE_GENERATOR_LOGGER.unableToReadOrInitializeHiValue( physicalTableName.render(), e ); + throw e; + } + } + private PreparedStatement prepareStatement( Connection connection, String sql, @@ -666,53 +693,81 @@ private ResultSet executeQuery( @Override public void registerExportables(Database database) { - final Namespace namespace = database.locateNamespace( + final var namespace = database.locateNamespace( qualifiedTableName.getCatalogName(), qualifiedTableName.getSchemaName() ); - - Table table = namespace.locateTable( qualifiedTableName.getObjectName() ); - if ( table == null ) { - table = namespace.createTable( - qualifiedTableName.getObjectName(), - (identifier) -> new Table( contributor, namespace, identifier, false ) - ); - if ( isNotEmpty( options ) ) { - table.setOptions( options ); - } - - final var basicTypeRegistry = database.getTypeConfiguration().getBasicTypeRegistry(); - // todo : not sure the best solution here. do we add the columns if missing? other? - final var ddlTypeRegistry = database.getTypeConfiguration().getDdlTypeRegistry(); - final var segmentColumn = - ExportableColumnHelper.column( database, table, segmentColumnName, - basicTypeRegistry.resolve( StandardBasicTypes.STRING ), - ddlTypeRegistry.getTypeName( Types.VARCHAR, Size.length( segmentValueLength ) ) ); - segmentColumn.setNullable( false ); - table.addColumn( segmentColumn ); - - // lol - table.setPrimaryKey( new PrimaryKey( table ) ); - table.getPrimaryKey().addColumn( segmentColumn ); - - final var type = basicTypeRegistry.resolve( StandardBasicTypes.LONG ); - final var valueColumn = - ExportableColumnHelper.column( database, table, valueColumnName, type, - ddlTypeRegistry.getTypeName( type.getJdbcType().getDdlTypeCode(), database.getDialect() ) ); - table.addColumn( valueColumn ); - } - + final var existingTable = namespace.locateTable( qualifiedTableName.getObjectName() ); + final var table = existingTable == null ? createTable( database, namespace ) : existingTable; // allow physical naming strategies a chance to kick in physicalTableName = table.getQualifiedTableName(); table.addInitCommand( this::generateInsertInitCommand ); + table.addResyncCommand( this::generateResyncCommand ); + } + + private InitCommand generateResyncCommand(SqlStringGenerationContext context, DdlTransactionIsolator isolator) { + final String sequenceTableName = context.format( physicalTableName ); + final String tableName = context.format( table.getQualifiedTableName() ); + final String primaryKeyColumnName = table.getPrimaryKey().getColumn( 0 ).getName(); + final int adjustment = optimizer.getAdjustment() - 1; + final long max = getMaxPrimaryKey( isolator, primaryKeyColumnName, tableName ); + final long current = + getCurrentTableValue( isolator, sequenceTableName, valueColumnName, + segmentColumnName, segmentValue ); + if ( max + adjustment > current ) { + final String update = + "update " + sequenceTableName + + " set " + valueColumnName + " = " + (max + adjustment) + + " where " + segmentColumnName + " = '" + segmentValue + "'"; + return new InitCommand( update ); + } + else { + return new InitCommand(); + } + } + + private Table createTable(Database database, Namespace namespace) { + final var table = + namespace.createTable( qualifiedTableName.getObjectName(), + identifier -> new Table( contributor, namespace, identifier, false ) ); + if ( isNotBlank( options ) ) { + table.setOptions( options ); + } + + final var typeConfiguration = database.getTypeConfiguration(); + // todo : not sure the best solution here. do we add the columns if missing? other? + final var segmentColumn = segmentColumn( database, table, typeConfiguration ); + table.addColumn( segmentColumn ); + table.setPrimaryKey( new PrimaryKey( table ) ); + table.getPrimaryKey().addColumn( segmentColumn ); + + final var longType = + typeConfiguration.getBasicTypeRegistry() + .resolve( StandardBasicTypes.LONG ); + final var valueColumn = + ExportableColumnHelper.column( database, table, valueColumnName, longType, + typeConfiguration.getDdlTypeRegistry() + .getTypeName( longType.getJdbcType().getDdlTypeCode(), database.getDialect() ) ); + table.addColumn( valueColumn ); + return table; + } + + private Column segmentColumn(Database database, Table table, TypeConfiguration typeConfiguration) { + final var segmentColumn = + ExportableColumnHelper.column( database, table, segmentColumnName, + typeConfiguration.getBasicTypeRegistry() + .resolve( StandardBasicTypes.STRING ), + typeConfiguration.getDdlTypeRegistry() + .getTypeName( Types.VARCHAR, Size.length( segmentValueLength ) ) ); + segmentColumn.setNullable( false ); + return segmentColumn; } @Override public void initialize(SqlStringGenerationContext context) { - final String formattedPhysicalTableName = context.format( physicalTableName ); - selectQuery = buildSelectQuery( formattedPhysicalTableName, context ); - updateQuery = buildUpdateQuery( formattedPhysicalTableName, context ); - insertQuery = buildInsertQuery( formattedPhysicalTableName, context ); + selectQuery = buildSelectQuery( context ); + updateQuery = buildUpdateQuery( context ); + insertQuery = buildInsertQuery( context ); } public static void applyConfiguration( diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java index b05160f9410b..e4371440cf11 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java @@ -14,29 +14,22 @@ import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.InitCommand; -import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.SqlStringGenerationContext; -import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.monitor.spi.EventMonitor; -import org.hibernate.event.monitor.spi.DiagnosticEvent; -import org.hibernate.mapping.Column; import org.hibernate.id.IdentifierGenerationException; import org.hibernate.id.IntegralDataTypeHolder; import org.hibernate.jdbc.AbstractReturningWork; import org.hibernate.mapping.Table; -import org.hibernate.stat.spi.StatisticsImplementor; -import org.hibernate.type.BasicType; import org.hibernate.type.StandardBasicTypes; -import org.hibernate.type.spi.TypeConfiguration; - import static org.hibernate.LockMode.PESSIMISTIC_WRITE; import static org.hibernate.id.IdentifierGeneratorHelper.getIntegralDataTypeHolder; +import static org.hibernate.id.enhanced.ResyncHelper.getCurrentTableValue; +import static org.hibernate.id.enhanced.ResyncHelper.getMaxPrimaryKey; import static org.hibernate.id.enhanced.TableGeneratorLogger.TABLE_GENERATOR_LOGGER; /** @@ -148,8 +141,8 @@ public AccessCallback buildCallback(final SharedSessionContractImplementor sessi throw new AssertionFailure( "SequenceStyleGenerator's TableStructure was not properly initialized" ); } - final SessionEventListenerManager statsCollector = session.getEventListenerManager(); - final SqlStatementLogger statementLogger = + final var statsCollector = session.getEventListenerManager(); + final var statementLogger = session.getFactory().getJdbcServices() .getSqlStatementLogger(); @@ -160,29 +153,29 @@ public IntegralDataTypeHolder getNextValue() { new AbstractReturningWork<>() { @Override public IntegralDataTypeHolder execute(Connection connection) throws SQLException { - final IntegralDataTypeHolder value = makeValue(); + final var value = makeValue(); int rows; do { - try ( PreparedStatement selectStatement = prepareStatement( + try ( var selectStatement = prepareStatement( connection, selectQuery, statementLogger, statsCollector, session ) ) { - final ResultSet selectRS = executeQuery( + final var resultSet = executeQuery( selectStatement, statsCollector, selectQuery, session ); - if ( !selectRS.next() ) { + if ( !resultSet.next() ) { throw new IdentifierGenerationException( "Could not read a hi value, populate the table: " + physicalTableName ); } - value.initialize( selectRS, 1 ); - selectRS.close(); + value.initialize( resultSet, 1 ); + resultSet.close(); } catch (SQLException sqle) { TABLE_GENERATOR_LOGGER.unableToReadHiValue( physicalTableName.render(), sqle ); @@ -190,7 +183,7 @@ public IntegralDataTypeHolder execute(Connection connection) throws SQLException } - try ( PreparedStatement updatePS = prepareStatement( + try ( var updateStatement = prepareStatement( connection, updateQuery, statementLogger, @@ -198,10 +191,10 @@ public IntegralDataTypeHolder execute(Connection connection) throws SQLException session ) ) { final int increment = applyIncrementSizeToSourceValues ? incrementSize : 1; - final IntegralDataTypeHolder updateValue = value.copy().add( increment ); - updateValue.bind( updatePS, 1 ); - value.bind( updatePS, 2 ); - rows = executeUpdate( updatePS, statsCollector, updateQuery, session ); + final var updateValue = value.copy().add( increment ); + updateValue.bind( updateStatement, 1 ); + value.bind( updateStatement, 2 ); + rows = executeUpdate( updateStatement, statsCollector, updateQuery, session ); } catch (SQLException e) { TABLE_GENERATOR_LOGGER.unableToUpdateHiValue( physicalTableName.render(), e ); @@ -232,9 +225,9 @@ private PreparedStatement prepareStatement( SessionEventListenerManager statsCollector, SharedSessionContractImplementor session) throws SQLException { logger.logStatement( sql, FormatStyle.BASIC.getFormatter() ); - final EventMonitor eventMonitor = session.getEventMonitor(); - final DiagnosticEvent creationEvent = eventMonitor.beginJdbcPreparedStatementCreationEvent(); - final StatisticsImplementor stats = session.getFactory().getStatistics(); + final var eventMonitor = session.getEventMonitor(); + final var creationEvent = eventMonitor.beginJdbcPreparedStatementCreationEvent(); + final var stats = session.getFactory().getStatistics(); try { statsCollector.jdbcPrepareStatementStart(); if ( stats != null && stats.isStatisticsEnabled() ) { @@ -256,8 +249,8 @@ private int executeUpdate( SessionEventListenerManager statsCollector, String sql, SharedSessionContractImplementor session) throws SQLException { - final EventMonitor eventMonitor = session.getEventMonitor(); - final DiagnosticEvent executionEvent = eventMonitor.beginJdbcPreparedStatementExecutionEvent(); + final var eventMonitor = session.getEventMonitor(); + final var executionEvent = eventMonitor.beginJdbcPreparedStatementExecutionEvent(); try { statsCollector.jdbcExecuteStatementStart(); return ps.executeUpdate(); @@ -274,8 +267,8 @@ private ResultSet executeQuery( SessionEventListenerManager statsCollector, String sql, SharedSessionContractImplementor session) throws SQLException { - final EventMonitor eventMonitor = session.getEventMonitor(); - final DiagnosticEvent executionEvent = eventMonitor.beginJdbcPreparedStatementExecutionEvent(); + final var eventMonitor = session.getEventMonitor(); + final var executionEvent = eventMonitor.beginJdbcPreparedStatementExecutionEvent(); try { statsCollector.jdbcExecuteStatementStart(); return ps.executeQuery(); @@ -293,19 +286,17 @@ public boolean isPhysicalSequence() { @Override public void registerExportables(Database database) { - - final Namespace namespace = database.locateNamespace( + final var namespace = database.locateNamespace( logicalQualifiedTableName.getCatalogName(), logicalQualifiedTableName.getSchemaName() ); - Table table = namespace.locateTable( logicalQualifiedTableName.getObjectName() ); + final var objectName = logicalQualifiedTableName.getObjectName(); + Table table = namespace.locateTable( objectName ); final boolean tableCreated; if ( table == null ) { - table = namespace.createTable( - logicalQualifiedTableName.getObjectName(), - (identifier) -> new Table( contributor, namespace, identifier, false ) - ); + table = namespace.createTable( objectName, + identifier -> new Table( contributor, namespace, identifier, false ) ); tableCreated = true; } else { @@ -313,23 +304,17 @@ public void registerExportables(Database database) { } physicalTableName = table.getQualifiedTableName(); - valueColumnNameText = logicalValueColumnNameIdentifier.render( database.getJdbcEnvironment().getDialect() ); + valueColumnNameText = logicalValueColumnNameIdentifier.render( database.getDialect() ); if ( tableCreated ) { - final TypeConfiguration typeConfiguration = database.getTypeConfiguration(); - final BasicType type = typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.LONG ); - final Column valueColumn = ExportableColumnHelper.column( - database, - table, - valueColumnNameText, - type, + final var typeConfiguration = database.getTypeConfiguration(); + final var type = typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.LONG ); + final String typeName = typeConfiguration.getDdlTypeRegistry() - .getTypeName( type.getJdbcType().getDdlTypeCode(), database.getDialect() ) - ); - + .getTypeName( type.getJdbcType().getDdlTypeCode(), database.getDialect() ); + final var valueColumn = + ExportableColumnHelper.column( database, table, valueColumnNameText, type, typeName ); table.addColumn( valueColumn ); - table.setOptions( options ); - table.addInitCommand( context -> new InitCommand( "insert into " + context.format( physicalTableName ) + " ( " + valueColumnNameText + " ) values ( " + initialValue + " )" @@ -339,16 +324,36 @@ public void registerExportables(Database database) { @Override public void initialize(SqlStringGenerationContext context) { - final Dialect dialect = context.getDialect(); + final var dialect = context.getDialect(); final String formattedPhysicalTableName = context.format( physicalTableName ); final String lockedTable = dialect.appendLockHint( new LockOptions( PESSIMISTIC_WRITE ), formattedPhysicalTableName ) + dialect.getForUpdateString(); selectQuery = "select " + valueColumnNameText + " as id_val" + " from " + lockedTable ; - updateQuery = "update " + formattedPhysicalTableName + " set " + valueColumnNameText + "= ?" + " where " + valueColumnNameText + "=?"; } + + @Override + public void registerExtraExportables(Table table, Optimizer optimizer) { + table.addResyncCommand( (context, isolator) -> { + final String sequenceTableName = context.format( physicalTableName ); + final String tableName = context.format( table.getQualifiedTableName() ); + final String primaryKeyColumnName = table.getPrimaryKey().getColumn( 0 ).getName(); + final int adjustment = optimizer.getAdjustment(); + final long max = getMaxPrimaryKey( isolator, primaryKeyColumnName, tableName ); + final long current = getCurrentTableValue( isolator, sequenceTableName, valueColumnNameText ); + if ( max + adjustment > current ) { + final String update = + "update " + sequenceTableName + + " set " + valueColumnNameText + " = " + (max + adjustment); + return new InitCommand( update ); + } + else { + return new InitCommand(); + } + } ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index edbd4f0887cc..c5e35b1def13 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -602,6 +602,11 @@ public Property getProperty() { return Property.this; } + @Override + public Value getValue() { + return value; + } + @Override public SqlStringGenerationContext getSqlStringGenerationContext() { return context.getSqlStringGenerationContext(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index 4055e102423e..2f372d4fd587 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -422,7 +422,7 @@ public Generator createGenerator( Property property, GeneratorSettings defaults) { if ( customIdGeneratorCreator != null ) { - final var context = new IdGeneratorCreationContext( rootClass, property, defaults ); + final var context = new IdGeneratorCreationContext( this, rootClass, property, defaults ); final var generator = customIdGeneratorCreator.createGenerator( context ); if ( generator.allowAssignedIdentifiers() && nullValue == null ) { setNullValueUndefined(); @@ -1074,11 +1074,13 @@ public Long[] getColumnLengths() { } private class IdGeneratorCreationContext implements GeneratorCreationContext { + private final SimpleValue identifier; private final RootClass rootClass; private final Property property; private final GeneratorSettings defaults; - public IdGeneratorCreationContext(RootClass rootClass, Property property, GeneratorSettings defaults) { + public IdGeneratorCreationContext(SimpleValue identifier, RootClass rootClass, Property property, GeneratorSettings defaults) { + this.identifier = identifier; this.rootClass = rootClass; this.property = property; this.defaults = defaults; @@ -1124,6 +1126,11 @@ public Property getProperty() { return property; } + @Override + public Value getValue() { + return identifier; + } + @Override public Type getType() { return SimpleValue.this.getType(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index 06398fe1e870..aceb43ae8820 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.Function; import org.hibernate.Incubating; @@ -28,6 +29,7 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.dialect.Dialect; +import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; import org.jboss.logging.Logger; import static java.util.Collections.emptyList; @@ -72,6 +74,7 @@ public class Table implements Serializable, ContributableDatabaseObject { private String options; private List> initCommandProducers; + private List> resyncCommandProducers; @Deprecated(since="6.2", forRemoval = true) public Table() { @@ -808,16 +811,29 @@ public void addInitCommand(Function com } public List getInitCommands(SqlStringGenerationContext context) { - if ( initCommandProducers == null ) { - return emptyList(); - } - else { - final List initCommands = new ArrayList<>(); - for ( Function producer : initCommandProducers ) { - initCommands.add( producer.apply( context ) ); - } - return unmodifiableList( initCommands ); + return initCommandProducers == null + ? emptyList() + : initCommandProducers.stream() + .map( producer -> producer.apply( context ) ) + .distinct() + .toList(); + } + + public void addResyncCommand(BiFunction commandProducer) { + if ( resyncCommandProducers == null ) { + resyncCommandProducers = new ArrayList<>(); } + resyncCommandProducers.add( commandProducer ); + } + + public List getResyncCommands(SqlStringGenerationContext context, DdlTransactionIsolator isolator) { + return resyncCommandProducers == null + ? emptyList() + : resyncCommandProducers.stream() + .map( producer -> producer.apply( context, isolator ) ) + .distinct() + .toList(); + } public String getOptions() { diff --git a/hibernate-core/src/main/java/org/hibernate/relational/SchemaManager.java b/hibernate-core/src/main/java/org/hibernate/relational/SchemaManager.java index 55cd0f40c28f..732df9a060cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/relational/SchemaManager.java +++ b/hibernate-core/src/main/java/org/hibernate/relational/SchemaManager.java @@ -5,6 +5,7 @@ package org.hibernate.relational; import org.hibernate.Incubating; +import org.hibernate.tool.schema.spi.GeneratorSynchronizer; /** * Allows programmatic {@linkplain #exportMappedObjects schema export}, @@ -19,7 +20,8 @@ * {@link jakarta.persistence.SchemaManager}, which it now inherits, * with a minor change to the naming of its operations. It is retained * for backward compatibility and as a place to define additional - * functionality such as {@link #populate} and {@link #forSchema}. + * operations like {@link #populate}, {@link #resynchronizeGenerators}, + * and {@link #forSchema}. * * @since 6.2 * @author Gavin King @@ -95,6 +97,25 @@ public interface SchemaManager extends jakarta.persistence.SchemaManager { @Incubating void populate(); + /** + * Resynchronize {@linkplain jakarta.persistence.SequenceGenerator sequences} and + * {@linkplain jakarta.persistence.TableGenerator table-based generators} after + * importing entity data. + *

+ * When data is imported to the database without the use of a Hibernate session, + * a database sequence might become stale with respect to the data in the table for + * which it is used to generate unique keys. This operation restarts every sequence + * so that the next generated unique key will be larger than the largest key + * currently in use. A similar phenomenon might occur for the database table backing + * a table-based generator, and so this operation also updates such tables. + *

+ * Programmatic way to run {@link GeneratorSynchronizer}. + * + * @since 7.2 + */ + @Incubating + void resynchronizeGenerators(); + /** * Obtain an instance which targets the given schema. *

diff --git a/hibernate-core/src/main/java/org/hibernate/relational/internal/SchemaManagerImpl.java b/hibernate-core/src/main/java/org/hibernate/relational/internal/SchemaManagerImpl.java index f74254d20900..10206bf8675a 100644 --- a/hibernate-core/src/main/java/org/hibernate/relational/internal/SchemaManagerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/relational/internal/SchemaManagerImpl.java @@ -142,6 +142,20 @@ public void populate() { ); } + @Override + public void resynchronizeGenerators() { + Map properties = new HashMap<>( sessionFactory.getProperties() ); + properties.put( JAKARTA_HBM2DDL_DATABASE_ACTION, Action.SYNCHRONIZE ); + properties.put( JAKARTA_HBM2DDL_SCRIPTS_ACTION, Action.NONE ); + addSchemaAndCatalog( properties ); + SchemaManagementToolCoordinator.process( + metadata, + sessionFactory.getServiceRegistry(), + properties, + action -> {} + ); + } + @Override public void create(boolean createSchemas) { exportMappedObjects( createSchemas ); diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/DdlTransactionIsolatorNonJtaImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/DdlTransactionIsolatorNonJtaImpl.java index a3920594c2a9..de52143d1037 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/DdlTransactionIsolatorNonJtaImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/DdlTransactionIsolatorNonJtaImpl.java @@ -41,8 +41,7 @@ public Connection getIsolatedConnection() { public Connection getIsolatedConnection(boolean autocommit) { if ( jdbcConnection == null ) { try { - this.jdbcConnection = jdbcContext.getJdbcConnectionAccess().obtainConnection(); - + jdbcConnection = jdbcContext.getJdbcConnectionAccess().obtainConnection(); try { if ( jdbcConnection.getAutoCommit() != autocommit ) { try { diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/DdlTransactionIsolator.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/DdlTransactionIsolator.java index 8a64c6bf35bd..7a027b2cf6cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/DdlTransactionIsolator.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/DdlTransactionIsolator.java @@ -15,7 +15,7 @@ * * @author Steve Ebersole */ -public interface DdlTransactionIsolator { +public interface DdlTransactionIsolator extends AutoCloseable { JdbcContext getJdbcContext(); /** @@ -43,4 +43,9 @@ public interface DdlTransactionIsolator { Connection getIsolatedConnection(boolean autocommit); void release(); + + @Override + default void close() { + release(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java index a5c2f0604ca5..a4d9c1d3f059 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java @@ -116,7 +116,17 @@ public enum Action { * * @since 7.0 */ - POPULATE; + POPULATE, + /** + * Synchronize sequences with the data held in tables. + * + * @apiNote This action is not defined by JPA. + * + * @see org.hibernate.tool.schema.spi.SchemaPopulator + * + * @since 7.2 + */ + SYNCHRONIZE; /** * @see #NONE @@ -150,6 +160,10 @@ public enum Action { * @see #POPULATE */ public static final String ACTION_POPULATE = "populate"; + /** + * @see #SYNCHRONIZE + */ + public static final String ACTION_SYNCHRONIZE = "synchronize"; /** * @see #NONE @@ -197,6 +211,7 @@ public String getExternalHbm2ddlName() { case VALIDATE -> ACTION_VALIDATE; case UPDATE -> ACTION_UPDATE; case POPULATE -> ACTION_POPULATE; + case SYNCHRONIZE -> ACTION_SYNCHRONIZE; default -> null; }; } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java index de45778d6de7..811c79f4e701 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java @@ -85,8 +85,7 @@ public void doMigration( final SqlStringGenerationContext sqlGenerationContext = sqlGenerationContext( metadata, options ); if ( !targetDescriptor.getTargetTypes().isEmpty() ) { final JdbcContext jdbcContext = tool.resolveJdbcContext( options.getConfigurationValues() ); - final DdlTransactionIsolator ddlTransactionIsolator = tool.getDdlTransactionIsolator( jdbcContext ); - try { + try ( DdlTransactionIsolator ddlTransactionIsolator = tool.getDdlTransactionIsolator( jdbcContext ) ) { final DatabaseInformation databaseInformation = Helper.buildDatabaseInformation( tool.getServiceRegistry(), ddlTransactionIsolator, @@ -136,9 +135,6 @@ public void doMigration( } } } - finally { - ddlTransactionIsolator.release(); - } } } @@ -179,7 +175,7 @@ private void performMigration( final Set exportIdentifiers = CollectionHelper.setOfSize( 50 ); final Database database = metadata.getDatabase(); - Exporter auxiliaryExporter = dialect.getAuxiliaryDatabaseObjectExporter(); + final Exporter auxiliaryExporter = dialect.getAuxiliaryDatabaseObjectExporter(); // Drop all AuxiliaryDatabaseObjects for ( AuxiliaryDatabaseObject auxiliaryDatabaseObject : database.getAuxiliaryDatabaseObjects() ) { @@ -255,7 +251,7 @@ private void performMigration( } } - //NOTE : Foreign keys must be created *after* all tables of all namespaces for cross namespace fks. see HHH-10420 + //NOTE: Foreign keys must be created *after* all tables of all namespaces for cross-namespace fks. see HHH-10420 for ( Namespace namespace : database.getNamespaces() ) { if ( schemaFilter.includeNamespace( namespace ) ) { final NameSpaceTablesInformation nameSpaceTablesInformation = tablesInformation.get( namespace ); diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java index 23876631f9f7..47e6a3acf2e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java @@ -63,26 +63,25 @@ public void doValidation( ); final JdbcContext jdbcContext = tool.resolveJdbcContext( options.getConfigurationValues() ); - final DdlTransactionIsolator isolator = tool.getDdlTransactionIsolator( jdbcContext ); - final DatabaseInformation databaseInformation = Helper.buildDatabaseInformation( - tool.getServiceRegistry(), - isolator, - context, - tool - ); + try ( DdlTransactionIsolator isolator = tool.getDdlTransactionIsolator( jdbcContext ) ) { + final DatabaseInformation databaseInformation = Helper.buildDatabaseInformation( + tool.getServiceRegistry(), + isolator, + context, + tool + ); - try { - performValidation( metadata, databaseInformation, options, contributableInclusionFilter, jdbcContext.getDialect() ); - } - finally { try { - databaseInformation.cleanup(); + performValidation( metadata, databaseInformation, options, contributableInclusionFilter, jdbcContext.getDialect() ); } - catch (Exception e) { - LOG.debug( "Problem releasing DatabaseInformation: " + e.getMessage() ); + finally { + try { + databaseInformation.cleanup(); + } + catch (Exception e) { + LOG.debug( "Problem releasing DatabaseInformation: " + e.getMessage() ); + } } - - isolator.release(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/GeneratorSynchronizerImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/GeneratorSynchronizerImpl.java new file mode 100644 index 000000000000..ce1c68fac3a1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/GeneratorSynchronizerImpl.java @@ -0,0 +1,113 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.tool.schema.internal; + +import org.hibernate.Internal; +import org.hibernate.boot.Metadata; +import org.hibernate.engine.jdbc.internal.FormatStyle; +import org.hibernate.engine.jdbc.internal.Formatter; +import org.hibernate.tool.schema.internal.exec.JdbcContext; +import org.hibernate.tool.schema.spi.ContributableMatcher; +import org.hibernate.tool.schema.spi.ExecutionOptions; +import org.hibernate.tool.schema.spi.GenerationTarget; +import org.hibernate.tool.schema.spi.SchemaFilter; +import org.hibernate.tool.schema.spi.GeneratorSynchronizer; +import org.hibernate.tool.schema.spi.TargetDescriptor; +import org.jboss.logging.Logger; + + +import static org.hibernate.tool.schema.internal.Helper.applySqlStrings; +import static org.hibernate.tool.schema.internal.Helper.createSqlStringGenerationContext; +import static org.hibernate.tool.schema.internal.Helper.interpretFormattingEnabled; + +/** + * Basic implementation of {@link GeneratorSynchronizer}. + * + * @author Gavin King + */ +public class GeneratorSynchronizerImpl implements GeneratorSynchronizer { + private static final Logger LOG = Logger.getLogger( GeneratorSynchronizerImpl.class ); + + private final HibernateSchemaManagementTool tool; + private final SchemaFilter schemaFilter; + + public GeneratorSynchronizerImpl(HibernateSchemaManagementTool tool, SchemaFilter truncatorFilter) { + this.tool = tool; + schemaFilter = truncatorFilter; + } + + @Override + public void doSynchronize( + Metadata metadata, + ExecutionOptions options, + ContributableMatcher contributableInclusionFilter, + TargetDescriptor targetDescriptor) { + final var configuration = options.getConfigurationValues(); + final var jdbcContext = tool.resolveJdbcContext( configuration ); + doSynchronize( metadata, options, contributableInclusionFilter, jdbcContext, + tool.buildGenerationTargets( targetDescriptor, jdbcContext, configuration, true ) ); + } + + @Internal + public void doSynchronize( + Metadata metadata, + ExecutionOptions options, + ContributableMatcher contributableInclusionFilter, + JdbcContext dialect, + GenerationTarget... targets) { + for ( var target : targets ) { + target.prepare(); + } + + try { + performSync( metadata, options, contributableInclusionFilter, dialect, targets ); + } + finally { + for ( var target : targets ) { + try { + target.release(); + } + catch (Exception e) { + LOG.debugf( "Problem releasing GenerationTarget [%s] : %s", target, e.getMessage() ); + } + } + } + } + + private void performSync( + Metadata metadata, + ExecutionOptions options, + ContributableMatcher contributableInclusionFilter, + JdbcContext dialect, + GenerationTarget... targets) { + final var formatter = + interpretFormattingEnabled( options.getConfigurationValues() ) + ? FormatStyle.DDL.getFormatter() + : FormatStyle.NONE.getFormatter(); + syncFromMetadata( metadata, options, schemaFilter, contributableInclusionFilter, dialect, formatter, targets ); + } + + private void syncFromMetadata( + Metadata metadata, + ExecutionOptions options, + SchemaFilter schemaFilter, + ContributableMatcher contributableInclusionFilter, + JdbcContext jdbcContext, + Formatter formatter, + GenerationTarget... targets) { + try ( var ddlTransactionIsolator = tool.getDdlTransactionIsolator( jdbcContext ) ) { + final var context = createSqlStringGenerationContext( options, metadata ); + for ( var namespace : metadata.getDatabase().getNamespaces() ) { + if ( schemaFilter.includeNamespace( namespace ) ) { + for ( var table : namespace.getTables() ) { + for ( var command : table.getResyncCommands( context, ddlTransactionIsolator ) ) { + applySqlStrings( command.initCommands(), formatter, options, targets ); + } + } + } + } + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.java index 22aa22ff7d0a..683440b2d0b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.java @@ -42,6 +42,7 @@ import org.hibernate.tool.schema.spi.SchemaPopulator; import org.hibernate.tool.schema.spi.SchemaTruncator; import org.hibernate.tool.schema.spi.SchemaValidator; +import org.hibernate.tool.schema.spi.GeneratorSynchronizer; import org.hibernate.tool.schema.spi.TargetDescriptor; import org.jboss.logging.Logger; @@ -106,6 +107,11 @@ public SchemaPopulator getSchemaPopulator(Map options) { return new SchemaPopulatorImpl( this ); } + @Override + public GeneratorSynchronizer getSequenceSynchronizer(Map options) { + return new GeneratorSynchronizerImpl( this, getSchemaFilterProvider( options ).getMigrateFilter() ); + } + @Override public SchemaMigrator getSchemaMigrator(Map options) { final SchemaFilter migrateFilter = getSchemaFilterProvider( options ).getMigrateFilter(); diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableMigrator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableMigrator.java index f043f57bf64b..df10c0148dd3 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableMigrator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableMigrator.java @@ -103,7 +103,7 @@ else if ( dialect.supportsAlterColumnType() ) { } if ( results.isEmpty() ) { - LOG.debugf( "No alter strings for table : %s", table.getQuotedName() ); + LOG.debugf( "No alter strings for table: %s", table.getQuotedName() ); } return results; diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/GeneratorSynchronizer.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/GeneratorSynchronizer.java new file mode 100644 index 000000000000..3a753aa9bf08 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/GeneratorSynchronizer.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.tool.schema.spi; + +import org.hibernate.Incubating; +import org.hibernate.boot.Metadata; + +/** + * Service delegate for handling sequence resynchronization. + * + * @author Gavin King + * + * @since 6.2 + */ +@Incubating +public interface GeneratorSynchronizer { + /** + * Perform sequence resynchronization from the indicated source(s) to the indicated target(s). + * @param metadata Represents the schema to be dropped. + * @param options Options for executing the drop + * @param contributableInclusionFilter Filter for Contributable instances to use + * @param targetDescriptor description of the target(s) for the drop commands + */ + void doSynchronize( + Metadata metadata, + ExecutionOptions options, + ContributableMatcher contributableInclusionFilter, + TargetDescriptor targetDescriptor); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementTool.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementTool.java index 3732fb036c50..874c03eaed9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementTool.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementTool.java @@ -27,6 +27,9 @@ default SchemaPopulator getSchemaPopulator(Map options) { default SchemaTruncator getSchemaTruncator(Map options) { throw new UnsupportedOperationException("Schema truncator is not supported by this schema management tool."); } + default GeneratorSynchronizer getSequenceSynchronizer(Map options) { + throw new UnsupportedOperationException("Schema populator is not supported by this schema management tool."); + } /** * This allows to set an alternative implementation for the Database diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java index 65c891a354d2..952186241169 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java @@ -282,6 +282,19 @@ private static void performDatabaseAction( ); break; } + case SYNCHRONIZE: { + tool.getSequenceSynchronizer( configurationValues ).doSynchronize( + metadata, + executionOptions, + contributableInclusionFilter, + buildDatabaseTargetDescriptor( + configurationValues, + CreateSettingSelector.INSTANCE, + serviceRegistry + ) + ); + break; + } case TRUNCATE: { tool.getSchemaTruncator( configurationValues ).doTruncate( metadata, diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaTruncator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaTruncator.java index f97ffd6faa2f..732c8d2c0cbe 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaTruncator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaTruncator.java @@ -17,7 +17,7 @@ @Incubating public interface SchemaTruncator { /** - * Perform a schema truncation from the indicated source(s) to the indicated target(s). + * Perform schema truncation from the indicated source(s) to the indicated target(s). * @param metadata Represents the schema to be dropped. * @param options Options for executing the drop * @param contributableInclusionFilter Filter for Contributable instances to use diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/HibernateOrmSpecificAttributesMappingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/HibernateOrmSpecificAttributesMappingTest.java index 1613b9ed679d..bde597a55115 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/HibernateOrmSpecificAttributesMappingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/HibernateOrmSpecificAttributesMappingTest.java @@ -21,6 +21,7 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.Value; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.SqlTypes; import org.hibernate.usertype.UserTypeSupport; @@ -169,5 +170,10 @@ public RootClass getRootClass() { public Property getProperty() { return tenantId; } + + @Override + public Value getValue() { + return tenantId.getValue(); + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/SequenceStyleGeneratorBehavesLikeSequeceHiloGeneratorWitZeroIncrementSizeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/SequenceStyleGeneratorBehavesLikeSequenceHiloGeneratorWithZeroIncrementSizeTest.java similarity index 94% rename from hibernate-core/src/test/java/org/hibernate/orm/test/id/SequenceStyleGeneratorBehavesLikeSequeceHiloGeneratorWitZeroIncrementSizeTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/id/SequenceStyleGeneratorBehavesLikeSequenceHiloGeneratorWithZeroIncrementSizeTest.java index 799772e8beaa..7212beb6fb7f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/SequenceStyleGeneratorBehavesLikeSequeceHiloGeneratorWitZeroIncrementSizeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/SequenceStyleGeneratorBehavesLikeSequenceHiloGeneratorWithZeroIncrementSizeTest.java @@ -19,9 +19,12 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.GeneratorCreationContext; import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.Table; +import org.hibernate.mapping.Value; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; @@ -43,7 +46,7 @@ */ @RequiresDialectFeature(feature = SupportsSequences.class) @BaseUnitTest -public class SequenceStyleGeneratorBehavesLikeSequeceHiloGeneratorWitZeroIncrementSizeTest { +public class SequenceStyleGeneratorBehavesLikeSequenceHiloGeneratorWithZeroIncrementSizeTest { private static final String TEST_SEQUENCE = "test_sequence"; private StandardServiceRegistry serviceRegistry; @@ -104,6 +107,11 @@ public Property getProperty() { return null; } + @Override + public Value getValue() { + return new BasicValue( buildingContext, new Table() ); + } + @Override public Type getType() { return buildingContext.getBootstrapContext() diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/enhanced/SequenceStyleConfigUnitTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/enhanced/SequenceStyleConfigUnitTest.java index 5684cff4f611..8af6fca20157 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/enhanced/SequenceStyleConfigUnitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/enhanced/SequenceStyleConfigUnitTest.java @@ -33,9 +33,12 @@ import org.hibernate.id.enhanced.SequenceStyleGenerator; import org.hibernate.id.enhanced.StandardOptimizerDescriptor; import org.hibernate.id.enhanced.TableStructure; +import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.Table; +import org.hibernate.mapping.Value; import org.hibernate.service.ServiceRegistry; import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.SettingProvider; @@ -393,19 +396,6 @@ public void testPreferredPooledOptimizerSetting() { } } - - public static final SettingProvider.Provider> TABLE_DIALECT_PROVIDER = new SettingProvider.Provider>() { - @Override - public Class getSetting() { - return TableDialect.class; - } - }; - - public static final SettingProvider.Provider> SEQUENCE_DIALECT_PROVIDER - = () -> SequenceDialect.class; - public static final SettingProvider.Provider> POOLED_SEQUENCE_DIALECT_PROVIDER - = () -> PooledSequenceDialect.class; - public static class TableDialect extends Dialect implements SettingProvider.Provider { @Override public DatabaseVersion getVersion() { @@ -444,11 +434,13 @@ public SequenceSupport getSequenceSupport() { private static class TestGeneratorCreationContext implements GeneratorCreationContext { private final Type type; + private final BasicValue value; private final MetadataImplementor metadata; private final ServiceRegistry serviceRegistry; public TestGeneratorCreationContext(Type type, MetadataBuildingContext buildingContext, ServiceRegistry serviceRegistry) { this.type = type; + this.value = new BasicValue( buildingContext, new Table() ); this.metadata = buildingContext.getMetadataCollector(); this.serviceRegistry = serviceRegistry; } @@ -488,6 +480,11 @@ public Property getProperty() { return null; } + @Override + public Value getValue() { + return value; + } + @Override public Type getType() { return type; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/table/Db2GenerationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/table/Db2GenerationTest.java deleted file mode 100644 index 6e6300fec629..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/table/Db2GenerationTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.orm.test.idgen.enhanced.table; - -import org.hibernate.boot.MetadataSources; -import org.hibernate.boot.model.relational.Database; -import org.hibernate.boot.model.relational.Namespace; -import org.hibernate.boot.model.relational.SqlStringGenerationContext; -import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; -import org.hibernate.boot.registry.StandardServiceRegistry; -import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.dialect.DB2Dialect; -import org.hibernate.generator.GeneratorCreationContext; -import org.hibernate.id.enhanced.TableGenerator; -import org.hibernate.mapping.PersistentClass; -import org.hibernate.mapping.Property; -import org.hibernate.mapping.RootClass; -import org.hibernate.mapping.Table; -import org.hibernate.service.ServiceRegistry; -import org.hibernate.testing.orm.junit.JiraKey; -import org.hibernate.testing.orm.junit.RequiresDialect; -import org.hibernate.testing.orm.junit.ServiceRegistryScope; -import org.hibernate.type.StandardBasicTypes; -import org.hibernate.type.Type; -import org.junit.jupiter.api.Test; - -import java.util.Properties; - -import static org.assertj.core.api.Assertions.assertThat; - - -@SuppressWarnings("JUnitMalformedDeclaration") -@org.hibernate.testing.orm.junit.ServiceRegistry -@RequiresDialect( DB2Dialect.class ) -public class Db2GenerationTest { - @Test - @JiraKey( value = "HHH-9850" ) - public void testNewGeneratorTableCreationOnDb2(ServiceRegistryScope registryScope) { - final StandardServiceRegistry ssr = registryScope.getRegistry(); - final MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ).buildMetadata(); - final Database database = metadata.getDatabase(); - final Namespace databaseNamespace = database.getDefaultNamespace(); - - assertThat( databaseNamespace.getTables() ).isEmpty(); - - final TableGenerator generator = new TableGenerator(); - generator.configure( - new GeneratorCreationContextImpl( metadata, ssr ), - new Properties() - ); - generator.registerExportables( database ); - - assertThat( databaseNamespace.getTables() ).hasSize( 1 ); - - final Table table = databaseNamespace.getTables().iterator().next(); - SqlStringGenerationContext sqlStringGenerationContext = - SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ); - final String[] createCommands = new DB2Dialect().getTableExporter().getSqlCreateStrings( table, metadata, - sqlStringGenerationContext - ); - assertThat( createCommands[0] ).contains( "sequence_name varchar(255) not null" ); - } - - private static class GeneratorCreationContextImpl implements GeneratorCreationContext { - private final MetadataImplementor metadata; - private final StandardServiceRegistry ssr; - - public GeneratorCreationContextImpl(MetadataImplementor metadata, StandardServiceRegistry ssr) { - this.metadata = metadata; - this.ssr = ssr; - } - - @Override - public Database getDatabase() { - return metadata.getDatabase(); - } - - @Override - public ServiceRegistry getServiceRegistry() { - return ssr; - } - - @Override - public String getDefaultCatalog() { - return ""; - } - - @Override - public String getDefaultSchema() { - return ""; - } - - @Override - public PersistentClass getPersistentClass() { - return null; - } - - @Override - public RootClass getRootClass() { - return null; - } - - @Override - public Property getProperty() { - return null; - } - - @Override - public Type getType() { - return metadata.getDatabase() - .getTypeConfiguration() - .getBasicTypeRegistry() - .resolve( StandardBasicTypes.INTEGER ); - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemamanager/SchemaManagerResyncSequencesPooledLoTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemamanager/SchemaManagerResyncSequencesPooledLoTest.java new file mode 100644 index 000000000000..392a2db5d108 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemamanager/SchemaManagerResyncSequencesPooledLoTest.java @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.schemamanager; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.hibernate.cfg.AvailableSettings.PREFERRED_POOLED_OPTIMIZER; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SessionFactory +@DomainModel(annotatedClasses = SchemaManagerResyncSequencesPooledLoTest.EntityWithSequence.class) +@ServiceRegistry(settings = @Setting(name = PREFERRED_POOLED_OPTIMIZER, value = "pooled-lo")) +class SchemaManagerResyncSequencesPooledLoTest { + @Test void test(SessionFactoryScope scope) { + var schemaManager = scope.getSessionFactory().getSchemaManager(); + scope.inStatelessTransaction( ss -> { + ss.upsert( new EntityWithSequence(50L, "x") ); + ss.upsert( new EntityWithSequence(100L, "y") ); + ss.upsert( new EntityWithSequence(200L, "z") ); + } ); + schemaManager.resynchronizeGenerators(); + scope.inStatelessTransaction( ss -> { + var entity = new EntityWithSequence(); + ss.insert( entity ); + assertEquals(201L, entity.id); + }); + } + @Entity(name = "EntityWithSequence") + static class EntityWithSequence { + @Id + @GeneratedValue + @SequenceGenerator(name = "TheSequence", allocationSize = 20) + Long id; + String name; + + EntityWithSequence(Long id, String name) { + this.id = id; + this.name = name; + } + EntityWithSequence() { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemamanager/SchemaManagerResyncSequencesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemamanager/SchemaManagerResyncSequencesTest.java new file mode 100644 index 000000000000..e4db780d2cc6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemamanager/SchemaManagerResyncSequencesTest.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.schemamanager; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SessionFactory +@DomainModel(annotatedClasses = SchemaManagerResyncSequencesTest.EntityWithSequence.class) +class SchemaManagerResyncSequencesTest { + @Test void test(SessionFactoryScope scope) { + var schemaManager = scope.getSessionFactory().getSchemaManager(); + scope.inStatelessTransaction( ss -> { + ss.upsert( new EntityWithSequence(50L, "x") ); + ss.upsert( new EntityWithSequence(100L, "y") ); + ss.upsert( new EntityWithSequence(200L, "z") ); + } ); + schemaManager.resynchronizeGenerators(); + scope.inStatelessTransaction( ss -> { + var entity = new EntityWithSequence(); + ss.insert( entity ); + assertEquals(201L, entity.id); + }); + } + @Entity(name = "EntityWithSequence") + static class EntityWithSequence { + @Id + @GeneratedValue + @SequenceGenerator(name = "TheSequence", allocationSize = 20) + Long id; + String name; + + EntityWithSequence(Long id, String name) { + this.id = id; + this.name = name; + } + EntityWithSequence() { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemamanager/SchemaManagerResyncTablesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemamanager/SchemaManagerResyncTablesTest.java new file mode 100644 index 000000000000..8c79b8b99360 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemamanager/SchemaManagerResyncTablesTest.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.schemamanager; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.TableGenerator; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SessionFactory +@DomainModel(annotatedClasses = SchemaManagerResyncTablesTest.EntityWithTable.class) +class SchemaManagerResyncTablesTest { + @Test void test(SessionFactoryScope scope) { + var schemaManager = scope.getSessionFactory().getSchemaManager(); + scope.inStatelessTransaction( ss -> { + ss.upsert( new EntityWithTable(50L, "x") ); + ss.upsert( new EntityWithTable(100L, "y") ); + ss.upsert( new EntityWithTable(200L, "z") ); + } ); + schemaManager.resynchronizeGenerators(); + scope.inStatelessTransaction( ss -> { + var entity = new EntityWithTable(); + ss.insert( entity ); + assertEquals(201L, entity.id); + }); + } + @Entity(name = "EntityWithTable") + static class EntityWithTable { + @Id + @GeneratedValue + @TableGenerator(name = "TheTable", allocationSize = 20) + Long id; + String name; + + EntityWithTable(Long id, String name) { + this.id = id; + this.name = name; + } + EntityWithTable() { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/LobSchemaUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/LobSchemaUpdateTest.java index 3b0a42db67bd..205039d42f35 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/LobSchemaUpdateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/LobSchemaUpdateTest.java @@ -59,11 +59,11 @@ public void testUpdateIsNotExecuted() throws Exception { assertThat( fileContent ).isEmpty(); } - private void createSchema(Class... annotatedClasses) { + private void createSchema(Class... annotatedClasses) { final MetadataSources metadataSources = new MetadataSources( ssr ); - for ( Class c : annotatedClasses ) { - metadataSources.addAnnotatedClass( c ); + for ( Class annotatedClass : annotatedClasses ) { + metadataSources.addAnnotatedClass( annotatedClass ); } metadata = (MetadataImplementor) metadataSources.buildMetadata(); metadata.orderColumns( false ); @@ -74,11 +74,11 @@ private void createSchema(Class... annotatedClasses) { .create( EnumSet.of( TargetType.DATABASE ), metadata ); } - private void updateSchema(Class... annotatedClasses) { + private void updateSchema(Class... annotatedClasses) { final MetadataSources metadataSources = new MetadataSources( ssr ); - for ( Class c : annotatedClasses ) { - metadataSources.addAnnotatedClass( c ); + for ( Class annotatedClass : annotatedClasses ) { + metadataSources.addAnnotatedClass( annotatedClass ); } metadata = (MetadataImplementor) metadataSources.buildMetadata(); metadata.orderColumns( false ); @@ -90,11 +90,11 @@ private void updateSchema(Class... annotatedClasses) { .execute( EnumSet.of( TargetType.SCRIPT, TargetType.DATABASE ), metadata ); } - private void dropDatabase(Class... annotatedClasses){ + private void dropDatabase(Class... annotatedClasses){ final MetadataSources metadataSources = new MetadataSources( ssr ); - for ( Class c : annotatedClasses ) { - metadataSources.addAnnotatedClass( c ); + for ( Class annotatedClass : annotatedClasses ) { + metadataSources.addAnnotatedClass( annotatedClass ); } metadata = (MetadataImplementor) metadataSources.buildMetadata(); metadata.orderColumns( false ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/util/uuid/IdGeneratorCreationContext.java b/hibernate-testing/src/main/java/org/hibernate/testing/util/uuid/IdGeneratorCreationContext.java index 858ab6ca37cf..270b25e219a5 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/util/uuid/IdGeneratorCreationContext.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/util/uuid/IdGeneratorCreationContext.java @@ -7,9 +7,11 @@ import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.generator.GeneratorCreationContext; +import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.Value; import org.hibernate.service.ServiceRegistry; /** @@ -19,6 +21,7 @@ public class IdGeneratorCreationContext implements GeneratorCreationContext { private final ServiceRegistry serviceRegistry; private final MetadataImplementor domainModel; private final RootClass entityMapping; + private final KeyValue identifier; public IdGeneratorCreationContext( ServiceRegistry serviceRegistry, @@ -27,7 +30,7 @@ public IdGeneratorCreationContext( this.serviceRegistry = serviceRegistry; this.domainModel = domainModel; this.entityMapping = entityMapping; - + this.identifier = entityMapping.getIdentifier(); assert entityMapping.getIdentifierProperty() != null; } @@ -73,4 +76,9 @@ public PersistentClass getPersistentClass() { public Property getProperty() { return entityMapping.getIdentifierProperty(); } + + @Override + public Value getValue() { + return identifier; + } }