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;
/**
* 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.
* <p>
* {@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.
* <p>
* 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:
* <ul>
* <li>For identity/autoincrement columns mapped to an identifier property,
* use {@link jakarta.persistence.GeneratedValue}.
* <li>For columns with a {@code generated always as} clause, prefer the
* {@link GeneratedColumn} annotation.
* <li>a database table has a column value populated by a database trigger,
* <li>a mapped column has a default value defined in DDL, in which case
* {@code Generated(INSERT)} is used in conjunction with
* {@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>
*
* @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.
* <ul>
* <li>If {@link GenerationTime#INSERT}, the generated value will be selected
* after each SQL {@code INSERT} statement is executed.
* <li>If {@link GenerationTime#ALWAYS}, the generated value will be selected
* after each SQL {@code INSERT} or {@code UPDATE} statement is executed.
* <li>If {@link GenerationTime#INSERT}, the generated value will be
* selected after each SQL {@code insert} statement is executed.
* <li>If {@link GenerationTime#ALWAYS}, the generated value will be
* selected after each SQL {@code insert} or {@code update}
* statement is executed.
* </ul>
*/
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;
}

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
* using {@link jakarta.persistence.Column#insertable insertable=false}.
* <p>
* 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.
* <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,
* 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
*/

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
* using {@link jakarta.persistence.Column#updatable() updatable=false}.
* <p>
* 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.
* <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,
* 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
*/

View File

@ -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;

View File

@ -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<Gener
private GenerationTiming timing;
private boolean writable;
private String sql;
public GeneratedValueGeneration() {
}
@ -29,7 +32,8 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration<Gener
@Override
public void initialize(Generated annotation, Class<?> 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<Gener
@Override
public String getDatabaseGeneratedReferencedColumnValue() {
return null;
return sql;
}
}

View File

@ -9,8 +9,18 @@ package org.hibernate.tuple;
import java.io.Serializable;
/**
* Describes the generation of values of a certain property of an entity. Property values might
* be generated in Java, or by the database.
* Describes the generation of values of a certain field or property of an entity. A generated
* 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.Generated
@ -97,8 +107,10 @@ public interface ValueGeneration extends Serializable {
* <li>{@link #referenceColumnInSql()} is {@code true} and
* {@link #getDatabaseGeneratedReferencedColumnValue()} returns {@code null}.
* </ul>
*
* @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;