HHH-15663 add writable member to @Generated annotation

This is useful if you're using custom SQL, e.g. @SqlInsert.

Also improve the Javadoc surrounding all this stuff.
This commit is contained in:
Gavin King 2022-11-04 11:21:18 +01:00
parent aef9ab2425
commit 383ffa56eb
11 changed files with 141 additions and 39 deletions

View File

@ -53,4 +53,13 @@ public @interface Generated {
* </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.
*
* @return {@code true} if the mapped column should be included in SQL
* {@code INSERT} and {@code UPDATE} statements.
*/
boolean writable() default false;
}

View File

@ -16,10 +16,16 @@ import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Specifies a custom SQL DML statement to be used in place of the default SQL generated by
* Hibernate when an entity or collection row is deleted from the database.
* Specifies a custom SQL DML statement to be used in place of the default SQL
* generated by Hibernate when an entity or collection row is deleted from the
* database.
* <p>
* The given {@link #sql SQL statement} must have exactly the number of JDBC
* {@code ?} parameters that Hibernate expects, in the exact order Hibernate
* expects. The primary key columns come before the version column if the
* entity is versioned.
*
* @author L<EFBFBD>szl<EFBFBD> Benke
* @author Laszlo Benke
*/
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)

View File

@ -16,10 +16,26 @@ import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Specifies a custom SQL DML statement to be used in place of the default SQL generated by
* Hibernate when an entity or collection row is inserted in the database.
* Specifies a custom SQL DML statement to be used in place of the default SQL
* generated by Hibernate when an entity or collection row is inserted in the
* database.
* <p>
* The given {@link #sql SQL statement} must have exactly the number of JDBC
* {@code ?} parameters that Hibernate expects, that is, one for each column
* mapped by the entity, in the exact order Hibernate expects. In particular,
* the {@link jakarta.persistence.Id primary key} columns must come last.
* <p>
* If a column should <em>not</em> be written as part of the insert statement,
* 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
* {@link Generated @Generated(value=INSERT, writable=true)} is specified,
* forcing Hibernate to reread the state of the entity after each insert.
*
* @author L<EFBFBD>szl<EFBFBD> Benke
* @author Laszlo Benke
*/
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)

View File

@ -16,10 +16,29 @@ import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Specifies a custom SQL DML statement to be used in place of the default SQL generated by
* Hibernate when an entity or collection row is updated in the database.
* Specifies a custom SQL DML statement to be used in place of the default SQL
* generated by Hibernate when an entity or collection row is updated in the
* database.
* <p>
* The given {@link #sql SQL statement} must have exactly the number of JDBC
* {@code ?} parameters that Hibernate expects, that is, one for each column
* mapped by the entity, in the exact order Hibernate expects. In particular,
* the {@link jakarta.persistence.Id primary key} columns come last unless
* the entity is {@link jakarta.persistence.Version versioned}, in which case
* there must be a second JDBC parameter for the version column, which comes
* after the primary key.
* <p>
* If a column should <em>not</em> be written as part of the update statement,
* 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
* {@link Generated @Generated(value=ALWAYS, writable=true)} is specified,
* forcing Hibernate to reread the state of the entity after each update.
*
* @author L<EFBFBD>szl<EFBFBD> Benke
* @author Laszlo Benke
*/
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)

View File

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

View File

@ -14,7 +14,8 @@ import org.hibernate.engine.spi.Mapping;
import org.hibernate.internal.util.StringHelper;
/**
* A mapping model object representing a unique key constraint on a relational database table.
* A mapping model object representing a {@linkplain jakarta.persistence.UniqueConstraint unique key}
* constraint on a relational database table.
*
* @author Brett Meyer
*/

View File

