HHH-18395 Fix intermittent failures of clock based tests by using custom clock

This commit is contained in:
Christian Beikov 2024-07-16 20:15:26 +02:00
parent a17b241f40
commit 5b2a87c5e8
14 changed files with 349 additions and 165 deletions

View File

@ -9,9 +9,10 @@ package org.hibernate.engine.internal;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.IdentifierValue;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionDelegatorBaseImpl; import org.hibernate.engine.spi.SharedSessionDelegatorBaseImpl;
import org.hibernate.engine.spi.VersionValue; import org.hibernate.engine.spi.VersionValue;
import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.KeyValue;
@ -85,7 +86,8 @@ public class UnsavedValueFactory {
Integer precision, Integer precision,
Integer scale, Integer scale,
Getter getter, Getter getter,
Supplier<?> templateInstanceAccess) { Supplier<?> templateInstanceAccess,
SessionFactoryImplementor sessionFactory) {
final String unsavedValue = bootVersionMapping.getNullValue(); final String unsavedValue = bootVersionMapping.getNullValue();
if ( unsavedValue == null ) { if ( unsavedValue == null ) {
if ( getter != null && templateInstanceAccess != null ) { if ( getter != null && templateInstanceAccess != null ) {
@ -95,8 +97,7 @@ public class UnsavedValueFactory {
// if the version of a newly instantiated object is not the same // if the version of a newly instantiated object is not the same
// as the version seed value, use that as the unsaved-value // as the version seed value, use that as the unsaved-value
final T seedValue = jtd.seed( length, precision, scale, final T seedValue = jtd.seed( length, precision, scale, mockSession( sessionFactory ) );
mockSession( bootVersionMapping.getBuildingContext() ) );
return jtd.areEqual( seedValue, defaultValue ) return jtd.areEqual( seedValue, defaultValue )
? VersionValue.UNDEFINED ? VersionValue.UNDEFINED
: new VersionValue( defaultValue ); : new VersionValue( defaultValue );
@ -121,12 +122,27 @@ public class UnsavedValueFactory {
} }
private static SharedSessionDelegatorBaseImpl mockSession(MetadataBuildingContext context) { private static SharedSessionDelegatorBaseImpl mockSession(SessionFactoryImplementor sessionFactory) {
return new SharedSessionDelegatorBaseImpl(null) { return new SharedSessionDelegatorBaseImpl(null) {
@Override
protected SharedSessionContract delegate() {
throw new UnsupportedOperationException( "Operation not supported" );
}
@Override
public SessionFactoryImplementor getFactory() {
return sessionFactory;
}
@Override
public SessionFactoryImplementor getSessionFactory() {
return sessionFactory;
}
@Override @Override
public JdbcServices getJdbcServices() { public JdbcServices getJdbcServices() {
return context.getBootstrapContext().getServiceRegistry() return sessionFactory.getJdbcServices();
.requireService( JdbcServices.class );
} }
}; };
} }

View File

@ -7,11 +7,13 @@
package org.hibernate.generator.internal; package org.hibernate.generator.internal;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.CurrentTimestamp; import org.hibernate.annotations.CurrentTimestamp;
import org.hibernate.annotations.SourceType; import org.hibernate.annotations.SourceType;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType; import org.hibernate.generator.EventType;
@ -36,7 +38,6 @@ import java.time.OffsetDateTime;
import java.time.OffsetTime; import java.time.OffsetTime;
import java.time.Year; import java.time.Year;
import java.time.YearMonth; import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -44,7 +45,9 @@ import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.IntFunction; import java.util.function.BiFunction;
import org.checkerframework.checker.nullness.qual.Nullable;
import static org.hibernate.generator.EventTypeSets.INSERT_AND_UPDATE; import static org.hibernate.generator.EventTypeSets.INSERT_AND_UPDATE;
import static org.hibernate.generator.EventTypeSets.INSERT_ONLY; import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;
@ -68,24 +71,34 @@ import static org.hibernate.generator.EventTypeSets.fromArray;
* @author Gavin King * @author Gavin King
*/ */
public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnExecutionGenerator { public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnExecutionGenerator {
/**
* Configuration property name to set a custom {@link Clock} for Hibernate ORM to use when generating VM based
* timestamp values for e.g. {@link CurrentTimestamp}, {@link CreationTimestamp}, {@link UpdateTimestamp}
* and {@link org.hibernate.type.descriptor.java.VersionJavaType} methods.
*
* @since 6.6
*/
public static final String CLOCK_SETTING_NAME = "hibernate.testing.clock";
private final EnumSet<EventType> eventTypes; private final EnumSet<EventType> eventTypes;
private final CurrentTimestampGeneratorDelegate delegate; private final CurrentTimestampGeneratorDelegate delegate;
private static final Map<Class<?>, IntFunction<CurrentTimestampGeneratorDelegate>> GENERATOR_PRODUCERS = new HashMap<>(); private static final Map<Class<?>, BiFunction<@Nullable Clock, Integer, CurrentTimestampGeneratorDelegate>> GENERATOR_PRODUCERS = new HashMap<>();
private static final Map<Key, CurrentTimestampGeneratorDelegate> GENERATOR_DELEGATES = new ConcurrentHashMap<>(); private static final Map<Key, CurrentTimestampGeneratorDelegate> GENERATOR_DELEGATES = new ConcurrentHashMap<>();
static { static {
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
Date.class, Date.class,
precision -> { (baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( precision, 3 ); final Clock clock = ClockHelper.forPrecision( baseClock, precision, 3 );
return () -> new Date( clock.millis() ); return () -> new Date( clock.millis() );
} }
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
Calendar.class, Calendar.class,
precision -> { (baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( precision, 3 ); final Clock clock = ClockHelper.forPrecision( baseClock, precision, 3 );
return () -> { return () -> {
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis( clock.millis() ); calendar.setTimeInMillis( clock.millis() );
@ -95,78 +108,78 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
java.sql.Date.class, java.sql.Date.class,
precision -> () -> new java.sql.Date( System.currentTimeMillis() ) (baseClock, precision) -> () -> new java.sql.Date( baseClock == null ? System.currentTimeMillis() : baseClock.millis() )
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
Time.class, Time.class,
precision -> { (baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( precision, 3 ); final Clock clock = ClockHelper.forPrecision( baseClock, precision, 3 );
return () -> new Time( clock.millis() ); return () -> new Time( clock.millis() );
} }
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
Timestamp.class, Timestamp.class,
precision -> { (baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 ); final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> Timestamp.from( clock.instant() ); return () -> Timestamp.from( clock.instant() );
} }
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
Instant.class, Instant.class,
precision -> { (baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 ); final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return clock::instant; return clock::instant;
} }
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
LocalDate.class, LocalDate.class,
precision -> LocalDate::now (baseClock, precision) -> () -> LocalDate.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
LocalDateTime.class, LocalDateTime.class,
precision -> { (baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 ); final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> LocalDateTime.now( clock ); return () -> LocalDateTime.now( clock );
} }
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
LocalTime.class, LocalTime.class,
precision -> { (baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 ); final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> LocalTime.now( clock ); return () -> LocalTime.now( clock );
} }
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
MonthDay.class, MonthDay.class,
precision -> MonthDay::now (baseClock, precision) -> () -> MonthDay.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
OffsetDateTime.class, OffsetDateTime.class,
precision -> { (baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 ); final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> OffsetDateTime.now( clock ); return () -> OffsetDateTime.now( clock );
} }
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
OffsetTime.class, OffsetTime.class,
precision -> { (baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 ); final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> OffsetTime.now( clock ); return () -> OffsetTime.now( clock );
} }
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
Year.class, Year.class,
precision -> Year::now (baseClock, precision) -> () -> Year.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
YearMonth.class, YearMonth.class,
precision -> YearMonth::now (baseClock, precision) -> () -> YearMonth.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
); );
GENERATOR_PRODUCERS.put( GENERATOR_PRODUCERS.put(
ZonedDateTime.class, ZonedDateTime.class,
precision -> { (baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 ); final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> ZonedDateTime.now( clock ); return () -> ZonedDateTime.now( clock );
} }
); );
@ -208,16 +221,19 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
context.getDatabase().getDialect(), context.getDatabase().getDialect(),
basicValue.getMetadata() basicValue.getMetadata()
); );
final Key key = new Key( propertyType, size.getPrecision() == null ? 0 : size.getPrecision() ); final Clock baseClock = context.getServiceRegistry()
.requireService( ConfigurationService.class )
.getSetting( CLOCK_SETTING_NAME, value -> (Clock) value );
final Key key = new Key( propertyType, baseClock, size.getPrecision() == null ? 0 : size.getPrecision() );
final CurrentTimestampGeneratorDelegate delegate = GENERATOR_DELEGATES.get( key ); final CurrentTimestampGeneratorDelegate delegate = GENERATOR_DELEGATES.get( key );
if ( delegate != null ) { if ( delegate != null ) {
return delegate; return delegate;
} }
final IntFunction<CurrentTimestampGeneratorDelegate> producer = GENERATOR_PRODUCERS.get( key.clazz ); final BiFunction<@Nullable Clock, Integer, CurrentTimestampGeneratorDelegate> producer = GENERATOR_PRODUCERS.get( key.clazz );
if ( producer == null ) { if ( producer == null ) {
return null; return null;
} }
final CurrentTimestampGeneratorDelegate generatorDelegate = producer.apply( key.precision ); final CurrentTimestampGeneratorDelegate generatorDelegate = producer.apply( key.clock, key.precision );
final CurrentTimestampGeneratorDelegate old = GENERATOR_DELEGATES.putIfAbsent( final CurrentTimestampGeneratorDelegate old = GENERATOR_DELEGATES.putIfAbsent(
key, key,
generatorDelegate generatorDelegate
@ -230,6 +246,10 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
} }
} }
public static <T extends Clock> T getClock(SessionFactory sessionFactory) {
return (T) sessionFactory.getProperties().get( CLOCK_SETTING_NAME );
}
@Override @Override
public boolean generatedOnExecution() { public boolean generatedOnExecution() {
return delegate == null; return delegate == null;
@ -267,10 +287,12 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
private static class Key { private static class Key {
private final Class<?> clazz; private final Class<?> clazz;
private final @Nullable Clock clock;
private final int precision; private final int precision;
public Key(Class<?> clazz, int precision) { public Key(Class<?> clazz, @Nullable Clock clock, int precision) {
this.clazz = clazz; this.clazz = clazz;
this.clock = clock;
this.precision = precision; this.precision = precision;
} }
@ -288,12 +310,13 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
if ( precision != key.precision ) { if ( precision != key.precision ) {
return false; return false;
} }
return clazz.equals( key.clazz ); return clock == key.clock && clazz.equals( key.clazz );
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = clazz.hashCode(); int result = clazz.hashCode();
result = 31 * result + ( clock == null ? 0 : clock.hashCode() );
result = 31 * result + precision; result = 31 * result + precision;
return result; return result;
} }

View File

@ -97,7 +97,8 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti
.getRepresentationStrategy() .getRepresentationStrategy()
.resolvePropertyAccess( bootEntityDescriptor.getVersion() ) .resolvePropertyAccess( bootEntityDescriptor.getVersion() )
.getGetter(), .getGetter(),
templateInstanceAccess templateInstanceAccess,
creationProcess.getCreationContext().getSessionFactory()
); );
} }

