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 org.hibernate.MappingException;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.IdentifierValue;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionDelegatorBaseImpl;
import org.hibernate.engine.spi.VersionValue;
import org.hibernate.mapping.KeyValue;
@ -85,7 +86,8 @@ public class UnsavedValueFactory {
Integer precision,
Integer scale,
Getter getter,
Supplier<?> templateInstanceAccess) {
Supplier<?> templateInstanceAccess,
SessionFactoryImplementor sessionFactory) {
final String unsavedValue = bootVersionMapping.getNullValue();
if ( unsavedValue == 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
// as the version seed value, use that as the unsaved-value
final T seedValue = jtd.seed( length, precision, scale,
mockSession( bootVersionMapping.getBuildingContext() ) );
final T seedValue = jtd.seed( length, precision, scale, mockSession( sessionFactory ) );
return jtd.areEqual( seedValue, defaultValue )
? VersionValue.UNDEFINED
: 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) {
@Override
protected SharedSessionContract delegate() {
throw new UnsupportedOperationException( "Operation not supported" );
}
@Override
public SessionFactoryImplementor getFactory() {
return sessionFactory;
}
@Override
public SessionFactoryImplementor getSessionFactory() {
return sessionFactory;
}
@Override
public JdbcServices getJdbcServices() {
return context.getBootstrapContext().getServiceRegistry()
.requireService( JdbcServices.class );
return sessionFactory.getJdbcServices();
}
};
}

View File

