From 94349ce6648b3d28673c819247ed278702976c6b Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 2 Jul 2025 15:04:06 +0200 Subject: [PATCH] HHH-19584 add @CurrentTimestamp(allowMutation) --- .../annotations/CurrentTimestamp.java | 13 ++++++ .../internal/CurrentTimestampAnnotation.java | 11 +++++ .../internal/CurrentTimestampGeneration.java | 43 ++++++++++++------- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/CurrentTimestamp.java b/hibernate-core/src/main/java/org/hibernate/annotations/CurrentTimestamp.java index 5bcbad1d3358..b9440a41a975 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/CurrentTimestamp.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/CurrentTimestamp.java @@ -85,4 +85,17 @@ * this additional {@code select} never occurs. */ SourceType source() default SourceType.DB; + + /** + * Specifies whether the value may be mutated by the application program + * during {@linkplain #event events} which do not trigger value + * generation. + *

+ * For example, a field annotated + * {@code CurrentTimestamp(event=INSERT, allowMutation=true)} + * is generated when a record is inserted and may be manually mutated later. + * + * @since 7.1 + */ + boolean allowMutation() default false; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/CurrentTimestampAnnotation.java b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/CurrentTimestampAnnotation.java index 77e34a37c94d..2f8ccfa5375d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/CurrentTimestampAnnotation.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/CurrentTimestampAnnotation.java @@ -18,6 +18,7 @@ public class CurrentTimestampAnnotation implements CurrentTimestamp { private org.hibernate.generator.EventType[] event; private org.hibernate.annotations.SourceType source; + private boolean allowMutation; /** * Used in creating dynamic annotation instances (e.g. from XML) @@ -25,6 +26,7 @@ public class CurrentTimestampAnnotation implements CurrentTimestamp { public CurrentTimestampAnnotation(ModelsContext modelContext) { this.event = new org.hibernate.generator.EventType[] {INSERT, UPDATE}; this.source = org.hibernate.annotations.SourceType.DB; + this.allowMutation = false; } /** @@ -33,6 +35,7 @@ public CurrentTimestampAnnotation(ModelsContext modelContext) { public CurrentTimestampAnnotation(CurrentTimestamp annotation, ModelsContext modelContext) { this.event = annotation.event(); this.source = annotation.source(); + this.allowMutation = annotation.allowMutation(); } /** @@ -41,6 +44,7 @@ public CurrentTimestampAnnotation(CurrentTimestamp annotation, ModelsContext mod public CurrentTimestampAnnotation(Map attributeValues, ModelsContext modelContext) { this.event = (org.hibernate.generator.EventType[]) attributeValues.get( "event" ); this.source = (org.hibernate.annotations.SourceType) attributeValues.get( "source" ); + this.allowMutation = (Boolean) attributeValues.get( "allowMutation" ); } @Override @@ -66,5 +70,12 @@ public void source(org.hibernate.annotations.SourceType value) { this.source = value; } + @Override + public boolean allowMutation() { + return allowMutation; + } + public void allowMutation(boolean value) { + this.allowMutation = value; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java b/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java index 86bebe98ba9e..60e9dfc248fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java @@ -79,6 +79,7 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE public static final String CLOCK_SETTING_NAME = "hibernate.testing.clock"; private final EnumSet eventTypes; + private final boolean allowMutation; private final CurrentTimestampGeneratorDelegate delegate; private static final Map, BiFunction<@Nullable Clock, Integer, CurrentTimestampGeneratorDelegate>> GENERATOR_PRODUCERS = new HashMap<>(); @@ -185,16 +186,19 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE public CurrentTimestampGeneration(CurrentTimestamp annotation, Member member, GeneratorCreationContext context) { delegate = getGeneratorDelegate( annotation.source(), member, context ); eventTypes = fromArray( annotation.event() ); + allowMutation = annotation.allowMutation(); } public CurrentTimestampGeneration(CreationTimestamp annotation, Member member, GeneratorCreationContext context) { delegate = getGeneratorDelegate( annotation.source(), member, context ); eventTypes = INSERT_ONLY; + allowMutation = false; } public CurrentTimestampGeneration(UpdateTimestamp annotation, Member member, GeneratorCreationContext context) { delegate = getGeneratorDelegate( annotation.source(), member, context ); eventTypes = INSERT_AND_UPDATE; + allowMutation = false; } private static CurrentTimestampGeneratorDelegate getGeneratorDelegate( @@ -216,24 +220,27 @@ static CurrentTimestampGeneratorDelegate getGeneratorDelegate( 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 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 var delegate = GENERATOR_DELEGATES.get( key ); if ( delegate != null ) { return delegate; } - final BiFunction<@Nullable Clock, Integer, CurrentTimestampGeneratorDelegate> producer = GENERATOR_PRODUCERS.get( key.clazz ); - if ( producer == null ) { - return null; + else { + final var producer = GENERATOR_PRODUCERS.get( key.clazz ); + if ( producer == null ) { + return null; + } + else { + final var generatorDelegate = producer.apply( key.clock, key.precision ); + final var old = GENERATOR_DELEGATES.putIfAbsent( key, generatorDelegate ); + return old != null ? old : generatorDelegate; + } } - final CurrentTimestampGeneratorDelegate generatorDelegate = producer.apply( key.clock, key.precision ); - final CurrentTimestampGeneratorDelegate old = GENERATOR_DELEGATES.putIfAbsent( - key, - generatorDelegate - ); - return old != null ? old : generatorDelegate; case DB: return null; default: @@ -255,6 +262,11 @@ public EnumSet getEventTypes() { return eventTypes; } + @Override + public boolean allowMutation() { + return allowMutation; + } + @Override public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) { return delegate.generate(); @@ -276,7 +288,8 @@ public String[] getReferencedColumnValues(Dialect dialect) { } interface CurrentTimestampGeneratorDelegate { - // Left out the Generator params, they're not used anyway. Since this is purely internal, this can be changed if needed + // Left out the Generator params, they're not used anyway. + // Since this is purely internal, this can be changed if needed Object generate(); }