View File

@ -10,6 +10,9 @@ import java.time.Clock;
import java.time.Duration; import java.time.Duration;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Helper for determining the correct clock for precision * Helper for determining the correct clock for precision
@ -17,15 +20,15 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
public class ClockHelper { public class ClockHelper {
private static final Clock TICK_9 = Clock.systemDefaultZone(); private static final Clock TICK_9 = Clock.systemDefaultZone();
private static final Clock TICK_8 = Clock.tick( TICK_9, Duration.ofNanos( 10L ) ); private static final Clock TICK_8 = forPrecision( TICK_9, 8, 9 );
private static final Clock TICK_7 = Clock.tick( TICK_9, Duration.ofNanos( 100L ) ); private static final Clock TICK_7 = forPrecision( TICK_9, 7, 9 );
private static final Clock TICK_6 = Clock.tick( TICK_9, Duration.ofNanos( 1000L ) ); private static final Clock TICK_6 = forPrecision( TICK_9, 6, 9 );
private static final Clock TICK_5 = Clock.tick( TICK_9, Duration.ofNanos( 10000L ) ); private static final Clock TICK_5 = forPrecision( TICK_9, 5, 9 );
private static final Clock TICK_4 = Clock.tick( TICK_9, Duration.ofNanos( 100000L ) ); private static final Clock TICK_4 = forPrecision( TICK_9, 4, 9 );
private static final Clock TICK_3 = Clock.tick( TICK_9, Duration.ofNanos( 1000000L ) ); private static final Clock TICK_3 = forPrecision( TICK_9, 3, 9 );
private static final Clock TICK_2 = Clock.tick( TICK_9, Duration.ofNanos( 10000000L ) ); private static final Clock TICK_2 = forPrecision( TICK_9, 2, 9 );
private static final Clock TICK_1 = Clock.tick( TICK_9, Duration.ofNanos( 100000000L ) ); private static final Clock TICK_1 = forPrecision( TICK_9, 1, 9 );
private static final Clock TICK_0 = Clock.tick( TICK_9, Duration.ofNanos( 1000000000L ) ); private static final Clock TICK_0 = forPrecision( TICK_9, 0, 9 );
public static Clock forPrecision(Integer precision, SharedSessionContractImplementor session) { public static Clock forPrecision(Integer precision, SharedSessionContractImplementor session) {
return forPrecision( precision, session, 9 ); return forPrecision( precision, session, 9 );
@ -39,31 +42,38 @@ public class ClockHelper {
else { else {
resolvedPrecision = precision; resolvedPrecision = precision;
} }
return forPrecision( resolvedPrecision, maxPrecision ); final Clock baseClock = (Clock) session.getFactory()
.getProperties()
.get( CurrentTimestampGeneration.CLOCK_SETTING_NAME );
return forPrecision( baseClock, resolvedPrecision, maxPrecision );
} }
public static Clock forPrecision(int resolvedPrecision, int maxPrecision) { public static Clock forPrecision(int resolvedPrecision, int maxPrecision) {
return forPrecision( null, resolvedPrecision, maxPrecision );
}
public static Clock forPrecision(@Nullable Clock baseClock, int resolvedPrecision, int maxPrecision) {
switch ( Math.min( resolvedPrecision, maxPrecision ) ) { switch ( Math.min( resolvedPrecision, maxPrecision ) ) {
case 0: case 0:
return TICK_0; return baseClock == null ? TICK_0 : Clock.tick( baseClock, Duration.ofNanos( 1000000000L ) );
case 1: case 1:
return TICK_1; return baseClock == null ? TICK_1 : Clock.tick( baseClock, Duration.ofNanos( 100000000L ) );
case 2: case 2:
return TICK_2; return baseClock == null ? TICK_2 : Clock.tick( baseClock, Duration.ofNanos( 10000000L ) );
case 3: case 3:
return TICK_3; return baseClock == null ? TICK_3 : Clock.tick( baseClock, Duration.ofNanos( 1000000L ) );
case 4: case 4:
return TICK_4; return baseClock == null ? TICK_4 : Clock.tick( baseClock, Duration.ofNanos( 100000L ) );
case 5: case 5:
return TICK_5; return baseClock == null ? TICK_5 : Clock.tick( baseClock, Duration.ofNanos( 10000L ) );
case 6: case 6:
return TICK_6; return baseClock == null ? TICK_6 : Clock.tick( baseClock, Duration.ofNanos( 1000L ) );
case 7: case 7:
return TICK_7; return baseClock == null ? TICK_7 : Clock.tick( baseClock, Duration.ofNanos( 100L ) );
case 8: case 8:
return TICK_8; return baseClock == null ? TICK_8 : Clock.tick( baseClock, Duration.ofNanos( 10L ) );
case 9: case 9:
return TICK_9; return baseClock == null ? TICK_9 : baseClock;
} }
throw new IllegalArgumentException( "Illegal precision: " + resolvedPrecision ); throw new IllegalArgumentException( "Illegal precision: " + resolvedPrecision );
} }

View File

@ -7,15 +7,17 @@
package org.hibernate.orm.test.annotations; package org.hibernate.orm.test.annotations;
import java.util.Date; import java.util.Date;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.JiraKey;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -25,9 +27,11 @@ import static org.junit.Assert.assertTrue;
/** /**
* @author Vlad Mihalcea * @author Vlad Mihalcea
*/ */
@TestForIssue(jiraKey = "HHH-13256") @JiraKey("HHH-13256")
public class InMemoryUpdateTimestampTest extends BaseEntityManagerFunctionalTestCase { public class InMemoryUpdateTimestampTest extends BaseEntityManagerFunctionalTestCase {
private static final MutableClock clock = new MutableClock();
@Override @Override
protected Class<?>[] getAnnotatedClasses() { protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { return new Class<?>[] {
@ -35,6 +39,12 @@ public class InMemoryUpdateTimestampTest extends BaseEntityManagerFunctionalTest
}; };
} }
@Override
protected void addConfigOptions(Map options) {
super.addConfigOptions( options );
options.put( CurrentTimestampGeneration.CLOCK_SETTING_NAME, clock );
}
@Test @Test
public void test() { public void test() {
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
@ -47,6 +57,7 @@ public class InMemoryUpdateTimestampTest extends BaseEntityManagerFunctionalTest
entityManager.flush(); entityManager.flush();
Assert.assertNotNull( person.getUpdatedOn() ); Assert.assertNotNull( person.getUpdatedOn() );
} ); } );
clock.tick();
AtomicReference<Date> beforeTimestamp = new AtomicReference<>(); AtomicReference<Date> beforeTimestamp = new AtomicReference<>();

View File

@ -0,0 +1,66 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.annotations;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
public final class MutableClock extends Clock {
private static final Instant START_INSTANT = LocalDateTime.of( 2000, 1, 1, 12, 30, 50, 123456789 )
.toInstant( ZoneOffset.UTC );
private final ZoneId zoneId;
private Instant instant;
public MutableClock() {
this( ZoneId.systemDefault(), START_INSTANT );
}
public MutableClock(ZoneId zoneId) {
this( zoneId, START_INSTANT );
}
public MutableClock(Instant instant) {
this( ZoneId.systemDefault(), instant );
}
public MutableClock(ZoneId zoneId, Instant instant) {
this.zoneId = zoneId;
this.instant = instant;
}
@Override
public ZoneId getZone() {
return zoneId;
}
@Override
public Clock withZone(ZoneId zone) {
return new MutableClock( zone, instant );
}
@Override
public Instant instant() {
return instant;
}
public void setInstant(Instant instant) {
this.instant = instant;
}
public void reset() {
instant = START_INSTANT;
}
public void tick() {
instant = instant.plusSeconds( 1 );
}
}

View File

@ -0,0 +1,19 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.annotations;
import java.time.Clock;
import org.hibernate.testing.orm.junit.SettingProvider;
public final class MutableClockSettingProvider implements SettingProvider.Provider<Clock> {
@Override
public Clock getSetting() {
return new MutableClock();
}
}

View File

@ -8,6 +8,7 @@ package org.hibernate.orm.test.annotations;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
@ -25,9 +26,10 @@ import jakarta.persistence.TemporalType;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.JiraKey;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -41,26 +43,32 @@ import static org.junit.Assert.assertTrue;
/** /**
* @author Andrea Boriero * @author Andrea Boriero
*/ */
@TestForIssue(jiraKey = "HHH-11867") @JiraKey("HHH-11867")
public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalTestCase { public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalTestCase {
private static final long SLEEP_MILLIS = 25;
private static final String customerId = "1"; private static final String customerId = "1";
private static final MutableClock clock = new MutableClock();
@Override @Override
protected Class<?>[] getAnnotatedClasses() { protected Class<?>[] getAnnotatedClasses() {
return new Class[] { Customer.class, AbstractPerson.class, Address.class }; return new Class[] { Customer.class, AbstractPerson.class, Address.class };
} }
@Override
protected void addConfigOptions(Map options) {
super.addConfigOptions( options );
options.put( CurrentTimestampGeneration.CLOCK_SETTING_NAME, clock );
}
@Before @Before
public void setUp() { public void setUp() {
clock.reset();
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setId( customerId ); customer.setId( customerId );
customer.addAddress( "address" ); customer.addAddress( "address" );
entityManager.persist( customer ); entityManager.persist( customer );
} ); } );
sleep( SLEEP_MILLIS ); clock.tick();
} }
@Test @Test
@ -72,6 +80,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
assertModifiedAtWasNotUpdated( customer ); assertModifiedAtWasNotUpdated( customer );
customer.setName( "xyz" ); customer.setName( "xyz" );
} ); } );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId ); Customer customer = entityManager.find( Customer.class, customerId );
@ -90,6 +99,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
assertModifiedAtWasNotUpdated( customer ); assertModifiedAtWasNotUpdated( customer );
customer.setEmail( "xyz@" ); customer.setEmail( "xyz@" );
} ); } );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId ); Customer customer = entityManager.find( Customer.class, customerId );
@ -110,6 +120,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
a.setStreet( "Lollard street" ); a.setStreet( "Lollard street" );
customer.setWorkAddress( a ); customer.setWorkAddress( a );
} ); } );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId ); Customer customer = entityManager.find( Customer.class, customerId );
@ -130,6 +141,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
a.setStreet( "Lollard Street" ); a.setStreet( "Lollard Street" );
customer.setHomeAddress( a ); customer.setHomeAddress( a );
} ); } );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId ); Customer customer = entityManager.find( Customer.class, customerId );
@ -150,6 +162,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
adresses.add( "another address" ); adresses.add( "another address" );
customer.setAdresses( adresses ); customer.setAdresses( adresses );
} ); } );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId ); Customer customer = entityManager.find( Customer.class, customerId );
@ -169,6 +182,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
Set<String> books = new HashSet<>(); Set<String> books = new HashSet<>();
customer.setBooks( books ); customer.setBooks( books );
} ); } );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId ); Customer customer = entityManager.find( Customer.class, customerId );

