Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.hibernate.generator.EventType.FORCE_INCREMENT;
import static org.hibernate.generator.EventType.INSERT;
import static org.hibernate.generator.EventType.UPDATE;

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

/**
* Specifies how the timestamp is generated. By default, it is generated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ else if ( propertyType instanceof ComponentType componentType ) {
else if ( propertyType instanceof CollectionType collectionType ) {
// persistent collections may have components
if ( collectionType.getElementType( session.getFactory() ) instanceof CompositeType componentType ) {
// check for all components values in the collection
// check for all component's values in the collection
final Iterator<?> iterator = getLoadedElementsIterator( collectionType, value );
while ( iterator.hasNext() ) {
final Object compositeElement = iterator.next();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
package org.hibernate.generator;

import org.hibernate.Incubating;

/**
* Enumerates event types that can result in generation of a new value.
* A {@link Generator} must specify which events it responds to, by
Expand Down Expand Up @@ -35,5 +37,12 @@ public enum EventType {
* This indicates, for example, that a version number should be
* incremented.
*/
UPDATE
UPDATE,
/**
* An event that occurs during verification of a lock of type
* of {@link org.hibernate.LockMode#OPTIMISTIC_FORCE_INCREMENT}
* or {@link org.hibernate.LockMode#PESSIMISTIC_FORCE_INCREMENT}.
*/
@Incubating
FORCE_INCREMENT
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
*/
package org.hibernate.generator;

import org.hibernate.Incubating;
import org.hibernate.engine.spi.SharedSessionContractImplementor;

import java.io.Serializable;
import java.util.EnumSet;

import static org.hibernate.generator.EventType.FORCE_INCREMENT;
import static org.hibernate.generator.EventType.INSERT;
import static org.hibernate.generator.EventType.UPDATE;

Expand Down Expand Up @@ -183,7 +185,8 @@ default boolean allowMutation() {
}

default boolean generatesSometimes() {
return !getEventTypes().isEmpty();
return generatesOnInsert()
|| generatesOnUpdate();
}

