HHH-15663 add @Generated(sql=....)

This commit is contained in:
Gavin King 2022-11-04 12:12:16 +01:00
parent f6e65dc91a
commit cea6774f01
6 changed files with 88 additions and 37 deletions

View File

@ -14,23 +14,35 @@ import java.lang.annotation.Target;
import org.hibernate.tuple.GeneratedValueGeneration; import org.hibernate.tuple.GeneratedValueGeneration;
/** /**
* Specifies that the value of the annotated property is generated by the database. * Specifies that the value of the annotated property is generated by the
* The generated value will be automatically retrieved using a SQL {@code SELECT} * database. The generated value will be automatically retrieved using a
* after it is generated. * SQL {@code select} after it is generated.
* <p> * <p>
* {@code Generated} relieves the program of the need to call * {@code @Generated} relieves the program of the need to explicitly call
* {@link org.hibernate.Session#refresh(Object)} explicitly to synchronize state * {@link org.hibernate.Session#refresh(Object)} to synchronize state held
* held in memory with state generated by the database when a SQL {@code INSERT} * in memory with state generated by the database when a SQL {@code insert}
* or {@code UPDATE} is executed. * or {@code update} is executed.
* <p> * <p>
* This is most useful for working with database tables where a column value is * This is most useful when:
* populated by a database trigger. A second possible scenario is the use of
* {@code Generated(INSERT)} with {@link ColumnDefault}.
* <ul> * <ul>
* <li>For identity/autoincrement columns mapped to an identifier property, * <li>a database table has a column value populated by a database trigger,
* use {@link jakarta.persistence.GeneratedValue}. * <li>a mapped column has a default value defined in DDL, in which case
* <li>For columns with a {@code generated always as} clause, prefer the * {@code Generated(INSERT)} is used in conjunction with
* {@link GeneratedColumn} annotation. * {@link ColumnDefault},
* <li>a {@linkplain #sql() SQL expression} is used to compute the value of
* a mapped column, or
* <li>when a custom SQL {@link SQLInsert insert} or {@link SQLUpdate update}
* statement specified by an entity assigns a value to the annotated
* property of the entity, or {@linkplain #writable() transforms} the
* value currently assigned to the annotated property.
* </ul>
* On the other hand:
* <ul>
* <li>for identity/autoincrement columns mapped to an identifier property,
* use {@link jakarta.persistence.GeneratedValue}, and
* <li>for columns with a {@code generated always as} clause, prefer the
* {@link GeneratedColumn} annotation, so that Hibernate automatically
* generates the correct DDL.
* </ul> * </ul>
* *
* @author Emmanuel Bernard * @author Emmanuel Bernard
@ -44,22 +56,33 @@ import org.hibernate.tuple.GeneratedValueGeneration;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Generated { 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.
* <ul> * <ul>
* <li>If {@link GenerationTime#INSERT}, the generated value will be selected * <li>If {@link GenerationTime#INSERT}, the generated value will be
* after each SQL {@code INSERT} statement is executed. * selected after each SQL {@code insert} statement is executed.
* <li>If {@link GenerationTime#ALWAYS}, the generated value will be selected * <li>If {@link GenerationTime#ALWAYS}, the generated value will be
* after each SQL {@code INSERT} or {@code UPDATE} statement is executed. * selected after each SQL {@code insert} or {@code update}
* statement is executed.
* </ul> * </ul>
*/ */
GenerationTime value(); GenerationTime value();
/** /**
* Determines if the column mapped by the annotated property is included in SQL * A SQL expression used to generate the value of the column mapped by
* {@code INSERT} and {@code UPDATE} statements. By default, it is excluded. * 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 * @return {@code true} if the current value should be included in SQL
* {@code INSERT} and {@code UPDATE} statements. * {@code insert} and {@code update} statements.
*/ */
boolean writable() default false; boolean writable() default false;
} }

View File