View File

@ -25,7 +25,6 @@ import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import org.hibernate.HibernateError;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
@ -36,16 +35,23 @@ import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.SybaseDialect; import org.hibernate.dialect.SybaseDialect;
import org.hibernate.dialect.TiDBDialect; import org.hibernate.dialect.TiDBDialect;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
import org.hibernate.orm.test.annotations.MutableClock;
import org.hibernate.orm.test.annotations.MutableClockSettingProvider;
import org.hibernate.tuple.ValueGenerator; import org.hibernate.tuple.ValueGenerator;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SettingProvider;
import org.hibernate.testing.orm.junit.SkipForDialect; import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -65,9 +71,19 @@ import static org.junit.Assert.assertTrue;
@SkipForDialect( dialectClass = TiDBDialect.class, reason = "See HHH-10196" ) @SkipForDialect( dialectClass = TiDBDialect.class, reason = "See HHH-10196" )
@DomainModel( annotatedClasses = DefaultGeneratedValueTest.TheEntity.class ) @DomainModel( annotatedClasses = DefaultGeneratedValueTest.TheEntity.class )
@SessionFactory @SessionFactory
@ServiceRegistry(settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class))
public class DefaultGeneratedValueTest { public class DefaultGeneratedValueTest {
private MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@Test @Test
@TestForIssue( jiraKey = "HHH-2907" ) @JiraKey( "HHH-2907" )
public void testGeneration(SessionFactoryScope scope) { public void testGeneration(SessionFactoryScope scope) {
final TheEntity created = scope.fromTransaction( (s) -> { final TheEntity created = scope.fromTransaction( (s) -> {
final TheEntity theEntity = new TheEntity( 1 ); final TheEntity theEntity = new TheEntity( 1 );
@ -153,8 +169,7 @@ public class DefaultGeneratedValueTest {
assertNotNull( created.vmCreatedSqlTimestamp ); assertNotNull( created.vmCreatedSqlTimestamp );
assertNotNull( created.updated ); assertNotNull( created.updated );
//We need to wait a little to make sure the timestamps produced are different clock.tick();
waitALittle();
scope.inTransaction( (s) -> { scope.inTransaction( (s) -> {
final TheEntity theEntity = s.get( TheEntity.class, 1 ); final TheEntity theEntity = s.get( TheEntity.class, 1 );
@ -260,12 +275,4 @@ public class DefaultGeneratedValueTest {
} }
} }
private static void waitALittle() {
try {
Thread.sleep( 10 );
}
catch (InterruptedException e) {
throw new HibernateError( "Unexpected wakeup from test sleep" );
}
}
} }

View File

@ -7,6 +7,7 @@
package org.hibernate.orm.test.mapping.generated; package org.hibernate.orm.test.mapping.generated;
import java.sql.Timestamp; import java.sql.Timestamp;
import jakarta.persistence.Basic; import jakarta.persistence.Basic;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@ -14,11 +15,17 @@ import jakarta.persistence.Table;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
import org.hibernate.orm.test.annotations.MutableClock;
import org.hibernate.orm.test.annotations.MutableClockSettingProvider;
import org.hibernate.testing.orm.junit.DomainModel; 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.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SettingProvider;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -31,14 +38,24 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
@DomainModel( annotatedClasses = InVmGenerationsWithAnnotationsTests.AuditedEntity.class ) @DomainModel( annotatedClasses = InVmGenerationsWithAnnotationsTests.AuditedEntity.class )
@SessionFactory @SessionFactory
@ServiceRegistry(settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class))
public class InVmGenerationsWithAnnotationsTests { public class InVmGenerationsWithAnnotationsTests {
private MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@Test @Test
public void testGenerations(SessionFactoryScope scope) { public void testGenerations(SessionFactoryScope scope) {
scope.inSession( (session) -> { scope.inSession( (session) -> {
// first creation // first creation
final AuditedEntity saved = scope.fromTransaction( session, (s) -> { final AuditedEntity saved = scope.fromTransaction( session, s -> {
final AuditedEntity entity = new AuditedEntity( 1, "it" ); final AuditedEntity entity = new AuditedEntity( 1, "it" );
session.persist( entity ); s.persist( entity );
return entity; return entity;
} ); } );
@ -47,18 +64,10 @@ public class InVmGenerationsWithAnnotationsTests {
assertThat( saved.lastUpdatedOn ).isNotNull(); assertThat( saved.lastUpdatedOn ).isNotNull();
saved.name = "changed"; saved.name = "changed";
// Let's sleep a millisecond to make sure we actually generate a different timestamp clock.tick();
try {
Thread.sleep( 1L );
}
catch (InterruptedException e) {
// Ignore
}
// then changing // then changing
final AuditedEntity merged = scope.fromTransaction( session, (s) -> { final AuditedEntity merged = scope.fromTransaction( session, s -> s.merge( saved ) );
return (AuditedEntity) session.merge( saved );
} );
assertThat( merged ).isNotNull(); assertThat( merged ).isNotNull();
assertThat( merged.createdOn ).isNotNull(); assertThat( merged.createdOn ).isNotNull();
@ -66,9 +75,7 @@ public class InVmGenerationsWithAnnotationsTests {
assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn ); assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn );
// lastly, make sure we can load it.. // lastly, make sure we can load it..
final AuditedEntity loaded = scope.fromTransaction( session, (s) -> { final AuditedEntity loaded = scope.fromTransaction( session, s -> s.get( AuditedEntity.class, 1 ) );
return session.get( AuditedEntity.class, 1 );
} );
assertThat( loaded ).isNotNull(); assertThat( loaded ).isNotNull();
assertThat( loaded.createdOn ).isEqualTo( merged.createdOn ); assertThat( loaded.createdOn ).isEqualTo( merged.createdOn );

View File

@ -8,6 +8,7 @@ package org.hibernate.orm.test.mapping.generated;
import java.sql.Date; import java.sql.Date;
import java.sql.Timestamp; import java.sql.Timestamp;
import jakarta.persistence.Basic; import jakarta.persistence.Basic;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@ -15,11 +16,17 @@ import jakarta.persistence.Table;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
import org.hibernate.orm.test.annotations.MutableClock;
import org.hibernate.orm.test.annotations.MutableClockSettingProvider;
import org.hibernate.testing.orm.junit.DomainModel; 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.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SettingProvider;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -32,14 +39,24 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
@DomainModel( annotatedClasses = InVmGenerationsWithAnnotationsWithMixedSqlTypesTests.AuditedEntity.class ) @DomainModel( annotatedClasses = InVmGenerationsWithAnnotationsWithMixedSqlTypesTests.AuditedEntity.class )
@SessionFactory @SessionFactory
@ServiceRegistry(settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class))
public class InVmGenerationsWithAnnotationsWithMixedSqlTypesTests { public class InVmGenerationsWithAnnotationsWithMixedSqlTypesTests {
private MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@Test @Test
public void testGenerations(SessionFactoryScope scope) { public void testGenerations(SessionFactoryScope scope) {
scope.inSession( (session) -> { scope.inSession( (session) -> {
// first creation // first creation
final AuditedEntity saved = scope.fromTransaction( session, (s) -> { final AuditedEntity saved = scope.fromTransaction( session, s -> {
final AuditedEntity entity = new AuditedEntity( 1, "it" ); final AuditedEntity entity = new AuditedEntity( 1, "it" );
session.persist( entity ); s.persist( entity );
return entity; return entity;
} ); } );
@ -48,18 +65,10 @@ public class InVmGenerationsWithAnnotationsWithMixedSqlTypesTests {
assertThat( saved.lastUpdatedOn ).isNotNull(); assertThat( saved.lastUpdatedOn ).isNotNull();
saved.name = "changed"; saved.name = "changed";
// Let's sleep a millisecond to make sure we actually generate a different timestamp clock.tick();
try {
Thread.sleep( 1L );
}
catch (InterruptedException e) {
// Ignore
}
// then changing // then changing
final AuditedEntity merged = scope.fromTransaction( session, (s) -> { final AuditedEntity merged = scope.fromTransaction( session, s -> s.merge( saved ) );
return (AuditedEntity) session.merge( saved );
} );
assertThat( merged ).isNotNull(); assertThat( merged ).isNotNull();
assertThat( merged.createdOn ).isNotNull(); assertThat( merged.createdOn ).isNotNull();
@ -67,9 +76,7 @@ public class InVmGenerationsWithAnnotationsWithMixedSqlTypesTests {
assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn ); assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn );
// lastly, make sure we can load it.. // lastly, make sure we can load it..
final AuditedEntity loaded = scope.fromTransaction( session, (s) -> { final AuditedEntity loaded = scope.fromTransaction( session, s -> s.get( AuditedEntity.class, 1 ) );
return session.get( AuditedEntity.class, 1 );
} );
assertThat( loaded ).isNotNull(); assertThat( loaded ).isNotNull();
assertThat( loaded.createdOn ).isEqualTo( merged.createdOn ); assertThat( loaded.createdOn ).isEqualTo( merged.createdOn );

View File

@ -7,19 +7,25 @@
package org.hibernate.orm.test.mapping.generated; package org.hibernate.orm.test.mapping.generated;
import java.sql.Date; import java.sql.Date;
import jakarta.persistence.Basic; import jakarta.persistence.Basic;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import org.hibernate.HibernateError;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
import org.hibernate.orm.test.annotations.MutableClock;
import org.hibernate.orm.test.annotations.MutableClockSettingProvider;
import org.hibernate.testing.orm.junit.DomainModel; 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.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SettingProvider;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -32,14 +38,24 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
@DomainModel( annotatedClasses = InVmGenerationsWithAnnotationsWithSqlDateTests.AuditedEntity.class ) @DomainModel( annotatedClasses = InVmGenerationsWithAnnotationsWithSqlDateTests.AuditedEntity.class )
@SessionFactory @SessionFactory
@ServiceRegistry(settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class))
public class InVmGenerationsWithAnnotationsWithSqlDateTests { public class InVmGenerationsWithAnnotationsWithSqlDateTests {
private MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@Test @Test
public void testGenerations(SessionFactoryScope scope) { public void testGenerations(SessionFactoryScope scope) {
scope.inSession( (session) -> { scope.inSession( (session) -> {
// first creation // first creation
final AuditedEntity saved = scope.fromTransaction( session, (s) -> { final AuditedEntity saved = scope.fromTransaction( session, s -> {
final AuditedEntity entity = new AuditedEntity( 1, "it" ); final AuditedEntity entity = new AuditedEntity( 1, "it" );
session.persist( entity ); s.persist( entity );
return entity; return entity;
} ); } );
@ -48,14 +64,10 @@ public class InVmGenerationsWithAnnotationsWithSqlDateTests {
assertThat( saved.lastUpdatedOn ).isNotNull(); assertThat( saved.lastUpdatedOn ).isNotNull();
saved.name = "changed"; saved.name = "changed";
clock.tick();
//We need to wait a little to make sure the timestamps produced are different
waitALittle();
// then changing // then changing
final AuditedEntity merged = scope.fromTransaction( session, (s) -> { final AuditedEntity merged = scope.fromTransaction( session, s -> s.merge( saved ) );
return (AuditedEntity) session.merge( saved );
} );
assertThat( merged ).isNotNull(); assertThat( merged ).isNotNull();
assertThat( merged.createdOn ).isNotNull(); assertThat( merged.createdOn ).isNotNull();
@ -63,9 +75,7 @@ public class InVmGenerationsWithAnnotationsWithSqlDateTests {
assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn ); assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn );
// lastly, make sure we can load it.. // lastly, make sure we can load it..
final AuditedEntity loaded = scope.fromTransaction( session, (s) -> { final AuditedEntity loaded = scope.fromTransaction( session, s -> s.get( AuditedEntity.class, 1 ) );
return session.get( AuditedEntity.class, 1 );
} );
assertThat( loaded ).isNotNull(); assertThat( loaded ).isNotNull();
assertThat( loaded.createdOn ).isEqualTo( merged.createdOn ); assertThat( loaded.createdOn ).isEqualTo( merged.createdOn );
@ -98,13 +108,4 @@ public class InVmGenerationsWithAnnotationsWithSqlDateTests {
this.name = name; this.name = name;
} }
} }
private static void waitALittle() {
try {
Thread.sleep( 10 );
}
catch (InterruptedException e) {
throw new HibernateError( "Unexpected wakeup from test sleep" );
}
}
} }

View File

@ -14,14 +14,19 @@ import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import org.hibernate.HibernateError;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
import org.hibernate.orm.test.annotations.MutableClock;
import org.hibernate.orm.test.annotations.MutableClockSettingProvider;
import org.hibernate.testing.orm.junit.DomainModel; 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.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SettingProvider;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -34,14 +39,24 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
@DomainModel( annotatedClasses = InVmGenerationsWithMultipleAnnotationsTests.AuditedEntity.class ) @DomainModel( annotatedClasses = InVmGenerationsWithMultipleAnnotationsTests.AuditedEntity.class )
@SessionFactory @SessionFactory
@ServiceRegistry(settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class))
public class InVmGenerationsWithMultipleAnnotationsTests { public class InVmGenerationsWithMultipleAnnotationsTests {
private MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@Test @Test
public void testGenerations(SessionFactoryScope scope) { public void testGenerations(SessionFactoryScope scope) {
scope.inSession( (session) -> { scope.inSession( (session) -> {
// first creation // first creation
final AuditedEntity saved = scope.fromTransaction( session, (s) -> { final AuditedEntity saved = scope.fromTransaction( session, s -> {
final AuditedEntity entity = new AuditedEntity( 1, "it" ); final AuditedEntity entity = new AuditedEntity( 1, "it" );
session.persist( entity ); s.persist( entity );
return entity; return entity;
} ); } );
@ -50,14 +65,10 @@ public class InVmGenerationsWithMultipleAnnotationsTests {
assertThat( saved.lastUpdatedOn ).isNotNull(); assertThat( saved.lastUpdatedOn ).isNotNull();
saved.name = "changed"; saved.name = "changed";
clock.tick();
//We need to wait a little to make sure the timestamps produced are different
waitALittle();
// then changing // then changing
final AuditedEntity merged = scope.fromTransaction( session, (s) -> { final AuditedEntity merged = scope.fromTransaction( session, s -> s.merge( saved ) );
return (AuditedEntity) session.merge( saved );
} );
assertThat( merged ).isNotNull(); assertThat( merged ).isNotNull();
assertThat( merged.createdOn ).isNotNull(); assertThat( merged.createdOn ).isNotNull();
@ -65,9 +76,7 @@ public class InVmGenerationsWithMultipleAnnotationsTests {
assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn ); assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn );
// lastly, make sure we can load it.. // lastly, make sure we can load it..
final AuditedEntity loaded = scope.fromTransaction( session, (s) -> { final AuditedEntity loaded = scope.fromTransaction( session, s -> s.get( AuditedEntity.class, 1 ) );
return session.get( AuditedEntity.class, 1 );
} );
assertThat( loaded ).isNotNull(); assertThat( loaded ).isNotNull();
assertThat( loaded.createdOn ).isEqualTo( merged.createdOn ); assertThat( loaded.createdOn ).isEqualTo( merged.createdOn );
@ -110,13 +119,4 @@ public class InVmGenerationsWithMultipleAnnotationsTests {
this.name = name; this.name = name;
} }
} }
private static void waitALittle() {
try {
Thread.sleep( 10 );
}
catch (InterruptedException e) {
throw new HibernateError( "Unexpected wakeup from test sleep" );
}
}
} }

View File

@ -6,7 +6,6 @@
*/ */
package org.hibernate.orm.test.tenantid; package org.hibernate.orm.test.tenantid;
import org.hibernate.HibernateError;
import org.hibernate.PropertyValueException; import org.hibernate.PropertyValueException;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.StatelessSession; import org.hibernate.StatelessSession;
@ -24,13 +23,18 @@ import org.hibernate.testing.orm.junit.SessionFactoryProducer;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting; import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.binder.internal.TenantIdBinder; import org.hibernate.binder.internal.TenantIdBinder;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
import org.hibernate.orm.test.annotations.MutableClock;
import org.hibernate.orm.test.annotations.MutableClockSettingProvider;
import org.hibernate.query.Query; import org.hibernate.query.Query;
import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery; import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.testing.orm.junit.SettingProvider;
import org.hibernate.testing.orm.junit.SkipForDialect; import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
@ -54,11 +58,19 @@ import java.util.List;
@ServiceRegistry( @ServiceRegistry(
settings = { settings = {
@Setting(name = JAKARTA_HBM2DDL_DATABASE_ACTION, value = "create-drop") @Setting(name = JAKARTA_HBM2DDL_DATABASE_ACTION, value = "create-drop")
} },
settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class)
) )
public class TenantIdTest implements SessionFactoryProducer { public class TenantIdTest implements SessionFactoryProducer {
String currentTenant; String currentTenant;
MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@AfterEach @AfterEach
public void cleanup(SessionFactoryScope scope) { public void cleanup(SessionFactoryScope scope) {
@ -211,8 +223,7 @@ public class TenantIdTest implements SessionFactoryProducer {
assertEquals( "mine", record.state.tenantId ); assertEquals( "mine", record.state.tenantId );
assertNotNull( record.state.updated ); assertNotNull( record.state.updated );
//We need to wait a little to make sure the timestamps produced are different clock.tick();
waitALittle();
scope.inTransaction( s -> { scope.inTransaction( s -> {
Record r = s.find( Record.class, record.id ); Record r = s.find( Record.class, record.id );
@ -342,13 +353,4 @@ public class TenantIdTest implements SessionFactoryProducer {
JpaRoot<Record> from = criteriaQuery.from( Record.class ); JpaRoot<Record> from = criteriaQuery.from( Record.class );
return session.createQuery( criteriaQuery ).getResultList(); return session.createQuery( criteriaQuery ).getResultList();
} }
private static void waitALittle() {
try {
Thread.sleep( 10 );
}
catch (InterruptedException e) {
throw new HibernateError( "Unexpected wakeup from test sleep" );
}
}
} }