@ -7,11 +7,13 @@
package org.hibernate.generator.internal;
import org.hibernate.AssertionFailure;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.CurrentTimestamp;
import org.hibernate.annotations.SourceType;
import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
@ -36,7 +38,6 @@ import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
@ -44,7 +45,9 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
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_ONLY;
@ -68,24 +71,34 @@ import static org.hibernate.generator.EventTypeSets.fromArray;
* @author Gavin King
*/
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 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<>();
static {
GENERATOR_PRODUCERS.put(
Date.class,
precision -> {
final Clock clock = ClockHelper.forPrecision( precision, 3 );
(baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 3 );
return () -> new Date( clock.millis() );
}
);
GENERATOR_PRODUCERS.put(
Calendar.class,
precision -> {
final Clock clock = ClockHelper.forPrecision( precision, 3 );
(baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 3 );
return () -> {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis( clock.millis() );
@ -95,78 +108,78 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
);
GENERATOR_PRODUCERS.put(
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(
Time.class,
precision -> {
final Clock clock = ClockHelper.forPrecision( precision, 3 );
(baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 3 );
return () -> new Time( clock.millis() );
}
);
GENERATOR_PRODUCERS.put(
Timestamp.class,
precision -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 );
(baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> Timestamp.from( clock.instant() );
}
);
GENERATOR_PRODUCERS.put(
Instant.class,
precision -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 );
(baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return clock::instant;
}
);
GENERATOR_PRODUCERS.put(
LocalDate.class,
precision -> LocalDate::now
(baseClock, precision) -> () -> LocalDate.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
);
GENERATOR_PRODUCERS.put(
LocalDateTime.class,
precision -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 );
(baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> LocalDateTime.now( clock );
}
);
GENERATOR_PRODUCERS.put(
LocalTime.class,
precision -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 );
(baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> LocalTime.now( clock );
}
);
GENERATOR_PRODUCERS.put(
MonthDay.class,
precision -> MonthDay::now
(baseClock, precision) -> () -> MonthDay.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
);
GENERATOR_PRODUCERS.put(
OffsetDateTime.class,
precision -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 );
(baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> OffsetDateTime.now( clock );
}
);
GENERATOR_PRODUCERS.put(
OffsetTime.class,
precision -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 );
(baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> OffsetTime.now( clock );
}
);
GENERATOR_PRODUCERS.put(
Year.class,
precision -> Year::now
(baseClock, precision) -> () -> Year.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
);
GENERATOR_PRODUCERS.put(
YearMonth.class,
precision -> YearMonth::now
(baseClock, precision) -> () -> YearMonth.now( baseClock == null ? Clock.systemDefaultZone() : baseClock )
);
GENERATOR_PRODUCERS.put(
ZonedDateTime.class,
precision -> {
final Clock clock = ClockHelper.forPrecision( precision, 9 );
(baseClock, precision) -> {
final Clock clock = ClockHelper.forPrecision( baseClock, precision, 9 );
return () -> ZonedDateTime.now( clock );
}
);
@ -208,16 +221,19 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
context.getDatabase().getDialect(),
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 );
if ( delegate != null ) {
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 ) {
return null;
}
final CurrentTimestampGeneratorDelegate generatorDelegate = producer.apply( key.precision );
final CurrentTimestampGeneratorDelegate generatorDelegate = producer.apply( key.clock, key.precision );
final CurrentTimestampGeneratorDelegate old = GENERATOR_DELEGATES.putIfAbsent(
key,
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
public boolean generatedOnExecution() {
return delegate == null;
@ -267,10 +287,12 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
private static class Key {
private final Class<?> clazz;
private final @Nullable Clock clock;
private final int precision;
public Key(Class<?> clazz, int precision) {
public Key(Class<?> clazz, @Nullable Clock clock, int precision) {
this.clazz = clazz;
this.clock = clock;
this.precision = precision;
}
@ -288,12 +310,13 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
if ( precision != key.precision ) {
return false;
}
return clazz.equals( key.clazz );
return clock == key.clock && clazz.equals( key.clazz );
}
@Override
public int hashCode() {
int result = clazz.hashCode();
result = 31 * result + ( clock == null ? 0 : clock.hashCode() );
result = 31 * result + precision;
return result;
}

View File

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

View File

@ -10,6 +10,9 @@ import java.time.Clock;
import java.time.Duration;
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
@ -17,15 +20,15 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
public class ClockHelper {
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_7 = Clock.tick( TICK_9, Duration.ofNanos( 100L ) );
private static final Clock TICK_6 = Clock.tick( TICK_9, Duration.ofNanos( 1000L ) );
private static final Clock TICK_5 = Clock.tick( TICK_9, Duration.ofNanos( 10000L ) );
private static final Clock TICK_4 = Clock.tick( TICK_9, Duration.ofNanos( 100000L ) );
private static final Clock TICK_3 = Clock.tick( TICK_9, Duration.ofNanos( 1000000L ) );
private static final Clock TICK_2 = Clock.tick( TICK_9, Duration.ofNanos( 10000000L ) );
private static final Clock TICK_1 = Clock.tick( TICK_9, Duration.ofNanos( 100000000L ) );
private static final Clock TICK_0 = Clock.tick( TICK_9, Duration.ofNanos( 1000000000L ) );
private static final Clock TICK_8 = forPrecision( TICK_9, 8, 9 );
private static final Clock TICK_7 = forPrecision( TICK_9, 7, 9 );
private static final Clock TICK_6 = forPrecision( TICK_9, 6, 9 );
private static final Clock TICK_5 = forPrecision( TICK_9, 5, 9 );
private static final Clock TICK_4 = forPrecision( TICK_9, 4, 9 );
private static final Clock TICK_3 = forPrecision( TICK_9, 3, 9 );
private static final Clock TICK_2 = forPrecision( TICK_9, 2, 9 );
private static final Clock TICK_1 = forPrecision( TICK_9, 1, 9 );
private static final Clock TICK_0 = forPrecision( TICK_9, 0, 9 );
public static Clock forPrecision(Integer precision, SharedSessionContractImplementor session) {
return forPrecision( precision, session, 9 );
@ -39,31 +42,38 @@ public class ClockHelper {
else {
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) {
return forPrecision( null, resolvedPrecision, maxPrecision );
}
public static Clock forPrecision(@Nullable Clock baseClock, int resolvedPrecision, int maxPrecision) {
switch ( Math.min( resolvedPrecision, maxPrecision ) ) {
case 0:
return TICK_0;
return baseClock == null ? TICK_0 : Clock.tick( baseClock, Duration.ofNanos( 1000000000L ) );
case 1:
return TICK_1;
return baseClock == null ? TICK_1 : Clock.tick( baseClock, Duration.ofNanos( 100000000L ) );
case 2:
return TICK_2;
return baseClock == null ? TICK_2 : Clock.tick( baseClock, Duration.ofNanos( 10000000L ) );
case 3:
return TICK_3;
return baseClock == null ? TICK_3 : Clock.tick( baseClock, Duration.ofNanos( 1000000L ) );
case 4:
return TICK_4;
return baseClock == null ? TICK_4 : Clock.tick( baseClock, Duration.ofNanos( 100000L ) );
case 5:
return TICK_5;
return baseClock == null ? TICK_5 : Clock.tick( baseClock, Duration.ofNanos( 10000L ) );
case 6:
return TICK_6;
return baseClock == null ? TICK_6 : Clock.tick( baseClock, Duration.ofNanos( 1000L ) );
case 7:
return TICK_7;
return baseClock == null ? TICK_7 : Clock.tick( baseClock, Duration.ofNanos( 100L ) );
case 8:
return TICK_8;
return baseClock == null ? TICK_8 : Clock.tick( baseClock, Duration.ofNanos( 10L ) );
case 9:
return TICK_9;
return baseClock == null ? TICK_9 : baseClock;
}
throw new IllegalArgumentException( "Illegal precision: " + resolvedPrecision );
}

View File

@ -7,15 +7,17 @@
package org.hibernate.orm.test.annotations;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
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.Test;
@ -25,9 +27,11 @@ import static org.junit.Assert.assertTrue;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-13256")
@JiraKey("HHH-13256")
public class InMemoryUpdateTimestampTest extends BaseEntityManagerFunctionalTestCase {
private static final MutableClock clock = new MutableClock();
@Override
protected Class<?>[] getAnnotatedClasses() {
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
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
@ -47,6 +57,7 @@ public class InMemoryUpdateTimestampTest extends BaseEntityManagerFunctionalTest
entityManager.flush();
Assert.assertNotNull( person.getUpdatedOn() );
} );
clock.tick();
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.HashSet;
import java.util.Map;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
@ -25,9 +26,10 @@ import jakarta.persistence.TemporalType;
import org.hibernate.Session;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.generator.internal.CurrentTimestampGeneration;
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.Test;
@ -41,26 +43,32 @@ import static org.junit.Assert.assertTrue;
/**
* @author Andrea Boriero
*/
@TestForIssue(jiraKey = "HHH-11867")
@JiraKey("HHH-11867")
public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalTestCase {
private static final long SLEEP_MILLIS = 25;
private static final String customerId = "1";
private static final MutableClock clock = new MutableClock();
@Override
protected Class<?>[] getAnnotatedClasses() {
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
public void setUp() {
clock.reset();
doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = new Customer();
customer.setId( customerId );
customer.addAddress( "address" );
entityManager.persist( customer );
} );
sleep( SLEEP_MILLIS );
clock.tick();
}
@Test
@ -72,6 +80,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
assertModifiedAtWasNotUpdated( customer );
customer.setName( "xyz" );
} );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId );
@ -90,6 +99,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
assertModifiedAtWasNotUpdated( customer );
customer.setEmail( "xyz@" );
} );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId );
@ -110,6 +120,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
a.setStreet( "Lollard street" );
customer.setWorkAddress( a );
} );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId );
@ -130,6 +141,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
a.setStreet( "Lollard Street" );
customer.setHomeAddress( a );
} );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId );
@ -150,6 +162,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
adresses.add( "another address" );
customer.setAdresses( adresses );
} );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId );
@ -169,6 +182,7 @@ public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalT
Set<String> books = new HashSet<>();
customer.setBooks( books );
} );
clock.tick();
doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find( Customer.class, customerId );