@ -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 * and has no corresponding JDBC parameter in the custom SQL, it must be mapped
* using {@link jakarta.persistence.Column#insertable insertable=false}. * using {@link jakarta.persistence.Column#insertable insertable=false}.
* <p> * <p>
* A custom SQL insert statement might transform the column values as they * A custom SQL insert statement might assign a value to a mapped column as it
* are written. In this case, the state of the entity held in memory loses * is written. In this case, the corresponding property of the entity remains
* synchronization with the database after the insert is executed unless * 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.
* <p>
* 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, * {@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 * @author Laszlo Benke
*/ */

View File

@ -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 * and has no corresponding JDBC parameter in the custom SQL, it must be mapped
* using {@link jakarta.persistence.Column#updatable() updatable=false}. * using {@link jakarta.persistence.Column#updatable() updatable=false}.
* <p> * <p>
* A custom SQL update statement might transform the column values as they * A custom SQL update statement might assign a value to a mapped column as it
* are written. In this case, the state of the entity held in memory loses * is written. In this case, the corresponding property of the entity remains
* synchronization with the database after the update is executed unless * 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.
* <p>
* 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, * {@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 * @author Laszlo Benke
*/ */

View File

@ -390,7 +390,7 @@ public class PropertyBinder {
return NoValueGeneration.INSTANCE; return NoValueGeneration.INSTANCE;
} }
if ( !valueGeneration.writeColumn() ) { if ( !valueGeneration.writePropertyValue() ) {
// if we have an in-db generator, mark it as not insertable nor updatable // if we have an in-db generator, mark it as not insertable nor updatable
insertable = false; insertable = false;
updatable = false; updatable = false;

View File

@ -8,6 +8,8 @@ package org.hibernate.tuple;
import org.hibernate.annotations.Generated; 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. * A {@link AnnotationValueGeneration} which marks a property as generated in the database.
* *
@ -18,6 +20,7 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration<Gener
private GenerationTiming timing; private GenerationTiming timing;
private boolean writable; private boolean writable;
private String sql;
public GeneratedValueGeneration() { public GeneratedValueGeneration() {
} }
@ -29,7 +32,8 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration<Gener
@Override @Override
public void initialize(Generated annotation, Class<?> propertyType) { public void initialize(Generated annotation, Class<?> propertyType) {
timing = annotation.value().getEquivalent(); timing = annotation.value().getEquivalent();
writable = annotation.writable(); sql = isEmpty( annotation.sql() ) ? null : annotation.sql();
writable = annotation.writable() || sql != null;
} }
@Override @Override
@ -50,7 +54,7 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration<Gener
@Override @Override
public String getDatabaseGeneratedReferencedColumnValue() { public String getDatabaseGeneratedReferencedColumnValue() {
return null; return sql;
} }
} }

View File

@ -9,8 +9,18 @@ package org.hibernate.tuple;
import java.io.Serializable; import java.io.Serializable;
/** /**
* Describes the generation of values of a certain property of an entity. Property values might * Describes the generation of values of a certain field or property of an entity. A generated
* be generated in Java, or by the database. * value might be generated in Java, or by the database.
* <ul>
* <li>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.
* <li>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}.
* </ul>
* *
* @see org.hibernate.annotations.ValueGenerationType * @see org.hibernate.annotations.ValueGenerationType
* @see org.hibernate.annotations.Generated * @see org.hibernate.annotations.Generated
@ -97,8 +107,10 @@ public interface ValueGeneration extends Serializable {
* <li>{@link #referenceColumnInSql()} is {@code true} and * <li>{@link #referenceColumnInSql()} is {@code true} and
* {@link #getDatabaseGeneratedReferencedColumnValue()} returns {@code null}. * {@link #getDatabaseGeneratedReferencedColumnValue()} returns {@code null}.
* </ul> * </ul>
*
* @see org.hibernate.annotations.Generated#writable()
*/ */
default boolean writeColumn() { default boolean writePropertyValue() {
return !generatedByDatabase() // value generated in memory and then written as normal return !generatedByDatabase() // value generated in memory and then written as normal
// current value of property of entity instance written completely as normal // current value of property of entity instance written completely as normal
|| referenceColumnInSql() && getDatabaseGeneratedReferencedColumnValue()==null; || referenceColumnInSql() && getDatabaseGeneratedReferencedColumnValue()==null;