Skip to content

Commit e9a1ffd

Browse files
committed
proof of concept for SchemaManager.resynchronizeSequences()
1 parent 881a09f commit e9a1ffd

27 files changed

+432
-45
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/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 database The database instance
99+
*
100+
* @see org.hibernate.relational.SchemaManager#resynchronizeSequences()
101+
*
102+
* @since 7.2
103+
*/
104+
default void registerExtraExportables(Table table, Database database) {
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/SequenceStructure.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88

99
import org.hibernate.AssertionFailure;
1010
import org.hibernate.boot.model.relational.Database;
11+
import org.hibernate.boot.model.relational.InitCommand;
1112
import org.hibernate.boot.model.relational.QualifiedName;
1213
import org.hibernate.boot.model.relational.Sequence;
1314
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
1415
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1516
import org.hibernate.id.IntegralDataTypeHolder;
17+
import org.hibernate.mapping.Table;
1618

1719
import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_LOGGER;
1820
import static org.hibernate.id.IdentifierGeneratorHelper.getIntegralDataTypeHolder;
@@ -164,6 +166,20 @@ public void initialize(SqlStringGenerationContext context) {
164166
.getSequenceNextValString( context.format( physicalSequenceName ) );
165167
}
166168

169+
@Override
170+
public void registerExtraExportables(Table table, Database database) {
171+
table.addResyncCommand( context -> {
172+
final String sequenceName = context.format( physicalSequenceName );
173+
final String tableName = context.format( table.getQualifiedTableName() );
174+
final String primaryKeyColumnName = table.getPrimaryKey().getColumn( 0 ).getName();
175+
final String sequenceCurrentValue =
176+
database.getDialect().getSequenceSupport()
177+
.getSelectSequenceNextValString( sequenceName );
178+
return new InitCommand( "alter sequence %s restart with select greatest(max(%s)+%d,%s) from %s"
179+
.formatted( sequenceName, primaryKeyColumnName, incrementSize, sequenceCurrentValue, tableName ) );
180+
} );
181+
}
182+
167183
@Override
168184
public boolean isPhysicalSequence() {
169185
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, database );
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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,16 @@ public void initialize(SqlStringGenerationContext context) {
333333
" set " + valueColumnNameText + "= ?" +
334334
" where " + valueColumnNameText + "=?";
335335
}
336+
337+
@Override
338+
public void registerExtraExportables(Table table, Database database) {
339+
table.addResyncCommand( context -> {
340+
final String sequenceTableName = context.format( physicalTableName );
341+
final String tableName = context.format( table.getQualifiedTableName() );
342+
final String primaryKeyColumnName = table.getPrimaryKey().getColumn( 0 ).getName();
343+
return new InitCommand( "update %s set %s = select greatest(max(%s)+%d,(select %s from %s)) from %s"
344+
.formatted( sequenceTableName, valueColumnNameText, primaryKeyColumnName,
345+
incrementSize, valueColumnNameText, sequenceTableName, tableName ) );
346+
} );
347+
}
336348
}

hibernate-core/src/main/java/org/hibernate/mapping/Property.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,11 @@ public Property getProperty() {
602602
return Property.this;
603603
}
604604

