Skip to content

Commit 5ec16b0

Browse files
gavinkingsebersole
authored andcommitted
HHH-17751 allow @CurrentTimestamp @Version with force-increment locking
This is not the best implementation, but it reproduces the capabilities of the deprecated generator type @source(DB).
1 parent 33665af commit 5ec16b0

File tree

8 files changed

+186
-130
lines changed

8 files changed

+186
-130
lines changed

hibernate-core/src/main/java/org/hibernate/annotations/CurrentTimestamp.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import static java.lang.annotation.ElementType.FIELD;
1515
import static java.lang.annotation.ElementType.METHOD;
1616
import static java.lang.annotation.RetentionPolicy.RUNTIME;
17+
import static org.hibernate.generator.EventType.FORCE_INCREMENT;
1718
import static org.hibernate.generator.EventType.INSERT;
1819
import static org.hibernate.generator.EventType.UPDATE;
1920

@@ -75,7 +76,7 @@
7576
* If it should be generated just once, on the initial SQL {@code insert},
7677
* explicitly specify {@link EventType#INSERT event = INSERT}.
7778
*/
78-
EventType[] event() default {INSERT, UPDATE};
79+
EventType[] event() default {INSERT, UPDATE, FORCE_INCREMENT};
7980

8081
/**
8182
* Specifies how the timestamp is generated. By default, it is generated

hibernate-core/src/main/java/org/hibernate/engine/internal/Nullability.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ else if ( propertyType instanceof ComponentType componentType ) {
171171
else if ( propertyType instanceof CollectionType collectionType ) {
172172
// persistent collections may have components
173173
if ( collectionType.getElementType( session.getFactory() ) instanceof CompositeType componentType ) {
174-
// check for all components values in the collection
174+
// check for all component's values in the collection
175175
final Iterator<?> iterator = getLoadedElementsIterator( collectionType, value );
176176
while ( iterator.hasNext() ) {
177177
final Object compositeElement = iterator.next();

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55
package org.hibernate.generator;
66

7+
import org.hibernate.Incubating;
8+
79
/**
810
* Enumerates event types that can result in generation of a new value.
911
* A {@link Generator} must specify which events it responds to, by
@@ -35,5 +37,12 @@ public enum EventType {
3537
* This indicates, for example, that a version number should be
3638
* incremented.
3739
*/
38-
UPDATE
40+
UPDATE,
41+
/**
42+
* An event that occurs during verification of a lock of type
43+
* of {@link org.hibernate.LockMode#OPTIMISTIC_FORCE_INCREMENT}
44+
* or {@link org.hibernate.LockMode#PESSIMISTIC_FORCE_INCREMENT}.
45+
*/
46+
@Incubating
47+
FORCE_INCREMENT
3948
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
*/
55
package org.hibernate.generator;
66

7+
import org.hibernate.Incubating;
78
import org.hibernate.engine.spi.SharedSessionContractImplementor;
89

910
import java.io.Serializable;
1011
import java.util.EnumSet;
1112

13+
import static org.hibernate.generator.EventType.FORCE_INCREMENT;
1214
import static org.hibernate.generator.EventType.INSERT;
1315
import static org.hibernate.generator.EventType.UPDATE;
1416

@@ -183,7 +185,8 @@ default boolean allowMutation() {
183185
}
184186

185187
default boolean generatesSometimes() {
186-
return !getEventTypes().isEmpty();
188+
return generatesOnInsert()
189+
|| generatesOnUpdate();
187190
}
188191

189192
default boolean generatesOnInsert() {
@@ -193,4 +196,9 @@ default boolean generatesOnInsert() {
193196
default boolean generatesOnUpdate() {
194197
return getEventTypes().contains(UPDATE);
195198
}
199+
200+
@Incubating
201+
default boolean generatesOnForceIncrement() {
202+
return getEventTypes().contains(FORCE_INCREMENT);
203+
}
196204
}

hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java

Lines changed: 106 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
package org.hibernate.generator.internal;
66

77
import java.lang.reflect.Member;
8+
import java.sql.CallableStatement;
9+
import java.sql.PreparedStatement;
10+
import java.sql.ResultSet;
11+
import java.sql.SQLException;
812
import java.sql.Time;
913
import java.sql.Timestamp;
1014
import java.time.Clock;
@@ -26,7 +30,6 @@
2630
import java.util.concurrent.ConcurrentHashMap;
2731
import java.util.function.BiFunction;
2832

29-
import org.hibernate.AssertionFailure;
3033
import org.hibernate.SessionFactory;
3134
import org.hibernate.annotations.CreationTimestamp;
3235
import org.hibernate.annotations.CurrentTimestamp;
@@ -35,6 +38,8 @@
3538
import org.hibernate.dialect.Dialect;
3639
import org.hibernate.engine.config.spi.ConfigurationService;
3740
import org.hibernate.engine.jdbc.Size;
41+
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
42+
import org.hibernate.engine.jdbc.spi.StatementPreparer;
3843
import org.hibernate.engine.spi.SharedSessionContractImplementor;
3944
import org.hibernate.generator.BeforeExecutionGenerator;
4045
import org.hibernate.generator.EventType;
@@ -45,7 +50,10 @@
4550
import org.hibernate.type.descriptor.java.ClockHelper;
4651

4752
import org.checkerframework.checker.nullness.qual.Nullable;
53+
import org.hibernate.type.descriptor.java.JavaType;
4854

55+
import static java.sql.Types.TIMESTAMP;
56+
import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_MESSAGE_LOGGER;
4957
import static org.hibernate.generator.EventTypeSets.INSERT_AND_UPDATE;
5058
import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;
5159
import static org.hibernate.generator.EventTypeSets.fromArray;
@@ -80,6 +88,8 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
8088

8189
private final EnumSet<EventType> eventTypes;
8290

91+
private final JavaType<Object> propertyType;
92+
8393
private final CurrentTimestampGeneratorDelegate delegate;
8494
private static final Map<Class<?>, BiFunction<@Nullable Clock, Integer, CurrentTimestampGeneratorDelegate>> GENERATOR_PRODUCERS = new HashMap<>();
8595
private static final Map<Key, CurrentTimestampGeneratorDelegate> GENERATOR_DELEGATES = new ConcurrentHashMap<>();
@@ -182,19 +192,27 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
182192
);
183193
}
184194

