From cea6774f013a3fcddf62e70bfd7bf70bfd22621c Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 4 Nov 2022 12:12:16 +0100 Subject: [PATCH] HHH-15663 add @Generated(sql=....) --- .../org/hibernate/annotations/Generated.java | 69 ++++++++++++------- .../org/hibernate/annotations/SQLInsert.java | 14 ++-- .../org/hibernate/annotations/SQLUpdate.java | 14 ++-- .../cfg/annotations/PropertyBinder.java | 2 +- .../tuple/GeneratedValueGeneration.java | 8 ++- .../org/hibernate/tuple/ValueGeneration.java | 18 ++++- 6 files changed, 88 insertions(+), 37 deletions(-) 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 2d92165c82..ccdb252ca2 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Generated.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Generated.java @@ -14,23 +14,35 @@ import java.lang.annotation.Target; import org.hibernate.tuple.GeneratedValueGeneration; /** - * Specifies that the value of the annotated property is generated by the database. - * The generated value will be automatically retrieved using a SQL {@code SELECT} - * after it is generated. + * Specifies that the value of the annotated property is generated by the + * database. The generated value will be automatically retrieved using a + * SQL {@code select} after it is generated. *

- * {@code Generated} relieves the program of the need to call - * {@link org.hibernate.Session#refresh(Object)} explicitly to synchronize state - * held in memory with state generated by the database when a SQL {@code INSERT} - * or {@code UPDATE} is executed. + * {@code @Generated} relieves the program of the need to explicitly call + * {@link org.hibernate.Session#refresh(Object)} to synchronize state held + * in memory with state generated by the database when a SQL {@code insert} + * or {@code update} is executed. *

- * This is most useful for working with database tables where a column value is - * populated by a database trigger. A second possible scenario is the use of - * {@code Generated(INSERT)} with {@link ColumnDefault}. + * This is most useful when: *

+ * On the other hand: + * * * @author Emmanuel Bernard @@ -44,22 +56,33 @@ import org.hibernate.tuple.GeneratedValueGeneration; @Retention(RetentionPolicy.RUNTIME) public @interface Generated { /** - * Specifies the events that cause the value to be generated by the database. + * Specifies the events that cause the value to be generated by the + * database. * */ GenerationTime value(); /** - * Determines if the column mapped by the annotated property is included in SQL - * {@code INSERT} and {@code UPDATE} statements. By default, it is excluded. + * A SQL expression used to generate the value of the column mapped by + * the annotated property. The expression is included in generated SQL + * {@code insert} and {@code update} statements. + */ + String sql() default ""; + + /** + * 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. * - * @return {@code true} if the mapped column should be included in SQL - * {@code INSERT} and {@code UPDATE} statements. + * @return {@code true} if the current value should be included in SQL + * {@code insert} and {@code update} statements. */ boolean writable() default false; } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/SQLInsert.java b/hibernate-core/src/main/java/org/hibernate/annotations/SQLInsert.java index ed2a2ca606..28fb835e12 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/SQLInsert.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/SQLInsert.java @@ -29,11 +29,17 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * and has no corresponding JDBC parameter in the custom SQL, it must be mapped * using {@link jakarta.persistence.Column#insertable insertable=false}. *

- * A custom SQL insert statement might transform the column values as they - * are written. In this case, the state of the entity held in memory loses - * synchronization with the database after the insert is executed unless + * A custom SQL insert statement might assign a value to a mapped column as it + * is written. In this case, the corresponding property of the entity remains + * unassigned after the insert is executed unless + * {@link Generated @Generated(INSERT)} is specified, forcing Hibernate to + * reread the state of the entity after each insert. + *

+ * Similarly, a custom insert statement might transform a mapped column value + * as it is written. In this case, the state of the entity held in memory + * loses synchronization with the database after the insert is executed unless * {@link Generated @Generated(value=INSERT, writable=true)} is specified, - * forcing Hibernate to reread the state of the entity after each insert. + * again forcing Hibernate to reread the state of the entity after each insert. * * @author Laszlo Benke */ diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/SQLUpdate.java b/hibernate-core/src/main/java/org/hibernate/annotations/SQLUpdate.java index 874f247533..78d196f780 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/SQLUpdate.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/SQLUpdate.java @@ -32,11 +32,17 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * and has no corresponding JDBC parameter in the custom SQL, it must be mapped * using {@link jakarta.persistence.Column#updatable() updatable=false}. *

- * A custom SQL update statement might transform the column values as they - * are written. In this case, the state of the entity held in memory loses - * synchronization with the database after the update is executed unless + * A custom SQL update statement might assign a value to a mapped column as it + * is written. In this case, the corresponding property of the entity remains + * unassigned after the update is executed unless + * {@link Generated @Generated(ALWAYS)} is specified, forcing Hibernate to + * reread the state of the entity after each update. + *

+ * Similarly, a custom update statement might transform a mapped column value + * as it is written. In this case, the state of the entity held in memory + * loses synchronization with the database after the update is executed unless * {@link Generated @Generated(value=ALWAYS, writable=true)} is specified, - * forcing Hibernate to reread the state of the entity after each update. + * again forcing Hibernate to reread the state of the entity after each update. * * @author Laszlo Benke */ diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java index 7f7afe973d..644d476481 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java @@ -390,7 +390,7 @@ public class PropertyBinder { return NoValueGeneration.INSTANCE; } - if ( !valueGeneration.writeColumn() ) { + if ( !valueGeneration.writePropertyValue() ) { // if we have an in-db generator, mark it as not insertable nor updatable insertable = false; updatable = false; diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/GeneratedValueGeneration.java b/hibernate-core/src/main/java/org/hibernate/tuple/GeneratedValueGeneration.java index e508d2352e..6c4caafb58 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/GeneratedValueGeneration.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/GeneratedValueGeneration.java @@ -8,6 +8,8 @@ package org.hibernate.tuple; import org.hibernate.annotations.Generated; +import static org.hibernate.internal.util.StringHelper.isEmpty; + /** * A {@link AnnotationValueGeneration} which marks a property as generated in the database. * @@ -18,6 +20,7 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration propertyType) { timing = annotation.value().getEquivalent(); - writable = annotation.writable(); + sql = isEmpty( annotation.sql() ) ? null : annotation.sql(); + writable = annotation.writable() || sql != null; } @Override @@ -50,7 +54,7 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration + *

  • Java value generation is the responsibility of an associated {@link ValueGenerator}. + * In this case, the generated value is written to the database just like any other field + * or property value. + *
  • A value generated by the database might be generated implicitly, by a trigger, or using + * a {@code default} column value specified in DDL, for example, or it might be generated + * by a SQL expression occurring explicitly in the SQL {@code insert} or {@code update} + * statement. In this case, the generated value is retrieved from the database using a SQL + * {@code select}. + * * * @see org.hibernate.annotations.ValueGenerationType * @see org.hibernate.annotations.Generated @@ -97,8 +107,10 @@ public interface ValueGeneration extends Serializable { *
  • {@link #referenceColumnInSql()} is {@code true} and * {@link #getDatabaseGeneratedReferencedColumnValue()} returns {@code null}. * + * + * @see org.hibernate.annotations.Generated#writable() */ - default boolean writeColumn() { + default boolean writePropertyValue() { return !generatedByDatabase() // value generated in memory and then written as normal // current value of property of entity instance written completely as normal || referenceColumnInSql() && getDatabaseGeneratedReferencedColumnValue()==null;