diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Generated.java b/hibernate-core/src/main/java/org/hibernate/annotations/Generated.java index 405cecc622..7f8b269d74 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Generated.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Generated.java @@ -97,8 +97,12 @@ public @interface Generated { /** * Determines if the value currently assigned to the annotated property * is included in SQL {@code insert} and {@code update} statements. This - * is useful if the generated value is obtained by transforming the - * assigned property value as it is being written. + * is useful if: + *
* Often used in combination with {@link SQLInsert}, {@link SQLUpdate}, * or {@link ColumnTransformer#write()}. diff --git a/hibernate-core/src/main/java/org/hibernate/generator/Generator.java b/hibernate-core/src/main/java/org/hibernate/generator/Generator.java index 447ca1ea05..568140f05e 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/Generator.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/Generator.java @@ -107,12 +107,13 @@ public interface Generator extends Serializable { * @param session The session from which the request originates. * * @return {@code true} if the value is generated by the database as a side effect of - * the execution of an {@code insert} or {@code update} statement, or false if - * it is generated in Java code before the statement is executed via JDBC. + * the execution of an {@code insert} or {@code update} statement, or false if + * it is generated in Java code before the statement is executed via JDBC. * * @see #generatedOnExecution() * @see BeforeExecutionGenerator * @see OnExecutionGenerator + * * @since 6.4 */ default boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) { @@ -143,6 +144,19 @@ public interface Generator extends Serializable { return false; } + /** + * Determine if this generator allows generated fields to be manually assigned a value on + * {@linkplain #getEventTypes events} which do not trigger value generation. + * + * @return {@code true} if this generator allows manually assigned values, + * {@code false} otherwise (default). + * + * @since 7.0 + */ + default boolean allowMutation() { + return false; + } + default boolean generatesSometimes() { return !getEventTypes().isEmpty(); } diff --git a/hibernate-core/src/main/java/org/hibernate/generator/internal/GeneratedGeneration.java b/hibernate-core/src/main/java/org/hibernate/generator/internal/GeneratedGeneration.java index 2fc155401c..7719e0ed6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/internal/GeneratedGeneration.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/internal/GeneratedGeneration.java @@ -4,6 +4,7 @@ */ package org.hibernate.generator.internal; +import org.hibernate.AnnotationException; import org.hibernate.annotations.Generated; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -41,7 +42,10 @@ public class GeneratedGeneration implements OnExecutionGenerator, BeforeExecutio public GeneratedGeneration(Generated annotation) { eventTypes = fromArray( annotation.event() ); sql = isEmpty( annotation.sql() ) ? null : new String[] { annotation.sql() }; - writable = annotation.writable() || sql != null; + writable = annotation.writable(); + if ( sql != null && writable ) { + throw new AnnotationException( "A field marked '@Generated(writable=true)' may not specify explicit 'sql'" ); + } } @Override @@ -51,7 +55,9 @@ public class GeneratedGeneration implements OnExecutionGenerator, BeforeExecutio @Override public boolean referenceColumnsInSql(Dialect dialect) { - return writable; + // include the column in when the field is writable, + // or when there is an explicit SQL expression + return writable || sql != null; } @Override @@ -61,7 +67,9 @@ public class GeneratedGeneration implements OnExecutionGenerator, BeforeExecutio @Override public boolean writePropertyValue() { - return writable && sql==null; + // include a ? parameter when the field is writable, + // but there is no explicit SQL expression + return writable; } @Override @@ -71,13 +79,15 @@ public class GeneratedGeneration implements OnExecutionGenerator, BeforeExecutio @Override public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) { - if ( !writable ) { + if ( writable ) { + // When this is the identifier generator and writable is true, allow pre-assigned identifiers + final EntityPersister entityPersister = session.getEntityPersister( null, entity ); + return entityPersister.getGenerator() != this + || entityPersister.getIdentifier( entity, session ) == null; + } + else { return true; } - - // When this is the identifier generator and writable is true, allow pre-assigned identifiers - final EntityPersister entityPersister = session.getEntityPersister( null, entity ); - return entityPersister.getGenerator() != this || entityPersister.getIdentifier( entity, session ) == null; } @Override @@ -91,4 +101,10 @@ public class GeneratedGeneration implements OnExecutionGenerator, BeforeExecutio public boolean allowAssignedIdentifiers() { return writable; } + + @Override + public boolean allowMutation() { + // the user may specify @Immutable if mutation should be disallowed + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 50199029d6..8e9146e2c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -321,15 +321,12 @@ public class EntityMetamodel implements Serializable { } else { generators[i] = generator; - final boolean noParameter = generatedWithNoParameter( generator ); - if ( noParameter && !generator.allowAssignedIdentifiers() ) { + if ( !generator.allowMutation() ) { propertyInsertability[i] = false; propertyUpdateability[i] = false; } if ( generator.generatesOnInsert() ) { - if ( noParameter ) { - propertyInsertability[i] = false; - } + propertyInsertability[i] = !generatedWithNoParameter( generator ); if ( generator.generatedOnExecution() ) { foundPostInsertGeneratedValues = true; if ( generator instanceof BeforeExecutionGenerator ) { @@ -341,9 +338,7 @@ public class EntityMetamodel implements Serializable { } } if ( generator.generatesOnUpdate() ) { - if ( noParameter ) { - propertyInsertability[i] = false; - } + propertyUpdateability[i] = !generatedWithNoParameter( generator ); if ( generator.generatedOnExecution() ) { foundPostUpdateGeneratedValues = true; if ( generator instanceof BeforeExecutionGenerator ) {