Skip to content

Commit 5b2a87c

Browse files
committed
HHH-18395 Fix intermittent failures of clock based tests by using custom clock
1 parent a17b241 commit 5b2a87c

14 files changed

+349
-165
lines changed

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

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
import java.util.function.Supplier;
1010

1111
import org.hibernate.MappingException;
12-
import org.hibernate.boot.spi.MetadataBuildingContext;
12+
import org.hibernate.SharedSessionContract;
1313
import org.hibernate.engine.jdbc.spi.JdbcServices;
1414
import org.hibernate.engine.spi.IdentifierValue;
15+
import org.hibernate.engine.spi.SessionFactoryImplementor;
1516
import org.hibernate.engine.spi.SharedSessionDelegatorBaseImpl;
1617
import org.hibernate.engine.spi.VersionValue;
1718
import org.hibernate.mapping.KeyValue;
@@ -85,7 +86,8 @@ public static <T> VersionValue getUnsavedVersionValue(
8586
Integer precision,
8687
Integer scale,
8788
Getter getter,
88-
Supplier<?> templateInstanceAccess) {
89+
Supplier<?> templateInstanceAccess,
90+
SessionFactoryImplementor sessionFactory) {
8991
final String unsavedValue = bootVersionMapping.getNullValue();
9092
if ( unsavedValue == null ) {
9193
if ( getter != null && templateInstanceAccess != null ) {
@@ -95,8 +97,7 @@ public static <T> VersionValue getUnsavedVersionValue(
9597

9698
// if the version of a newly instantiated object is not the same
9799
// as the version seed value, use that as the unsaved-value
98-
final T seedValue = jtd.seed( length, precision, scale,
99-
mockSession( bootVersionMapping.getBuildingContext() ) );
100+
final T seedValue = jtd.seed( length, precision, scale, mockSession( sessionFactory ) );
100101
return jtd.areEqual( seedValue, defaultValue )
101102
? VersionValue.UNDEFINED
102103
: new VersionValue( defaultValue );
@@ -121,12 +122,27 @@ public static <T> VersionValue getUnsavedVersionValue(
121122

122123
}
123124

124-
private static SharedSessionDelegatorBaseImpl mockSession(MetadataBuildingContext context) {
125+
private static SharedSessionDelegatorBaseImpl mockSession(SessionFactoryImplementor sessionFactory) {
125126
return new SharedSessionDelegatorBaseImpl(null) {
127+
128+
@Override
129+
protected SharedSessionContract delegate() {
130+
throw new UnsupportedOperationException( "Operation not supported" );
131+
}
132+
133+
@Override
134+
public SessionFactoryImplementor getFactory() {
135+
return sessionFactory;
136+
}
137+
138+
@Override
139+
public SessionFactoryImplementor getSessionFactory() {
140+
return sessionFactory;
141+
}
142+
126143
@Override
127144
public JdbcServices getJdbcServices() {
128-
return context.getBootstrapContext().getServiceRegistry()
129-
.requireService( JdbcServices.class );
145+
return sessionFactory.getJdbcServices();
130146
}
131147
};
132148
}

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

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
package org.hibernate.generator.internal;
88

99
import org.hibernate.AssertionFailure;
10+
import org.hibernate.SessionFactory;
1011
import org.hibernate.annotations.CreationTimestamp;
1112
import org.hibernate.annotations.CurrentTimestamp;
1213
import org.hibernate.annotations.SourceType;
1314
import org.hibernate.annotations.UpdateTimestamp;
1415
import org.hibernate.dialect.Dialect;
16+
import org.hibernate.engine.config.spi.ConfigurationService;
1517
import org.hibernate.engine.jdbc.Size;
1618
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1719
import org.hibernate.generator.EventType;
@@ -36,15 +38,16 @@
3638
import java.time.OffsetTime;
3739
import java.time.Year;
3840
import java.time.YearMonth;
39-
import java.time.ZoneId;
4041
import java.time.ZonedDateTime;
4142
import java.util.Calendar;
4243
import java.util.Date;
4344
import java.util.EnumSet;
4445
import java.util.HashMap;
4546
import java.util.Map;
4647
import java.util.concurrent.ConcurrentHashMap;
47-
import java.util.function.IntFunction;
48+
import java.util.function.BiFunction;
49+
50+
import org.checkerframework.checker.nullness.qual.Nullable;
4851

4952
import static org.hibernate.generator.EventTypeSets.INSERT_AND_UPDATE;
5053
import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;
@@ -68,24 +71,34 @@
6871
* @author Gavin King
6972
*/
7073
public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnExecutionGenerator {
74+
75+
/**
76+
* Configuration property name to set a custom {@link Clock} for Hibernate ORM to use when generating VM based
77+
* timestamp values for e.g. {@link CurrentTimestamp}, {@link CreationTimestamp}, {@link UpdateTimestamp}
78+
* and {@link org.hibernate.type.descriptor.java.VersionJavaType} methods.
79+
*
80+
* @since 6.6
81+
*/
82+
public static final String CLOCK_SETTING_NAME = "hibernate.testing.clock";
83+
7184
private final EnumSet<EventType> eventTypes;
7285

7386
private final CurrentTimestampGeneratorDelegate delegate;
74-
private static final Map<Class<?>, IntFunction<CurrentTimestampGeneratorDelegate>> GENERATOR_PRODUCERS = new HashMap<>();
87+
private static final Map<Class<?>, BiFunction<@Nullable Clock, Integer, CurrentTimestampGeneratorDelegate>> GENERATOR_PRODUCERS = new HashMap<>();
7588
private static final Map<Key, CurrentTimestampGeneratorDelegate> GENERATOR_DELEGATES = new ConcurrentHashMap<>();
7689

7790
static {
7891
GENERATOR_PRODUCERS.put(
7992
Date.class,
80-
precision -> {
81-
final Clock clock = ClockHelper.forPrecision( precision, 3 );
93+
(baseClock, precision) -> {
94+
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 3 );
8295
return () -> new Date( clock.millis() );
8396
}
8497
);
8598
GENERATOR_PRODUCERS.put(
8699
Calendar.class,
87-
precision -> {
88-
final Clock clock = ClockHelper.forPrecision( precision, 3 );
100+
(baseClock, precision) -> {
101+
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 3 );
89102
return () -> {
90103
Calendar calendar = Calendar.getInstance();
91104
calendar.setTimeInMillis( clock.millis() );
@@ -95,78 +108,78 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
95108
);
96109
GENERATOR_PRODUCERS.put(
97110
java.sql.Date.class,
98-
precision -> () -> new java.sql.Date( System.currentTimeMillis() )
111+
(baseClock, precision) -> () -> new java.sql.Date( baseClock == null ? System.currentTimeMillis() : baseClock.millis() )
99112
);
100113

101114
GENERATOR_PRODUCERS.put(
102115
Time.class,
103-
precision -> {
104-
final Clock clock = ClockHelper.forPrecision( precision, 3 );
116+
(baseClock, precision) -> {
117+
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 3 );
105118
return () -> new Time( clock.millis() );
106119
}
107120
);
108121
GENERATOR_PRODUCERS.put(
109122
Timestamp.class,
110-
precision -> {
111-
final Clock clock = ClockHelper.forPrecision( precision, 9 );
123+
(baseClock, precision) -> {
124+
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
112125
return () -> Timestamp.from( clock.instant() );
113126
}
114127
);
115128
GENERATOR_PRODUCERS.put(
116129
Instant.class,
117-
precision -> {
118-
final Clock clock = ClockHelper.forPrecision( precision, 9 );
130+
(baseClock, precision) -> {
131+
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
119132
return clock::instant;
120133
}
121134
);
122135
GENERATOR_PRODUCERS.put(
123136
LocalDate.class,
124-
precision -> LocalDate::now
137+
(baseClock, precision) -> () -> LocalDate.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
125138
);
126139
GENERATOR_PRODUCERS.put(
127140
LocalDateTime.class,
128-
precision -> {
129-
final Clock clock = ClockHelper.forPrecision( precision, 9 );
141+
(baseClock, precision) -> {
142+
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
130143
return () -> LocalDateTime.now( clock );
131144
}
132145
);
133146
GENERATOR_PRODUCERS.put(
134147
LocalTime.class,
135-
precision -> {
136-
final Clock clock = ClockHelper.forPrecision( precision, 9 );
148+
(baseClock, precision) -> {
149+
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
137150
return () -> LocalTime.now( clock );
138151
}
139152
);
140153
GENERATOR_PRODUCERS.put(
141154
MonthDay.class,
142-
precision -> MonthDay::now
155+
(baseClock, precision) -> () -> MonthDay.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
143156
);
144157
GENERATOR_PRODUCERS.put(
145158
OffsetDateTime.class,
146-
precision -> {
147-
final Clock clock = ClockHelper.forPrecision( precision, 9 );
159+
(baseClock, precision) -> {
160+
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
148161
return () -> OffsetDateTime.now( clock );
149162
}
150163
);
151164
GENERATOR_PRODUCERS.put(
152165
OffsetTime.class,
153-
precision -> {
154-
final Clock clock = ClockHelper.forPrecision( precision, 9 );
166+
(baseClock, precision) -> {
167+
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
155168
return () -> OffsetTime.now( clock );
156169
}
157170
);
158171
GENERATOR_PRODUCERS.put(
159172
Year.class,
160-
precision -> Year::now
173+
(baseClock, precision) -> () -> Year.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
161174
);
162175
GENERATOR_PRODUCERS.put(
163176
YearMonth.class,
164-
precision -> YearMonth::now
177+
(baseClock, precision) -> () -> YearMonth.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
165178
);
166179
GENERATOR_PRODUCERS.put(
167180
ZonedDateTime.class,
168-
precision -> {
169-
final Clock clock = ClockHelper.forPrecision( precision, 9 );
181+
(baseClock, precision) -> {
182+
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
170183
return () -> ZonedDateTime.now( clock );
171184
}
172185
);
@@ -208,16 +221,19 @@ static CurrentTimestampGeneratorDelegate getGeneratorDelegate(
208221
context.getDatabase().getDialect(),
209222
basicValue.getMetadata()
210223
);
211-
final Key key = new Key( propertyType, size.getPrecision() == null ? 0 : size.getPrecision() );
224+
final Clock baseClock = context.getServiceRegistry()
225+
.requireService( ConfigurationService.class )
226+
.getSetting( CLOCK_SETTING_NAME, value -> (Clock) value );
227+
final Key key = new Key( propertyType, baseClock, size.getPrecision() == null ? 0 : size.getPrecision() );
212228
final CurrentTimestampGeneratorDelegate delegate = GENERATOR_DELEGATES.get( key );
213229
if ( delegate != null ) {
214230
return delegate;
215231
}
216-
final IntFunction<CurrentTimestampGeneratorDelegate> producer = GENERATOR_PRODUCERS.get( key.clazz );
232+
final BiFunction<@Nullable Clock, Integer, CurrentTimestampGeneratorDelegate> producer = GENERATOR_PRODUCERS.get( key.clazz );
217233
if ( producer == null ) {
218234
return null;
219235
}
220-
final CurrentTimestampGeneratorDelegate generatorDelegate = producer.apply( key.precision );
236+
final CurrentTimestampGeneratorDelegate generatorDelegate = producer.apply( key.clock, key.precision );
221237
final CurrentTimestampGeneratorDelegate old = GENERATOR_DELEGATES.putIfAbsent(
222238
key,
223239
generatorDelegate
@@ -230,6 +246,10 @@ static CurrentTimestampGeneratorDelegate getGeneratorDelegate(
230246
}
231247
}
232248

249+
public static <T extends Clock> T getClock(SessionFactory sessionFactory) {
250+
return (T) sessionFactory.getProperties().get( CLOCK_SETTING_NAME );
251+
}
252+
233253
@Override
234254
public boolean generatedOnExecution() {
235255
return delegate == null;
@@ -267,10 +287,12 @@ interface CurrentTimestampGeneratorDelegate {
267287

268288
private static class Key {
269289
private final Class<?> clazz;
290+
private final @Nullable Clock clock;
270291
private final int precision;
271292

272-
public Key(Class<?> clazz, int precision) {
293+
public Key(Class<?> clazz, @Nullable Clock clock, int precision) {
273294
this.clazz = clazz;
295+
this.clock = clock;
274296
this.precision = precision;
275297
}
276298

@@ -288,12 +310,13 @@ public boolean equals(Object o) {
288310
if ( precision != key.precision ) {
289311
return false;
290312
}
291-
return clazz.equals( key.clazz );
313+
return clock == key.clock && clazz.equals( key.clazz );
292314
}
293315

294316
@Override
295317
public int hashCode() {
296318
int result = clazz.hashCode();
319+
result = 31 * result + ( clock == null ? 0 : clock.hashCode() );
297320
result = 31 * result + precision;
298321
return result;
299322
}

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ public EntityVersionMappingImpl(
9797
.getRepresentationStrategy()
9898
.resolvePropertyAccess( bootEntityDescriptor.getVersion() )
9999
.getGetter(),
100-
templateInstanceAccess
100+
templateInstanceAccess,
101+
creationProcess.getCreationContext().getSessionFactory()
101102
);
102103
}
103104

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClockHelper.java

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,25 @@
1010
import java.time.Duration;
1111

1212
import org.hibernate.engine.spi.SharedSessionContractImplementor;
13+
import org.hibernate.generator.internal.CurrentTimestampGeneration;
14+
15+
import org.checkerframework.checker.nullness.qual.Nullable;
1316

1417
/**
1518
* Helper for determining the correct clock for precision
1619
*/
1720
public class ClockHelper {
1821

1922
private static final Clock TICK_9 = Clock.systemDefaultZone();
20-
private static final Clock TICK_8 = Clock.tick( TICK_9, Duration.ofNanos( 10L ) );
21-
private static final Clock TICK_7 = Clock.tick( TICK_9, Duration.ofNanos( 100L ) );
22-
private static final Clock TICK_6 = Clock.tick( TICK_9, Duration.ofNanos( 1000L ) );
23-
private static final Clock TICK_5 = Clock.tick( TICK_9, Duration.ofNanos( 10000L ) );
24-
private static final Clock TICK_4 = Clock.tick( TICK_9, Duration.ofNanos( 100000L ) );
25-
private static final Clock TICK_3 = Clock.tick( TICK_9, Duration.ofNanos( 1000000L ) );
26-
private static final Clock TICK_2 = Clock.tick( TICK_9, Duration.ofNanos( 10000000L ) );
27-
private static final Clock TICK_1 = Clock.tick( TICK_9, Duration.ofNanos( 100000000L ) );
28-
private static final Clock TICK_0 = Clock.tick( TICK_9, Duration.ofNanos( 1000000000L ) );
23+
private static final Clock TICK_8 = forPrecision( TICK_9, 8, 9 );
24+
private static final Clock TICK_7 = forPrecision( TICK_9, 7, 9 );
25+
private static final Clock TICK_6 = forPrecision( TICK_9, 6, 9 );
26+
private static final Clock TICK_5 = forPrecision( TICK_9, 5, 9 );
27+
private static final Clock TICK_4 = forPrecision( TICK_9, 4, 9 );
28+
private static final Clock TICK_3 = forPrecision( TICK_9, 3, 9 );
29+
private static final Clock TICK_2 = forPrecision( TICK_9, 2, 9 );
30+
private static final Clock TICK_1 = forPrecision( TICK_9, 1, 9 );
31+
private static final Clock TICK_0 = forPrecision( TICK_9, 0, 9 );
2932

3033
public static Clock forPrecision(Integer precision, SharedSessionContractImplementor session) {
3134
return forPrecision( precision, session, 9 );
@@ -39,31 +42,38 @@ public static Clock forPrecision(Integer precision, SharedSessionContractImpleme
3942
else {
4043
resolvedPrecision = precision;
4144
}
42-
return forPrecision( resolvedPrecision, maxPrecision );
45+
final Clock baseClock = (Clock) session.getFactory()
46+
.getProperties()
47+
.get( CurrentTimestampGeneration.CLOCK_SETTING_NAME );
48+
return forPrecision( baseClock, resolvedPrecision, maxPrecision );
4349
}
4450

4551
public static Clock forPrecision(int resolvedPrecision, int maxPrecision) {
52+
return forPrecision( null, resolvedPrecision, maxPrecision );
53+
}
54+
55+
public static Clock forPrecision(@Nullable Clock baseClock, int resolvedPrecision, int maxPrecision) {
4656
switch ( Math.min( resolvedPrecision, maxPrecision ) ) {
4757
case 0:
48-
return TICK_0;
58+
return baseClock == null ? TICK_0 : Clock.tick( baseClock, Duration.ofNanos( 1000000000L ) );
4959
case 1:
50-
return TICK_1;
60+
return baseClock == null ? TICK_1 : Clock.tick( baseClock, Duration.ofNanos( 100000000L ) );
5161
case 2:
52-
return TICK_2;
62+
return baseClock == null ? TICK_2 : Clock.tick( baseClock, Duration.ofNanos( 10000000L ) );
5363
case 3:
54-
return TICK_3;
64+
return baseClock == null ? TICK_3 : Clock.tick( baseClock, Duration.ofNanos( 1000000L ) );
5565
case 4:
56-
return TICK_4;
66+
return baseClock == null ? TICK_4 : Clock.tick( baseClock, Duration.ofNanos( 100000L ) );
5767
case 5:
58-
return TICK_5;
68+
return baseClock == null ? TICK_5 : Clock.tick( baseClock, Duration.ofNanos( 10000L ) );
5969
case 6:
60-
return TICK_6;
70+
return baseClock == null ? TICK_6 : Clock.tick( baseClock, Duration.ofNanos( 1000L ) );
6171
case 7:
62-
return TICK_7;
72+
return baseClock == null ? TICK_7 : Clock.tick( baseClock, Duration.ofNanos( 100L ) );
6373
case 8:
64-
return TICK_8;
74+
return baseClock == null ? TICK_8 : Clock.tick( baseClock, Duration.ofNanos( 10L ) );
6575
case 9:
66-
return TICK_9;
76+
return baseClock == null ? TICK_9 : baseClock;
6777
}
6878
throw new IllegalArgumentException( "Illegal precision: " + resolvedPrecision );
6979
}

0 commit comments

Comments
 (0)