Skip to content

Commit

Permalink
HHH-15663 add @generated(sql=....)
Browse files Browse the repository at this point in the history
  • Loading branch information
gavinking committed Nov 4, 2022
1 parent f6e65dc commit cea6774
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,35 @@
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
Expand All @@ -44,22 +56,33 @@
@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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,17 @@
* 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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@
* 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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ private ValueGeneration determineValueGenerationStrategy(XProperty property) {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

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.
*
Expand All @@ -18,6 +20,7 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration<Gener

private GenerationTiming timing;
private boolean writable;
private String sql;

public GeneratedValueGeneration() {
}
Expand All @@ -29,7 +32,8 @@ public GeneratedValueGeneration(GenerationTiming timing) {
@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
Expand All @@ -50,7 +54,7 @@ public boolean referenceColumnInSql() {

@Override
public String getDatabaseGeneratedReferencedColumnValue() {
return null;
return sql;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@
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
Expand Down Expand Up @@ -97,8 +107,10 @@ default boolean generatedByDatabase() {
* <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;
Expand Down

0 comments on commit cea6774

Please sign in to comment.