Skip to content

Commit

Permalink
HHH-15663 add writable member to @generated annotation
Browse files Browse the repository at this point in the history
This is useful if you're using custom SQL, e.g. @SqlInsert.

Also improve the Javadoc surrounding all this stuff.
  • Loading branch information
gavinking committed Nov 4, 2022
1 parent aef9ab2 commit 383ffa5
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,13 @@
* </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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@
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�szl� Benke
* @author Laszlo Benke
*/
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,26 @@
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�szl� Benke
* @author Laszlo Benke
*/
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,29 @@
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�szl� Benke
* @author Laszlo Benke
*/
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
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.getValueGenerator() == null ) {
if ( !valueGeneration.writeColumn() ) {
// 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 @@ -14,7 +14,8 @@
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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ static GeneratedValueResolver from(
// 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 );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2937,7 +2937,7 @@ public String generateUpdateString(
else {
final ValueGeneration valueGeneration = attributeMapping.getValueGeneration();
if ( valueGeneration.getGenerationTiming().includesUpdate()
&& valueGeneration.getValueGenerator() == null
&& valueGeneration.generatedByDatabase()
&& valueGeneration.referenceColumnInSql() ) {
update.addColumns(
getPropertyColumnNames( index ),
Expand Down Expand Up @@ -3059,7 +3059,7 @@ public String generateInsertString(boolean[] includeProperty, int j) {
else {
final ValueGeneration valueGeneration = attributeMapping.getValueGeneration();
if ( valueGeneration.getGenerationTiming().includesInsert()
&& valueGeneration.getValueGenerator() == null
&& valueGeneration.generatedByDatabase()
&& valueGeneration.referenceColumnInSql() ) {
insert.addColumns(
getPropertyColumnNames( index ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
public class GeneratedValueGeneration implements AnnotationValueGeneration<Generated> {

private GenerationTiming timing;
private boolean writable;

public GeneratedValueGeneration() {
}
Expand All @@ -27,7 +28,8 @@ public GeneratedValueGeneration(GenerationTiming timing) {

@Override
public void initialize(Generated annotation, Class<?> propertyType) {
this.timing = annotation.value().getEquivalent();
timing = annotation.value().getEquivalent();
writable = annotation.writable();
}

@Override
Expand All @@ -43,8 +45,7 @@ public ValueGenerator<?> getValueGenerator() {

@Override
public boolean referenceColumnInSql() {
// historically these columns are not referenced in the SQL
return false;
return writable;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,98 @@
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ private static GenerationStrategyPair buildGenerationStrategyPair(
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 )
Expand Down

0 comments on commit 383ffa5

Please sign in to comment.