View File

@ -25,7 +25,6 @@ import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.HibernateError;
import org.hibernate.Session;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp;
@ -36,16 +35,23 @@ import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.SybaseDialect;
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.testing.TestForIssue;
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.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SettingProvider;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals;
@ -65,9 +71,19 @@ import static org.junit.Assert.assertTrue;
@SkipForDialect( dialectClass = TiDBDialect.class, reason = "See HHH-10196" )
@DomainModel( annotatedClasses = DefaultGeneratedValueTest.TheEntity.class )
@SessionFactory
@ServiceRegistry(settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class))
public class DefaultGeneratedValueTest {
private MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@Test
@TestForIssue( jiraKey = "HHH-2907" )
@JiraKey( "HHH-2907" )
public void testGeneration(SessionFactoryScope scope) {
final TheEntity created = scope.fromTransaction( (s) -> {
final TheEntity theEntity = new TheEntity( 1 );
@ -153,8 +169,7 @@ public class DefaultGeneratedValueTest {
assertNotNull( created.vmCreatedSqlTimestamp );
assertNotNull( created.updated );
//We need to wait a little to make sure the timestamps produced are different
waitALittle();
clock.tick();
scope.inTransaction( (s) -> {
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;
import java.sql.Timestamp;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@ -14,11 +15,17 @@ import jakarta.persistence.Table;
import org.hibernate.annotations.CreationTimestamp;
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.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
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.BeforeEach;
import org.junit.jupiter.api.Test;
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 )
@SessionFactory
@ServiceRegistry(settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class))
public class InVmGenerationsWithAnnotationsTests {
private MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@Test
public void testGenerations(SessionFactoryScope scope) {
scope.inSession( (session) -> {
// first creation
final AuditedEntity saved = scope.fromTransaction( session, (s) -> {
final AuditedEntity saved = scope.fromTransaction( session, s -> {
final AuditedEntity entity = new AuditedEntity( 1, "it" );
session.persist( entity );
s.persist( entity );
return entity;
} );
@ -47,18 +64,10 @@ public class InVmGenerationsWithAnnotationsTests {
assertThat( saved.lastUpdatedOn ).isNotNull();
saved.name = "changed";
// Let's sleep a millisecond to make sure we actually generate a different timestamp
try {
Thread.sleep( 1L );
}
catch (InterruptedException e) {
// Ignore
}
clock.tick();
// then changing
final AuditedEntity merged = scope.fromTransaction( session, (s) -> {
return (AuditedEntity) session.merge( saved );
} );
final AuditedEntity merged = scope.fromTransaction( session, s -> s.merge( saved ) );
assertThat( merged ).isNotNull();
assertThat( merged.createdOn ).isNotNull();
@ -66,9 +75,7 @@ public class InVmGenerationsWithAnnotationsTests {
assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn );
// lastly, make sure we can load it..
final AuditedEntity loaded = scope.fromTransaction( session, (s) -> {
return session.get( AuditedEntity.class, 1 );
} );
final AuditedEntity loaded = scope.fromTransaction( session, s -> s.get( AuditedEntity.class, 1 ) );
assertThat( loaded ).isNotNull();
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.Timestamp;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@ -15,11 +16,17 @@ import jakarta.persistence.Table;
import org.hibernate.annotations.CreationTimestamp;
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.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
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.BeforeEach;
import org.junit.jupiter.api.Test;
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 )
@SessionFactory
@ServiceRegistry(settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class))
public class InVmGenerationsWithAnnotationsWithMixedSqlTypesTests {
private MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@Test
public void testGenerations(SessionFactoryScope scope) {
scope.inSession( (session) -> {
// first creation
final AuditedEntity saved = scope.fromTransaction( session, (s) -> {
final AuditedEntity saved = scope.fromTransaction( session, s -> {
final AuditedEntity entity = new AuditedEntity( 1, "it" );
session.persist( entity );
s.persist( entity );
return entity;
} );
@ -48,18 +65,10 @@ public class InVmGenerationsWithAnnotationsWithMixedSqlTypesTests {
assertThat( saved.lastUpdatedOn ).isNotNull();
saved.name = "changed";
// Let's sleep a millisecond to make sure we actually generate a different timestamp
try {
Thread.sleep( 1L );
}
catch (InterruptedException e) {
// Ignore
}
clock.tick();
// then changing
final AuditedEntity merged = scope.fromTransaction( session, (s) -> {
return (AuditedEntity) session.merge( saved );
} );
final AuditedEntity merged = scope.fromTransaction( session, s -> s.merge( saved ) );
assertThat( merged ).isNotNull();
assertThat( merged.createdOn ).isNotNull();
@ -67,9 +76,7 @@ public class InVmGenerationsWithAnnotationsWithMixedSqlTypesTests {
assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn );
// lastly, make sure we can load it..
final AuditedEntity loaded = scope.fromTransaction( session, (s) -> {
return session.get( AuditedEntity.class, 1 );
} );
final AuditedEntity loaded = scope.fromTransaction( session, s -> s.get( AuditedEntity.class, 1 ) );
assertThat( loaded ).isNotNull();
assertThat( loaded.createdOn ).isEqualTo( merged.createdOn );

View File

@ -7,19 +7,25 @@
package org.hibernate.orm.test.mapping.generated;
import java.sql.Date;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.HibernateError;
import org.hibernate.annotations.CreationTimestamp;
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.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
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.BeforeEach;
import org.junit.jupiter.api.Test;
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 )
@SessionFactory
@ServiceRegistry(settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class))
public class InVmGenerationsWithAnnotationsWithSqlDateTests {
private MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@Test
public void testGenerations(SessionFactoryScope scope) {
scope.inSession( (session) -> {
// first creation
final AuditedEntity saved = scope.fromTransaction( session, (s) -> {
final AuditedEntity saved = scope.fromTransaction( session, s -> {
final AuditedEntity entity = new AuditedEntity( 1, "it" );
session.persist( entity );
s.persist( entity );
return entity;
} );
@ -48,14 +64,10 @@ public class InVmGenerationsWithAnnotationsWithSqlDateTests {
assertThat( saved.lastUpdatedOn ).isNotNull();
saved.name = "changed";
//We need to wait a little to make sure the timestamps produced are different
waitALittle();
clock.tick();
// then changing
final AuditedEntity merged = scope.fromTransaction( session, (s) -> {
return (AuditedEntity) session.merge( saved );
} );
final AuditedEntity merged = scope.fromTransaction( session, s -> s.merge( saved ) );
assertThat( merged ).isNotNull();
assertThat( merged.createdOn ).isNotNull();
@ -63,9 +75,7 @@ public class InVmGenerationsWithAnnotationsWithSqlDateTests {
assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn );
// lastly, make sure we can load it..
final AuditedEntity loaded = scope.fromTransaction( session, (s) -> {
return session.get( AuditedEntity.class, 1 );
} );
final AuditedEntity loaded = scope.fromTransaction( session, s -> s.get( AuditedEntity.class, 1 ) );
assertThat( loaded ).isNotNull();
assertThat( loaded.createdOn ).isEqualTo( merged.createdOn );
@ -98,13 +108,4 @@ public class InVmGenerationsWithAnnotationsWithSqlDateTests {
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.Table;
import org.hibernate.HibernateError;
import org.hibernate.annotations.CreationTimestamp;
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.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
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.BeforeEach;
import org.junit.jupiter.api.Test;
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 )
@SessionFactory
@ServiceRegistry(settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class))
public class InVmGenerationsWithMultipleAnnotationsTests {
private MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@Test
public void testGenerations(SessionFactoryScope scope) {
scope.inSession( (session) -> {
// first creation
final AuditedEntity saved = scope.fromTransaction( session, (s) -> {
final AuditedEntity saved = scope.fromTransaction( session, s -> {
final AuditedEntity entity = new AuditedEntity( 1, "it" );
session.persist( entity );
s.persist( entity );
return entity;
} );
@ -50,14 +65,10 @@ public class InVmGenerationsWithMultipleAnnotationsTests {
assertThat( saved.lastUpdatedOn ).isNotNull();
saved.name = "changed";
//We need to wait a little to make sure the timestamps produced are different
waitALittle();
clock.tick();
// then changing
final AuditedEntity merged = scope.fromTransaction( session, (s) -> {
return (AuditedEntity) session.merge( saved );
} );
final AuditedEntity merged = scope.fromTransaction( session, s -> s.merge( saved ) );
assertThat( merged ).isNotNull();
assertThat( merged.createdOn ).isNotNull();
@ -65,9 +76,7 @@ public class InVmGenerationsWithMultipleAnnotationsTests {
assertThat( merged.lastUpdatedOn ).isNotEqualTo( merged.createdOn );
// lastly, make sure we can load it..
final AuditedEntity loaded = scope.fromTransaction( session, (s) -> {
return session.get( AuditedEntity.class, 1 );
} );
final AuditedEntity loaded = scope.fromTransaction( session, s -> s.get( AuditedEntity.class, 1 ) );
assertThat( loaded ).isNotNull();
assertThat( loaded.createdOn ).isEqualTo( merged.createdOn );
@ -110,13 +119,4 @@ public class InVmGenerationsWithMultipleAnnotationsTests {
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;
import org.hibernate.HibernateError;
import org.hibernate.PropertyValueException;
import org.hibernate.Session;
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.Setting;
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.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.testing.orm.junit.SettingProvider;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.EntityManager;
@ -54,11 +58,19 @@ import java.util.List;
@ServiceRegistry(
settings = {
@Setting(name = JAKARTA_HBM2DDL_DATABASE_ACTION, value = "create-drop")
}
},
settingProviders = @SettingProvider(settingName = CurrentTimestampGeneration.CLOCK_SETTING_NAME, provider = MutableClockSettingProvider.class)
)
public class TenantIdTest implements SessionFactoryProducer {
String currentTenant;
MutableClock clock;
@BeforeEach
public void setup(SessionFactoryScope scope) {
clock = CurrentTimestampGeneration.getClock( scope.getSessionFactory() );
clock.reset();
}
@AfterEach
public void cleanup(SessionFactoryScope scope) {
@ -211,8 +223,7 @@ public class TenantIdTest implements SessionFactoryProducer {
assertEquals( "mine", record.state.tenantId );
assertNotNull( record.state.updated );
//We need to wait a little to make sure the timestamps produced are different
waitALittle();
clock.tick();
scope.inTransaction( s -> {
Record r = s.find( Record.class, record.id );
@ -342,13 +353,4 @@ public class TenantIdTest implements SessionFactoryProducer {
JpaRoot<Record> from = criteriaQuery.from( Record.class );
return session.createQuery( criteriaQuery ).getResultList();
}
private static void waitALittle() {
try {
Thread.sleep( 10 );
}
catch (InterruptedException e) {
throw new HibernateError( "Unexpected wakeup from test sleep" );
}
}
}