195+
private static JavaType<Object> getPropertyType(GeneratorCreationContext context) {
196+
return context.getDatabase().getTypeConfiguration().getJavaTypeRegistry()
197+
.getDescriptor( context.getProperty().getType().getReturnedClass() );
198+
}
199+
185200
public CurrentTimestampGeneration(CurrentTimestamp annotation, Member member, GeneratorCreationContext context) {
186201
delegate = getGeneratorDelegate( annotation.source(), member, context );
187202
eventTypes = fromArray( annotation.event() );
203+
propertyType = getPropertyType( context );
188204
}
189205

190206
public CurrentTimestampGeneration(CreationTimestamp annotation, Member member, GeneratorCreationContext context) {
191207
delegate = getGeneratorDelegate( annotation.source(), member, context );
192208
eventTypes = INSERT_ONLY;
209+
propertyType = getPropertyType( context );
193210
}
194211

195212
public CurrentTimestampGeneration(UpdateTimestamp annotation, Member member, GeneratorCreationContext context) {
196213
delegate = getGeneratorDelegate( annotation.source(), member, context );
197214
eventTypes = INSERT_AND_UPDATE;
215+
propertyType = getPropertyType( context );
198216
}
199217

200218
private static CurrentTimestampGeneratorDelegate getGeneratorDelegate(
@@ -208,36 +226,42 @@ static CurrentTimestampGeneratorDelegate getGeneratorDelegate(
208226
SourceType source,
209227
Class<?> propertyType,
210228
GeneratorCreationContext context) {
211-
switch (source) {
212-
case VM:
229+
return switch (source) {
230+
case DB -> null;
231+
case VM -> {
213232
// Generator is only used for in-VM generation
214-
final BasicValue basicValue = (BasicValue) context.getProperty().getValue();
215-
final Size size = basicValue.getColumns().get( 0 ).getColumnSize(
216-
context.getDatabase().getDialect(),
217-
basicValue.getMetadata()
218-
);
219-
final Clock baseClock =
220-
context.getServiceRegistry().requireService( ConfigurationService.class )
221-
.getSetting( CLOCK_SETTING_NAME, value -> (Clock) value );
222-
final Key key = new Key( propertyType, baseClock, size.getPrecision() == null ? 0 : size.getPrecision() );
223-
final CurrentTimestampGeneratorDelegate delegate = GENERATOR_DELEGATES.get( key );
233+
final Key key = new Key( propertyType, getBaseClock( context ), getPrecision( context ) );
234+
final var delegate = GENERATOR_DELEGATES.get( key );
224235
if ( delegate != null ) {
225-
return delegate;
226-
}
227-
final var producer = GENERATOR_PRODUCERS.get( key.clazz );
228-
if ( producer == null ) {
229-
return null;
236+
yield delegate;
230237
}
231238
else {
232-
final var generatorDelegate = producer.apply( key.clock, key.precision );
233-
final var old = GENERATOR_DELEGATES.putIfAbsent( key, generatorDelegate );
234-
return old != null ? old : generatorDelegate;
239+
final var producer = GENERATOR_PRODUCERS.get( key.clazz );
240+
if ( producer == null ) {
241+
yield null;
242+
}
243+
else {
244+
final var generatorDelegate = producer.apply( key.clock, key.precision );
245+
final var old = GENERATOR_DELEGATES.putIfAbsent( key, generatorDelegate );
246+
yield old != null ? old : generatorDelegate;
247+
}
235248
}
236-
case DB:
237-
return null;
238-
default:
239-
throw new AssertionFailure("unknown source");
240-
}
249+
}
250+
};
251+
}
252+
253+
private static int getPrecision(GeneratorCreationContext context) {
254+
final BasicValue basicValue = (BasicValue) context.getProperty().getValue();
255+
final Size size =
256+
basicValue.getColumns().get( 0 )
257+
.getColumnSize( context.getDatabase().getDialect(),
258+
basicValue.getMetadata() );
259+
return size.getPrecision() == null ? 0 : size.getPrecision();
260+
}
261+
262+
private static Clock getBaseClock(GeneratorCreationContext context) {
263+
return context.getServiceRegistry().requireService( ConfigurationService.class )
264+
.getSetting( CLOCK_SETTING_NAME, value -> (Clock) value );
241265
}
242266

243267
public static <T extends Clock> T getClock(SessionFactory sessionFactory) {
@@ -256,7 +280,15 @@ public EnumSet<EventType> getEventTypes() {
256280

257281
@Override
258282
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
259-
return delegate.generate();
283+
if ( delegate == null ) {
284+
if ( eventType != EventType.FORCE_INCREMENT ) {
285+
throw new UnsupportedOperationException( "CurrentTimestampGeneration.generate() should not have been called" );
286+
}
287+
return propertyType.wrap( getCurrentTimestamp( session ), session );
288+
}
289+
else {
290+
return delegate.generate();
291+
}
260292
}
261293

262294
@Override
@@ -281,4 +313,51 @@ interface CurrentTimestampGeneratorDelegate {
281313

282314
private record Key(Class<?> clazz, @Nullable Clock clock, int precision) {
283315
}
316+
317+
static Timestamp getCurrentTimestamp(SharedSessionContractImplementor session) {
318+
final Dialect dialect = session.getJdbcServices().getJdbcEnvironment().getDialect();
319+
final boolean callable = dialect.isCurrentTimestampSelectStringCallable();
320+
final String timestampSelectString = dialect.getCurrentTimestampSelectString();
321+
final JdbcCoordinator coordinator = session.getJdbcCoordinator();
322+
final StatementPreparer statementPreparer = coordinator.getStatementPreparer();
323+
PreparedStatement statement = null;
324+
try {
325+
statement = statementPreparer.prepareStatement( timestampSelectString, callable );
326+
final Timestamp ts = callable
327+
? extractCalledResult( statement, coordinator, timestampSelectString )
328+
: extractResult( statement, coordinator, timestampSelectString );
329+
if ( JDBC_MESSAGE_LOGGER.isTraceEnabled() ) {
330+
JDBC_MESSAGE_LOGGER.currentTimestampRetrievedFromDatabase( ts, ts.getNanos(), ts.getTime() );
331+
}
332+
return ts;
333+
}
334+
catch (SQLException e) {
335+
throw session.getJdbcServices().getSqlExceptionHelper().convert(
336+
e,
337+
"could not obtain current timestamp from database",
338+
timestampSelectString
339+
);
340+
}
341+
finally {
342+
if ( statement != null ) {
343+
coordinator.getLogicalConnection().getResourceRegistry().release( statement );
344+
coordinator.afterStatementExecution();
345+
}
346+
}
347+
}
348+
349+
static Timestamp extractResult(PreparedStatement statement, JdbcCoordinator coordinator, String sql)
350+
throws SQLException {
351+
final ResultSet resultSet = coordinator.getResultSetReturn().extract( statement, sql );
352+
resultSet.next();
353+
return resultSet.getTimestamp( 1 );
354+
}
355+
356+
static Timestamp extractCalledResult(PreparedStatement statement, JdbcCoordinator coordinator, String sql)
357+
throws SQLException {
358+
final CallableStatement callable = (CallableStatement) statement;
359+
callable.registerOutParameter( 1, TIMESTAMP );
360+
coordinator.getResultSetReturn().execute( callable, sql );
361+
return callable.getTimestamp( 1 );
362+
}
284363
}

hibernate-core/src/main/java/org/hibernate/generator/internal/SourceGeneration.java

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import org.hibernate.annotations.Source;
99
import org.hibernate.annotations.SourceType;
1010
import org.hibernate.dialect.Dialect;
11-
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
12-
import org.hibernate.engine.jdbc.spi.StatementPreparer;
1311
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1412
import org.hibernate.generator.EventType;
1513
import org.hibernate.generator.EventTypeSets;
@@ -19,16 +17,9 @@
1917

2018

2119
import java.lang.reflect.Member;
22-
import java.sql.CallableStatement;
23-
import java.sql.PreparedStatement;
24-
import java.sql.ResultSet;
25-
import java.sql.SQLException;
26-
import java.sql.Timestamp;
2720
import java.util.EnumSet;
2821

29-
import static java.sql.Types.TIMESTAMP;
30-
import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_MESSAGE_LOGGER;
31-
import static org.hibernate.generator.EventTypeSets.INSERT_AND_UPDATE;
22+
import static org.hibernate.generator.internal.CurrentTimestampGeneration.getCurrentTimestamp;
3223

3324
/**
3425
* Value generation strategy using the query {@link Dialect#getCurrentTimestampSelectString()}.
@@ -63,11 +54,11 @@ public SourceGeneration(SourceType sourceType, Class<?> propertyType, GeneratorC
6354
}
6455

6556
/**
66-
* @return {@link EventTypeSets#INSERT_ONLY}
57+
* @return {@link EventTypeSets#ALL}
6758
*/
6859
@Override
6960
public EnumSet<EventType> getEventTypes() {
70-
return INSERT_AND_UPDATE;
61+
return EventTypeSets.ALL;
7162
}
7263

7364
@Override
@@ -76,52 +67,4 @@ public Object generate(SharedSessionContractImplementor session, Object owner, O
7667
? propertyType.wrap( getCurrentTimestamp( session ), session )
7768
: valueGenerator.generate();
7869
}
79-
80-
private Timestamp getCurrentTimestamp(SharedSessionContractImplementor session) {
81-
final Dialect dialect = session.getJdbcServices().getJdbcEnvironment().getDialect();
82-
final boolean callable = dialect.isCurrentTimestampSelectStringCallable();
83-
final String timestampSelectString = dialect.getCurrentTimestampSelectString();
84-
final JdbcCoordinator coordinator = session.getJdbcCoordinator();
85-
final StatementPreparer statementPreparer = coordinator.getStatementPreparer();
86-
PreparedStatement statement = null;
87-
try {
88-
statement = statementPreparer.prepareStatement( timestampSelectString, callable );
89-
final Timestamp ts = callable
90-
? extractCalledResult( statement, coordinator, timestampSelectString )
91-
: extractResult( statement, coordinator, timestampSelectString );
92-
if ( JDBC_MESSAGE_LOGGER.isTraceEnabled() ) {
93-
JDBC_MESSAGE_LOGGER.currentTimestampRetrievedFromDatabase( ts, ts.getNanos(), ts.getTime() );
94-
}
95-
return ts;
96-
}
97-
catch (SQLException e) {
98-
throw session.getJdbcServices().getSqlExceptionHelper().convert(
99-
e,
100-
"could not obtain current timestamp from database",
101-
timestampSelectString
102-
);
103-
}
104-
finally {
105-
if ( statement != null ) {
106-
coordinator.getLogicalConnection().getResourceRegistry().release( statement );
107-
coordinator.afterStatementExecution();
108-
}
109-
}
110-
}
111-
112-
private static Timestamp extractResult(PreparedStatement statement, JdbcCoordinator coordinator, String sql)
113-
throws SQLException {
114-
final ResultSet resultSet = coordinator.getResultSetReturn().extract( statement, sql );
115-
resultSet.next();
116-
return resultSet.getTimestamp( 1 );
117-
}
118-
119-
private static Timestamp extractCalledResult(PreparedStatement statement, JdbcCoordinator coordinator, String sql)
120-
throws SQLException {
121-
final CallableStatement callable = (CallableStatement) statement;
122-
callable.registerOutParameter( 1, TIMESTAMP );
123-
coordinator.getResultSetReturn().execute( callable, sql );
124-
return callable.getTimestamp( 1 );
125-
}
126-
12770
}

0 commit comments

Comments
 (0)