Skip to content

Commit fa2c678

Browse files
committed
introduce SchemaManager.resynchronizeSequences()
1 parent 6955b7a commit fa2c678

34 files changed

+593
-73
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/relational/InitCommand.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55
package org.hibernate.boot.model.relational;
66

7+
import java.util.Arrays;
8+
79
/**
810
* A general SQL command to be used while initializing a schema.
911
*
@@ -14,4 +16,15 @@ public record InitCommand(String... initCommands) {
1416
public String[] getInitCommands() {
1517
return initCommands;
1618
}
19+
20+
@Override
21+
public boolean equals(Object object) {
22+
return object instanceof InitCommand that
23+
&& Arrays.equals( this.initCommands, that.initCommands );
24+
}
25+
26+
@Override
27+
public int hashCode() {
28+
return Arrays.hashCode( initCommands );
29+
}
1730
}

hibernate-core/src/main/java/org/hibernate/cfg/MappingSettings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public interface MappingSettings {
118118
String KEYWORD_AUTO_QUOTING_ENABLED = "hibernate.auto_quote_keyword";
119119

120120
/**
121-
* When a generator specifies an increment-size and an optimizer was not explicitly
121+
* When a generator specifies an increment size and an optimizer was not explicitly
122122
* specified, which of the "pooled" optimizers should be preferred? Can specify an
123123
* optimizer short name or the name of a class which implements
124124
* {@link org.hibernate.id.enhanced.Optimizer}.

hibernate-core/src/main/java/org/hibernate/generator/GeneratorCreationContext.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.hibernate.mapping.PersistentClass;
1313
import org.hibernate.mapping.Property;
1414
import org.hibernate.mapping.RootClass;
15+
import org.hibernate.mapping.Value;
1516
import org.hibernate.service.ServiceRegistry;
1617
import org.hibernate.type.Type;
1718

@@ -64,6 +65,11 @@ public interface GeneratorCreationContext {
6465
*/
6566
Property getProperty();
6667

68+
/**
69+
* The identifier.
70+
*/
71+
Value getValue();
72+
6773
/**
6874
* Mapping details for the identifier type.
6975
*/

hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.hibernate.boot.model.relational.QualifiedName;
1010
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
1111
import org.hibernate.engine.spi.SharedSessionContractImplementor;
12+
import org.hibernate.mapping.Table;
1213

1314
/**
1415
* Encapsulates definition of the underlying data structure backing a
@@ -88,6 +89,21 @@ default void configure(Optimizer optimizer) {
8889
@Override
8990
void registerExportables(Database database);
9091

92+
/**
93+
* Register additional database objects which need to be aware of the
94+
* table for which this structure is used to generate values. Used to
95+
* deal with automatic sequence resynchronization after data import.
96+
*
97+
* @param table The table for which this structure is used to generate values
98+
* @param optimizer The {@link Optimizer} for this generator
99+
*
100+
* @see org.hibernate.relational.SchemaManager#resynchronizeSequences()
101+
*
102+
* @since 7.2
103+
*/
104+
default void registerExtraExportables(Table table, Optimizer optimizer) {
105+
}
106+
91107
/**
92108
* Initializes this structure, in particular pre-generates SQL as necessary.
93109
* <p>

hibernate-core/src/main/java/org/hibernate/id/enhanced/Optimizer.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public interface Optimizer {
5858
*
5959
* @return True if the values in the source are to be incremented
6060
* according to the defined increment size; false otherwise, in which
61-
* case the increment is totally an in memory construct.
61+
* case the increment size is a completely in-memory construct.
6262
*/
6363
boolean applyIncrementSizeToSourceValues();
6464

@@ -75,4 +75,11 @@ public interface Optimizer {
7575
* @since 7.1
7676
*/
7777
Expression createLowValueExpression(Expression databaseValue, SessionFactoryImplementor sessionFactory);
78+
79+
/**
80+
* @since 7.2
81+
*/
82+
default int getAdjustment() {
83+
return 1;
84+
}
7885
}

hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledLoOptimizer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public Serializable generate(AccessCallback callback) {
5959
generationState.lastSourceValue = callback.getNextValue();
6060
generationState.upperLimitValue = generationState.lastSourceValue.copy().add( incrementSize );
6161
generationState.value = generationState.lastSourceValue.copy();
62-
// handle cases where initial-value is less that one (hsqldb for instance).
62+
// handle cases where the initial value is less than one (hsqldb, for instance)
6363
while ( generationState.value.lt( 1 ) ) {
6464
generationState.value.increment();
6565
}

hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ public PooledOptimizer(Class<?> returnClass, int incrementSize) {
6161
public Serializable generate(AccessCallback callback) {
6262
lock.lock();
6363
try {
64-
final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() );
65-
64+
final var generationState = locateGenerationState( callback.getTenantIdentifier() );
6665
if ( generationState.hiValue == null ) {
6766
generationState.hiValue = callback.getNextValue();
6867
// unfortunately not really safe to normalize this
@@ -86,7 +85,6 @@ else if ( generationState.value.gt( generationState.hiValue ) ) {
8685
generationState.hiValue = callback.getNextValue();
8786
generationState.value = generationState.hiValue.copy().subtract( incrementSize - 1 );
8887
}
89-
9088
return generationState.value.makeValueThenIncrement();
9189
}
9290
finally {
@@ -175,4 +173,9 @@ public Expression createLowValueExpression(Expression databaseValue, SessionFact
175173
integerType
176174
);
177175
}
176+
177+
@Override
178+
public int getAdjustment() {
179+
return incrementSize;
180+
}
178181
}

hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44
*/
55
package org.hibernate.id.enhanced;
66

7+
import java.sql.Connection;
78
import java.sql.SQLException;
89

910
import org.hibernate.AssertionFailure;
11+
import org.hibernate.HibernateException;
1012
import org.hibernate.boot.model.relational.Database;
13+
import org.hibernate.boot.model.relational.InitCommand;
1114
import org.hibernate.boot.model.relational.QualifiedName;
1215
import org.hibernate.boot.model.relational.Sequence;
1316
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
17+
import org.hibernate.dialect.Dialect;
1418
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1519
import org.hibernate.id.IntegralDataTypeHolder;
20+
import org.hibernate.mapping.Table;
1621

1722
import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_LOGGER;
1823
import static org.hibernate.id.IdentifierGeneratorHelper.getIntegralDataTypeHolder;
@@ -164,6 +169,51 @@ public void initialize(SqlStringGenerationContext context) {
164169
.getSequenceNextValString( context.format( physicalSequenceName ) );
165170
}
166171

172+
@Override
173+
public void registerExtraExportables(Table table, Optimizer optimizer) {
174+
table.addResyncCommand( (context, connection) -> {
175+
final String sequenceName = context.format( physicalSequenceName );
176+
final String tableName = context.format( table.getQualifiedTableName() );
177+
final String primaryKeyColumnName = table.getPrimaryKey().getColumn( 0 ).getName();
178+
final int adjustment = optimizer.getAdjustment();
179+
final long max = getMax( connection, primaryKeyColumnName, tableName );
180+
final long current = getCurrent( connection, sequenceName, context.getDialect() );
181+
final long newValue = Math.max( max + adjustment, current );
182+
final String restart = "alter sequence " + sequenceName + " restart with " + newValue;
183+
return new InitCommand( restart );
184+
} );
185+
}
186+
187+
188+
private long getCurrent(Connection connection, String sequenceName, Dialect dialect) {
189+
final String sequenceCurrentValue =
190+
dialect.getSequenceSupport()
191+
.getSequenceNextValString( sequenceName );
192+
try ( var select = connection.prepareStatement( sequenceCurrentValue ) ) {
193+
try ( var resultSet = select.executeQuery() ) {
194+
resultSet.next();
195+
return resultSet.getLong(1);
196+
}
197+
}
198+
catch (SQLException e) {
199+
throw new HibernateException( "Could not fetch the current sequence value from the database", e );
200+
}
201+
}
202+
203+
private static long getMax(Connection connection, String primaryKeyColumnName, String tableName) {
204+
final String selectMax =
205+
"select max(" + primaryKeyColumnName + ") from " + tableName;
206+
try ( var select = connection.prepareStatement( selectMax ) ) {
207+
try ( var resultSet = select.executeQuery() ) {
208+
resultSet.next();
209+
return resultSet.getLong(1);
210+
}
211+
}
212+
catch (SQLException e) {
213+
throw new HibernateException( "Could not fetch the max primary key from the database", e );
214+
}
215+
}
216+
167217
@Override
168218
public boolean isPhysicalSequence() {
169219
return true;

hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.hibernate.id.IdentifierGenerator;
2525
import org.hibernate.id.PersistentIdentifierGenerator;
2626
import org.hibernate.id.SequenceMismatchStrategy;
27+
import org.hibernate.mapping.Table;
2728
import org.hibernate.service.ServiceRegistry;
2829
import org.hibernate.tool.schema.Action;
2930
import org.hibernate.tool.schema.extract.spi.SequenceInformation;
@@ -151,6 +152,7 @@ public class SequenceStyleGenerator
151152
private DatabaseStructure databaseStructure;
152153
private Optimizer optimizer;
153154
private Type identifierType;
155+
private Table table;
154156

155157
/**
156158
* Getter for property 'databaseStructure'.
@@ -190,6 +192,7 @@ public void configure(GeneratorCreationContext creationContext, Properties param
190192
final var dialect = jdbcEnvironment.getDialect();
191193

192194
identifierType = creationContext.getType();
195+
table = creationContext.getValue().getTable();
193196

194197
final var sequenceName = determineSequenceName( parameters, jdbcEnvironment, serviceRegistry );
195198
final int initialValue = determineInitialValue( parameters );
@@ -329,6 +332,7 @@ private boolean isSchemaToBeRecreated(String contributor, ConfigurationService c
329332
@Override
330333
public void registerExportables(Database database) {
331334
databaseStructure.registerExportables( database );
335+
databaseStructure.registerExtraExportables( table, optimizer );
332336
}
333337

334338
@Override
@@ -380,7 +384,7 @@ private static QualifiedName sequenceName(
380384

381385
/**
382386
* Determine the name of the column used to store the generator value in
383-
* the db.
387+
* the database.
384388
* <p>
385389
* Called during {@linkplain #configure configuration} <b>when resolving to a
386390
* physical table</b>.
@@ -409,8 +413,8 @@ protected int determineInitialValue(Properties params) {
409413
}
410414

411415
/**
412-
* Determine the increment size to be applied. The exact implications of
413-
* this value depends on the {@linkplain #getOptimizer() optimizer} being used.
416+
* Determine the increment size to be applied. The exact implications of
417+
* this value depend on the {@linkplain #getOptimizer() optimizer} in use.
414418
* <p>
415419
* Called during {@linkplain #configure configuration}.
416420
*
@@ -438,7 +442,7 @@ protected OptimizerDescriptor determineOptimizationStrategy(Properties params, i
438442

439443
/**
440444
* In certain cases we need to adjust the increment size based on the
441-
* selected optimizer. This is the hook to achieve that.
445+
* selected optimizer. This is the hook to achieve that.
442446
*
443447
* @param optimizationStrategy The optimizer strategy (name)
444448
* @param incrementSize The {@link #determineIncrementSize determined increment size}

hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.sql.SQLException;
1111

1212
import org.hibernate.AssertionFailure;
13+
import org.hibernate.HibernateException;
1314
import org.hibernate.LockOptions;
1415
import org.hibernate.boot.model.naming.Identifier;
1516
import org.hibernate.boot.model.relational.Database;
@@ -333,4 +334,53 @@ public void initialize(SqlStringGenerationContext context) {
333334
" set " + valueColumnNameText + "= ?" +
334335
" where " + valueColumnNameText + "=?";
335336
}
337+
338+
@Override
339+
public void registerExtraExportables(Table table, Optimizer optimizer) {
340+
table.addResyncCommand( (context, connection) -> {
341+
final String sequenceTableName = context.format( physicalTableName );
342+
final String tableName = context.format( table.getQualifiedTableName() );
343+
final String primaryKeyColumnName = table.getPrimaryKey().getColumn( 0 ).getName();
344+
final int adjustment = optimizer.getAdjustment();
345+
final long max = getMax( connection, primaryKeyColumnName, tableName );
346+
final long current = getCurrent( connection, sequenceTableName );
347+
if ( max + adjustment > current ) {
348+
final String update =
349+
"update " + sequenceTableName
350+
+ " set " + valueColumnNameText + " = " + (max + adjustment);
351+
return new InitCommand( update );
352+
}
353+
else {
354+
return new InitCommand();
355+
}
356+
} );
357+
}
358+
359+
private long getCurrent(Connection connection, String sequenceTableName) {
360+
final String selectCurrent =
361+
"select " + valueColumnNameText + " from " + sequenceTableName;
362+
try ( var select = connection.prepareStatement( selectCurrent ) ) {
363+
try ( var resultSet = select.executeQuery() ) {
364+
resultSet.next();
365+
return resultSet.getLong(1);
366+
}
367+
}
368+
catch (SQLException e) {
369+
throw new HibernateException( "Could not fetch the current sequence value from the database", e );
370+
}
371+
}
372+
373+
private static long getMax(Connection connection, String primaryKeyColumnName, String tableName) {
374+
final String selectMax =
375+
"select max(" + primaryKeyColumnName + ") from " + tableName;
376+
try ( var select = connection.prepareStatement( selectMax ) ) {
377+
try ( var resultSet = select.executeQuery() ) {
378+
resultSet.next();
379+
return resultSet.getLong(1);
380+
}
381+
}
382+
catch (SQLException e) {
383+
throw new HibernateException( "Could not fetch the max primary key from the database", e );
384+
}
385+
}
336386
}

0 commit comments

Comments
 (0)