@ -39,7 +39,7 @@ public interface GeneratedValueResolver {
// value generation which we'll circle back to as we convert write operations to
// use the "runtime mapping" (`org.hibernate.metamodel.mapping`) model
if ( valueGeneration.getValueGenerator() == null ) {
if ( valueGeneration.generatedByDatabase() ) {
// in-db generation (column-default, function, etc)
return new InDatabaseGeneratedValueResolver( requestedTiming, dbSelectionPosition );
}

View File

@ -2937,7 +2937,7 @@ public abstract class AbstractEntityPersister
else {
final ValueGeneration valueGeneration = attributeMapping.getValueGeneration();
if ( valueGeneration.getGenerationTiming().includesUpdate()
&& valueGeneration.getValueGenerator() == null
&& valueGeneration.generatedByDatabase()
&& valueGeneration.referenceColumnInSql() ) {
update.addColumns(
getPropertyColumnNames( index ),
@ -3059,7 +3059,7 @@ public abstract class AbstractEntityPersister
else {
final ValueGeneration valueGeneration = attributeMapping.getValueGeneration();
if ( valueGeneration.getGenerationTiming().includesInsert()
&& valueGeneration.getValueGenerator() == null
&& valueGeneration.generatedByDatabase()
&& valueGeneration.referenceColumnInSql() ) {
insert.addColumns(
getPropertyColumnNames( index ),

View File

@ -17,6 +17,7 @@ import org.hibernate.annotations.Generated;
public class GeneratedValueGeneration implements AnnotationValueGeneration<Generated> {
private GenerationTiming timing;
private boolean writable;
public GeneratedValueGeneration() {
}
@ -27,7 +28,8 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration<Gener
@Override
public void initialize(Generated annotation, Class<?> propertyType) {
this.timing = annotation.value().getEquivalent();
timing = annotation.value().getEquivalent();
writable = annotation.writable();
}
@Override
@ -43,8 +45,7 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration<Gener
@Override
public boolean referenceColumnInSql() {
// historically these columns are not referenced in the SQL
return false;
return writable;
}
@Override

View File

@ -9,48 +9,98 @@ package org.hibernate.tuple;
import java.io.Serializable;
/**
* Describes the generation of property values.
* Describes the generation of values of a certain property of an entity. Property values might
* be generated in Java, or by the database.
*
* @see org.hibernate.annotations.ValueGenerationType
* @see org.hibernate.annotations.Generated
* @see org.hibernate.annotations.GeneratorType
*
* @author Steve Ebersole
*/
public interface ValueGeneration extends Serializable {
/**
* When is this value generated : NEVER, INSERT, ALWAYS (INSERT+UPDATE)
* Specifies that the property value is generated:
* <ul>
* <li>{@linkplain GenerationTiming#INSERT when the entity is inserted},
* <li>{@linkplain GenerationTiming#ALWAYS whenever the entity is inserted or updated}, or
* <li>{@linkplain GenerationTiming#NEVER never}.
* </ul>
*
* @return When the value is generated.
* @return The {@link GenerationTiming} specifying when the value is generated.
*/
GenerationTiming getGenerationTiming();
/**
* Obtain the in-VM value generator.
* <p/>
* May return {@code null}. In fact for values that are generated "in the database" via execution of the
* INSERT/UPDATE statement, the expectation is that {@code null} be returned here
* Obtain the {@linkplain ValueGenerator Java value generator}, if the value is generated in
* Java, or return {@code null} if the value is generated by the database.
*
* @return The strategy for performing in-VM value generation
* @return The value generator
*/
ValueGenerator<?> getValueGenerator();
/**
* For values which are generated in the database ({@link #getValueGenerator()} == {@code null}), should the
* column be referenced in the INSERT / UPDATE SQL?
* <p/>
* This will be false most often to have a DDL-defined DEFAULT value be applied on INSERT
* Determines if the column whose value is generated is included in the column list of the
* SQL {@code insert} or {@code update} statement, in the case where the value is generated
* by the database. For example, this method should return:
* <ul>
* <li>{@code true} if the value is generated by calling a SQL function like
* {@code current_timestamp}, or
* <li>{@code false} if the value is generated by a trigger,
* by {@link org.hibernate.annotations.GeneratedColumn generated always as}, or
* using a {@linkplain org.hibernate.annotations.ColumnDefault column default value}.
* </ul>
* If the value is generated in Java, this method is not called, and so for backward
* compatibility with Hibernate 5 it is permitted to return any value. On the other hand,
* when a property value is generated in Java, the column certainly must be included in the
* column list, and so it's most correct for this method to return {@code true}!
*
* @return {@code true} indicates the column should be included in the SQL.
* @return {@code true} if the column is included in the column list of the SQL statement.
*/
boolean referenceColumnInSql();
/**
* For values which are generated in the database ({@link #getValueGenerator} == {@code null}), if the
* column will be referenced in the SQL ({@link #referenceColumnInSql()} == {@code true}), what value should be
* used in the SQL as the column value.
* <p/>
* Generally this will be a function call or a marker (DEFAULTS).
* <p/>
* NOTE : for in-VM generation, this will not be called and the column value will implicitly be a JDBC parameter ('?')
* A SQL expression indicating how to calculate the generated value when the property value
* is {@linkplain #generatedByDatabase() generated in the database} and the mapped column is
* {@linkplain #referenceColumnInSql() included in the SQL statement}. The SQL expression
* might be:
* <ul>
* <li>a function call like {@code current_timestamp} or {@code nextval('mysequence')}, or
* <li>a syntactic marker like {@code default}.
* </ul>
* When the property value is generated in Java, this method is not called, and its value is
* implicitly the string {@code "?"}, that is, a JDBC parameter to which the generated value
* is bound.
*
* @return The column value to be used in the SQL.
* @return The column value to be used in the generated SQL statement.
*/
String getDatabaseGeneratedReferencedColumnValue();
/**
* Determines if the property value is generated in Java, or by the database.
* <p>
* This default implementation returns true if the {@linkplain #getValueGenerator() Java
* value generator} is {@code null}.
*
* @return {@code true} if the value is generated by the database, or false if it is
* generated in Java using a {@link ValueGenerator}.
*/
default boolean generatedByDatabase() {
return getValueGenerator() == null;
}
/**
* Determines if the property value is written to JDBC as the argument of a JDBC {@code ?}
* parameter. This is the case when either:
* <ul>
* <li>the value is generated in Java, or
* <li>{@link #referenceColumnInSql()} is {@code true} and
* {@link #getDatabaseGeneratedReferencedColumnValue()} returns {@code null}.
* </ul>
*/
default boolean writeColumn() {
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;
}
}

View File

@ -449,7 +449,7 @@ public class EntityMetamodel implements Serializable {
final ValueGeneration valueGeneration = mappingProperty.getValueGenerationStrategy();
if ( valueGeneration != null && valueGeneration.getGenerationTiming() != GenerationTiming.NEVER ) {
// the property is generated in full. build the generation strategy pair.
if ( valueGeneration.getValueGenerator() != null ) {
if ( !valueGeneration.generatedByDatabase() ) {
// in-memory generation
return new GenerationStrategyPair(
FullInMemoryValueGenerationStrategy.create( valueGeneration )