605+
@Override
606+
public Value getValue() {
607+
return value;
608+
}
609+
605610
@Override
606611
public SqlStringGenerationContext getSqlStringGenerationContext() {
607612
return context.getSqlStringGenerationContext();

hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ public Generator createGenerator(
422422
Property property,
423423
GeneratorSettings defaults) {
424424
if ( customIdGeneratorCreator != null ) {
425-
final var context = new IdGeneratorCreationContext( rootClass, property, defaults );
425+
final var context = new IdGeneratorCreationContext( this, rootClass, property, defaults );
426426
final var generator = customIdGeneratorCreator.createGenerator( context );
427427
if ( generator.allowAssignedIdentifiers() && nullValue == null ) {
428428
setNullValueUndefined();
@@ -1074,11 +1074,13 @@ public Long[] getColumnLengths() {
10741074
}
10751075

10761076
private class IdGeneratorCreationContext implements GeneratorCreationContext {
1077+
private final SimpleValue identifier;
10771078
private final RootClass rootClass;
10781079
private final Property property;
10791080
private final GeneratorSettings defaults;
10801081

1081-
public IdGeneratorCreationContext(RootClass rootClass, Property property, GeneratorSettings defaults) {
1082+
public IdGeneratorCreationContext(SimpleValue identifier, RootClass rootClass, Property property, GeneratorSettings defaults) {
1083+
this.identifier = identifier;
10821084
this.rootClass = rootClass;
10831085
this.property = property;
10841086
this.defaults = defaults;
@@ -1124,6 +1126,11 @@ public Property getProperty() {
11241126
return property;
11251127
}
11261128

1129+
@Override
1130+
public Value getValue() {
1131+
return identifier;
1132+
}
1133+
11271134
@Override
11281135
public Type getType() {
11291136
return SimpleValue.this.getType();

hibernate-core/src/main/java/org/hibernate/mapping/Table.java

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public class Table implements Serializable, ContributableDatabaseObject {
7272
private String options;
7373

7474
private List<Function<SqlStringGenerationContext, InitCommand>> initCommandProducers;
75+
private List<Function<SqlStringGenerationContext, InitCommand>> resyncCommandProducers;
7576

7677
@Deprecated(since="6.2", forRemoval = true)
7778
public Table() {
@@ -808,16 +809,28 @@ public void addInitCommand(Function<SqlStringGenerationContext, InitCommand> com
808809
}
809810

810811
public List<InitCommand> getInitCommands(SqlStringGenerationContext context) {
811-
if ( initCommandProducers == null ) {
812-
return emptyList();
813-
}
814-
else {
815-
final List<InitCommand> initCommands = new ArrayList<>();
816-
for ( Function<SqlStringGenerationContext, InitCommand> producer : initCommandProducers ) {
817-
initCommands.add( producer.apply( context ) );
818-
}
819-
return unmodifiableList( initCommands );
812+
return initCommandProducers == null
813+
? emptyList()
814+
: initCommandProducers.stream()
815+
.map( producer -> producer.apply( context ) )
816+
.toList();
817+
}
818+
819+
public void addResyncCommand(Function<SqlStringGenerationContext, InitCommand> commandProducer) {
820+
if ( resyncCommandProducers == null ) {
821+
resyncCommandProducers = new ArrayList<>();
820822
}
823+
resyncCommandProducers.add( commandProducer );
824+
}
825+
826+
public List<InitCommand> getResyncCommands(SqlStringGenerationContext context) {
827+
return resyncCommandProducers == null
828+
? emptyList()
829+
: resyncCommandProducers.stream()
830+
.map( producer -> producer.apply( context ) )
831+
.distinct()
832+
.toList();
833+
821834
}
822835

823836
public String getOptions() {

hibernate-core/src/main/java/org/hibernate/relational/SchemaManager.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
* {@link jakarta.persistence.SchemaManager}, which it now inherits,
2020
* with a minor change to the naming of its operations. It is retained
2121
* for backward compatibility and as a place to define additional
22-
* functionality such as {@link #populate} and {@link #forSchema}.
22+
* operations like {@link #populate}, {@link #resynchronizeSequences},
23+
* and {@link #forSchema}.
2324
*
2425
* @since 6.2
2526
* @author Gavin King
@@ -95,6 +96,22 @@ public interface SchemaManager extends jakarta.persistence.SchemaManager {
9596
@Incubating
9697
void populate();
9798

99+
/**
100+
* Resynchronize sequences after importing test data.
101+
* <p>
102+
* When data is imported to the database without the use of a Hibernate session,
103+
* a database sequence might become stale with respect to the data in the table for
104+
* which it is used to generate unique keys. This operation restarts every sequence
105+
* so that the next generated unique key will be larger than the largest key
106+
* currently in use.
107+
* <p>
108+
* Programmatic way to run {@link org.hibernate.tool.schema.spi.SequenceSynchronizer}.
109+
*
110+
* @since 7.2
111+
*/
112+
@Incubating
113+
void resynchronizeSequences();
114+
98115
/**
99116
* Obtain an instance which targets the given schema.
100117
* <p>

0 commit comments

Comments
 (0)