default boolean generatesOnInsert() {
Expand All @@ -193,4 +196,9 @@ default boolean generatesOnInsert() {
default boolean generatesOnUpdate() {
return getEventTypes().contains(UPDATE);
}

@Incubating
default boolean generatesOnForceIncrement() {
return getEventTypes().contains(FORCE_INCREMENT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
package org.hibernate.generator.internal;

import java.lang.reflect.Member;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Clock;
Expand All @@ -26,7 +30,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;

import org.hibernate.AssertionFailure;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.CurrentTimestamp;
Expand All @@ -35,6 +38,8 @@
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.StatementPreparer;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.EventType;
Expand All @@ -45,7 +50,10 @@
import org.hibernate.type.descriptor.java.ClockHelper;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.type.descriptor.java.JavaType;

import static java.sql.Types.TIMESTAMP;
import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_MESSAGE_LOGGER;
import static org.hibernate.generator.EventTypeSets.INSERT_AND_UPDATE;
import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;
import static org.hibernate.generator.EventTypeSets.fromArray;
Expand Down Expand Up @@ -80,6 +88,8 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE

private final EnumSet<EventType> eventTypes;

private final JavaType<Object> propertyType;

private final CurrentTimestampGeneratorDelegate delegate;
private static final Map<Class<?>, BiFunction<@Nullable Clock, Integer, CurrentTimestampGeneratorDelegate>> GENERATOR_PRODUCERS = new HashMap<>();
private static final Map<Key, CurrentTimestampGeneratorDelegate> GENERATOR_DELEGATES = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -182,19 +192,27 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE
);
}

private static JavaType<Object> getPropertyType(GeneratorCreationContext context) {
return context.getDatabase().getTypeConfiguration().getJavaTypeRegistry()
.getDescriptor( context.getProperty().getType().getReturnedClass() );
}

public CurrentTimestampGeneration(CurrentTimestamp annotation, Member member, GeneratorCreationContext context) {
delegate = getGeneratorDelegate( annotation.source(), member, context );
eventTypes = fromArray( annotation.event() );
propertyType = getPropertyType( context );
}

public CurrentTimestampGeneration(CreationTimestamp annotation, Member member, GeneratorCreationContext context) {
delegate = getGeneratorDelegate( annotation.source(), member, context );
eventTypes = INSERT_ONLY;
propertyType = getPropertyType( context );
}

public CurrentTimestampGeneration(UpdateTimestamp annotation, Member member, GeneratorCreationContext context) {
delegate = getGeneratorDelegate( annotation.source(), member, context );
eventTypes = INSERT_AND_UPDATE;
propertyType = getPropertyType( context );
}

private static CurrentTimestampGeneratorDelegate getGeneratorDelegate(
Expand All @@ -208,36 +226,42 @@ static CurrentTimestampGeneratorDelegate getGeneratorDelegate(
SourceType source,
Class<?> propertyType,
GeneratorCreationContext context) {
switch (source) {
case VM:
return switch (source) {
case DB -> null;
case VM -> {
// Generator is only used for in-VM generation
final BasicValue basicValue = (BasicValue) context.getProperty().getValue();
final Size size = basicValue.getColumns().get( 0 ).getColumnSize(
context.getDatabase().getDialect(),
basicValue.getMetadata()
);
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 Key key = new Key( propertyType, getBaseClock( context ), getPrecision( context ) );
final var delegate = GENERATOR_DELEGATES.get( key );
if ( delegate != null ) {
return delegate;
}
final var producer = GENERATOR_PRODUCERS.get( key.clazz );
if ( producer == null ) {
return null;
yield delegate;
}
else {
final var generatorDelegate = producer.apply( key.clock, key.precision );
final var old = GENERATOR_DELEGATES.putIfAbsent( key, generatorDelegate );
return old != null ? old : generatorDelegate;
final var producer = GENERATOR_PRODUCERS.get( key.clazz );
if ( producer == null ) {
yield null;
}
else {
final var generatorDelegate = producer.apply( key.clock, key.precision );
final var old = GENERATOR_DELEGATES.putIfAbsent( key, generatorDelegate );
yield old != null ? old : generatorDelegate;
}
}
case DB:
return null;
default:
throw new AssertionFailure("unknown source");
}
}
};
}

private static int getPrecision(GeneratorCreationContext context) {
final BasicValue basicValue = (BasicValue) context.getProperty().getValue();
final Size size =
basicValue.getColumns().get( 0 )
.getColumnSize( context.getDatabase().getDialect(),
basicValue.getMetadata() );
return size.getPrecision() == null ? 0 : size.getPrecision();
}

private static Clock getBaseClock(GeneratorCreationContext context) {
return context.getServiceRegistry().requireService( ConfigurationService.class )
.getSetting( CLOCK_SETTING_NAME, value -> (Clock) value );
}

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

@Override
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
return delegate.generate();
if ( delegate == null ) {
if ( eventType != EventType.FORCE_INCREMENT ) {
throw new UnsupportedOperationException( "CurrentTimestampGeneration.generate() should not have been called" );
}
return propertyType.wrap( getCurrentTimestamp( session ), session );
}
else {
return delegate.generate();
}
}

@Override
Expand All @@ -281,4 +313,51 @@ interface CurrentTimestampGeneratorDelegate {

private record Key(Class<?> clazz, @Nullable Clock clock, int precision) {
}

static Timestamp getCurrentTimestamp(SharedSessionContractImplementor session) {
final Dialect dialect = session.getJdbcServices().getJdbcEnvironment().getDialect();
final boolean callable = dialect.isCurrentTimestampSelectStringCallable();
final String timestampSelectString = dialect.getCurrentTimestampSelectString();
final JdbcCoordinator coordinator = session.getJdbcCoordinator();
final StatementPreparer statementPreparer = coordinator.getStatementPreparer();
PreparedStatement statement = null;
try {
statement = statementPreparer.prepareStatement( timestampSelectString, callable );
final Timestamp ts = callable
? extractCalledResult( statement, coordinator, timestampSelectString )
: extractResult( statement, coordinator, timestampSelectString );
if ( JDBC_MESSAGE_LOGGER.isTraceEnabled() ) {
JDBC_MESSAGE_LOGGER.currentTimestampRetrievedFromDatabase( ts, ts.getNanos(), ts.getTime() );
}
return ts;
}
catch (SQLException e) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
e,
"could not obtain current timestamp from database",
timestampSelectString
);
}
finally {
if ( statement != null ) {
coordinator.getLogicalConnection().getResourceRegistry().release( statement );
coordinator.afterStatementExecution();
}
}
}

static Timestamp extractResult(PreparedStatement statement, JdbcCoordinator coordinator, String sql)
throws SQLException {
final ResultSet resultSet = coordinator.getResultSetReturn().extract( statement, sql );
resultSet.next();
return resultSet.getTimestamp( 1 );
}

static Timestamp extractCalledResult(PreparedStatement statement, JdbcCoordinator coordinator, String sql)
throws SQLException {
final CallableStatement callable = (CallableStatement) statement;
callable.registerOutParameter( 1, TIMESTAMP );
coordinator.getResultSetReturn().execute( callable, sql );
return callable.getTimestamp( 1 );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import org.hibernate.annotations.Source;
import org.hibernate.annotations.SourceType;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.StatementPreparer;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.generator.EventTypeSets;
Expand All @@ -19,16 +17,9 @@


import java.lang.reflect.Member;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.EnumSet;

import static java.sql.Types.TIMESTAMP;
import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_MESSAGE_LOGGER;
import static org.hibernate.generator.EventTypeSets.INSERT_AND_UPDATE;
import static org.hibernate.generator.internal.CurrentTimestampGeneration.getCurrentTimestamp;

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

/**
* @return {@link EventTypeSets#INSERT_ONLY}
* @return {@link EventTypeSets#ALL}
*/
@Override
public EnumSet<EventType> getEventTypes() {
return INSERT_AND_UPDATE;
return EventTypeSets.ALL;
}

@Override
Expand All @@ -76,52 +67,4 @@ public Object generate(SharedSessionContractImplementor session, Object owner, O
? propertyType.wrap( getCurrentTimestamp( session ), session )
: valueGenerator.generate();
}

private Timestamp getCurrentTimestamp(SharedSessionContractImplementor session) {
final Dialect dialect = session.getJdbcServices().getJdbcEnvironment().getDialect();
final boolean callable = dialect.isCurrentTimestampSelectStringCallable();
final String timestampSelectString = dialect.getCurrentTimestampSelectString();
final JdbcCoordinator coordinator = session.getJdbcCoordinator();
final StatementPreparer statementPreparer = coordinator.getStatementPreparer();
PreparedStatement statement = null;
try {
statement = statementPreparer.prepareStatement( timestampSelectString, callable );
final Timestamp ts = callable
? extractCalledResult( statement, coordinator, timestampSelectString )
: extractResult( statement, coordinator, timestampSelectString );
if ( JDBC_MESSAGE_LOGGER.isTraceEnabled() ) {
JDBC_MESSAGE_LOGGER.currentTimestampRetrievedFromDatabase( ts, ts.getNanos(), ts.getTime() );
}
return ts;
}
catch (SQLException e) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
e,
"could not obtain current timestamp from database",
timestampSelectString
);
}
finally {
if ( statement != null ) {
coordinator.getLogicalConnection().getResourceRegistry().release( statement );
coordinator.afterStatementExecution();
}
}
}

private static Timestamp extractResult(PreparedStatement statement, JdbcCoordinator coordinator, String sql)
throws SQLException {
final ResultSet resultSet = coordinator.getResultSetReturn().extract( statement, sql );
resultSet.next();
return resultSet.getTimestamp( 1 );
}

private static Timestamp extractCalledResult(PreparedStatement statement, JdbcCoordinator coordinator, String sql)
throws SQLException {
final CallableStatement callable = (CallableStatement) statement;
callable.registerOutParameter( 1, TIMESTAMP );
coordinator.getResultSetReturn().execute( callable, sql );
return callable.getTimestamp( 1 );
}

}
Loading
Loading