HHH-15672 introduce Generated(UPDATE) for properties only generated on update
This commit is contained in:
parent
aaeed841c8
commit
61c128000b
|
@ -314,6 +314,11 @@ public class SybaseLegacyDialect extends AbstractTransactSQLDialect {
|
||||||
throw new UnsupportedOperationException( "format() function not supported on Sybase");
|
throw new UnsupportedOperationException( "format() function not supported on Sybase");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsStandardCurrentTimestampFunction() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData)
|
public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData)
|
||||||
throws SQLException {
|
throws SQLException {
|
||||||
|
|
|
@ -25,6 +25,10 @@ public enum GenerationTime {
|
||||||
* Indicates the value is generated on insert.
|
* Indicates the value is generated on insert.
|
||||||
*/
|
*/
|
||||||
INSERT( GenerationTiming.INSERT ),
|
INSERT( GenerationTiming.INSERT ),
|
||||||
|
/**
|
||||||
|
* Indicates the value is generated on update.
|
||||||
|
*/
|
||||||
|
UPDATE( GenerationTiming.UPDATE ),
|
||||||
/**
|
/**
|
||||||
* Indicates the value is generated on insert and on update.
|
* Indicates the value is generated on insert and on update.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -998,11 +998,17 @@ public class ModelBinder {
|
||||||
// generated; aka, "insert" is invalid; this is dis-allowed by the DTD,
|
// generated; aka, "insert" is invalid; this is dis-allowed by the DTD,
|
||||||
// but just to make sure...
|
// but just to make sure...
|
||||||
if ( prop.getValueGenerationStrategy() != null ) {
|
if ( prop.getValueGenerationStrategy() != null ) {
|
||||||
if ( prop.getValueGenerationStrategy().getGenerationTiming() == GenerationTiming.INSERT ) {
|
switch ( prop.getValueGenerationStrategy().getGenerationTiming() ) {
|
||||||
|
case INSERT:
|
||||||
throw new MappingException(
|
throw new MappingException(
|
||||||
"'generated' attribute cannot be 'insert' for version/timestamp property",
|
"'generated' attribute cannot be 'insert' for version/timestamp property",
|
||||||
sourceDocument.getOrigin()
|
sourceDocument.getOrigin()
|
||||||
);
|
);
|
||||||
|
case UPDATE:
|
||||||
|
throw new MappingException(
|
||||||
|
"'generated' attribute cannot be 'update' for version/timestamp property",
|
||||||
|
sourceDocument.getOrigin()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2531,13 +2537,13 @@ public class ModelBinder {
|
||||||
property.setLazy( singularAttributeSource.isBytecodeLazy() );
|
property.setLazy( singularAttributeSource.isBytecodeLazy() );
|
||||||
|
|
||||||
final GenerationTiming generationTiming = singularAttributeSource.getGenerationTiming();
|
final GenerationTiming generationTiming = singularAttributeSource.getGenerationTiming();
|
||||||
if ( generationTiming == GenerationTiming.ALWAYS || generationTiming == GenerationTiming.INSERT ) {
|
if ( generationTiming != null && generationTiming != GenerationTiming.NEVER ) {
|
||||||
// we had generation specified...
|
// we had generation specified...
|
||||||
// HBM only supports "database generated values"
|
// HBM only supports "database generated values"
|
||||||
property.setValueGenerationStrategy( new GeneratedValueGeneration( generationTiming ) );
|
property.setValueGenerationStrategy( new GeneratedValueGeneration( generationTiming ) );
|
||||||
|
|
||||||
// generated properties can *never* be insertable...
|
// generated properties can *never* be insertable...
|
||||||
if ( property.isInsertable() ) {
|
if ( property.isInsertable() && generationTiming.includesInsert() ) {
|
||||||
log.debugf(
|
log.debugf(
|
||||||
"Property [%s] specified %s generation, setting insertable to false : %s",
|
"Property [%s] specified %s generation, setting insertable to false : %s",
|
||||||
propertySource.getName(),
|
propertySource.getName(),
|
||||||
|
@ -2548,7 +2554,7 @@ public class ModelBinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// properties generated on update can never be updatable...
|
// properties generated on update can never be updatable...
|
||||||
if ( property.isUpdateable() && generationTiming == GenerationTiming.ALWAYS ) {
|
if ( property.isUpdateable() && generationTiming.includesUpdate() ) {
|
||||||
log.debugf(
|
log.debugf(
|
||||||
"Property [%s] specified ALWAYS generation, setting updateable to false : %s",
|
"Property [%s] specified ALWAYS generation, setting updateable to false : %s",
|
||||||
propertySource.getName(),
|
propertySource.getName(),
|
||||||
|
|
|
@ -434,14 +434,19 @@ public class PropertyBinder {
|
||||||
final Class<? extends AnnotationValueGeneration<?>> generationType = generatorAnnotation.generatedBy();
|
final Class<? extends AnnotationValueGeneration<?>> generationType = generatorAnnotation.generatedBy();
|
||||||
final AnnotationValueGeneration<A> valueGeneration = instantiateAndInitializeValueGeneration( annotation, generationType, property );
|
final AnnotationValueGeneration<A> valueGeneration = instantiateAndInitializeValueGeneration( annotation, generationType, property );
|
||||||
|
|
||||||
if ( annotation.annotationType() == Generated.class
|
if ( annotation.annotationType() == Generated.class && property.isAnnotationPresent(Version.class) ) {
|
||||||
&& property.isAnnotationPresent(Version.class)
|
switch ( valueGeneration.getGenerationTiming() ) {
|
||||||
&& valueGeneration.getGenerationTiming() == GenerationTiming.INSERT ) {
|
case INSERT:
|
||||||
|
|
||||||
throw new AnnotationException("Property '" + qualify( holder.getPath(), name )
|
throw new AnnotationException("Property '" + qualify( holder.getPath(), name )
|
||||||
+ "' is annotated '@Generated(INSERT)' and '@Version' (use '@Generated(ALWAYS)' instead)"
|
+ "' is annotated '@Generated(INSERT)' and '@Version' (use '@Generated(ALWAYS)' instead)"
|
||||||
|
|
||||||
);
|
);
|
||||||
|
case UPDATE:
|
||||||
|
throw new AnnotationException("Property '" + qualify( holder.getPath(), name )
|
||||||
|
+ "' is annotated '@Generated(UPDATE)' and '@Version' (use '@Generated(ALWAYS)' instead)"
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return valueGeneration;
|
return valueGeneration;
|
||||||
|
|
|
@ -2142,8 +2142,8 @@ public abstract class Dialect implements ConversionContext {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should the value returned by {@link #getCurrentTimestampSelectString}
|
* Should the value returned by {@link #getCurrentTimestampSelectString}
|
||||||
* be treated as callable. Typically this indicates that JDBC escape
|
* be treated as callable. Typically, this indicates that JDBC escape
|
||||||
* syntax is being used...
|
* syntax is being used.
|
||||||
*
|
*
|
||||||
* @return True if the {@link #getCurrentTimestampSelectString} return
|
* @return True if the {@link #getCurrentTimestampSelectString} return
|
||||||
* is callable; false otherwise.
|
* is callable; false otherwise.
|
||||||
|
@ -2162,6 +2162,13 @@ public abstract class Dialect implements ConversionContext {
|
||||||
throw new UnsupportedOperationException( "Database not known to define a current timestamp function" );
|
throw new UnsupportedOperationException( "Database not known to define a current timestamp function" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this database have an ANSI-SQL {@code current_timestamp} function?
|
||||||
|
*/
|
||||||
|
public boolean supportsStandardCurrentTimestampFunction() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// SQLException support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// SQLException support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -324,6 +324,11 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
|
||||||
throw new UnsupportedOperationException( "format() function not supported on Sybase");
|
throw new UnsupportedOperationException( "format() function not supported on Sybase");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsStandardCurrentTimestampFunction() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData)
|
public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData)
|
||||||
throws SQLException {
|
throws SQLException {
|
||||||
|
|
|
@ -25,27 +25,22 @@ public interface GeneratedValueResolver {
|
||||||
int dbSelectionPosition) {
|
int dbSelectionPosition) {
|
||||||
assert requestedTiming != GenerationTiming.NEVER;
|
assert requestedTiming != GenerationTiming.NEVER;
|
||||||
|
|
||||||
if ( valueGeneration == null || valueGeneration.getGenerationTiming().includes( GenerationTiming.NEVER ) ) {
|
if ( valueGeneration == null || !valueGeneration.getGenerationTiming().includes( requestedTiming ) ) {
|
||||||
return NoGeneratedValueResolver.INSTANCE;
|
return NoGeneratedValueResolver.INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( requestedTiming == GenerationTiming.ALWAYS && valueGeneration.getGenerationTiming() == GenerationTiming.INSERT ) {
|
|
||||||
return NoGeneratedValueResolver.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo (6.x) : incorporate `org.hibernate.tuple.InDatabaseValueGenerationStrategy`
|
// todo (6.x) : incorporate `org.hibernate.tuple.InDatabaseValueGenerationStrategy`
|
||||||
// and `org.hibernate.tuple.InMemoryValueGenerationStrategy` from `EntityMetamodel`.
|
// and `org.hibernate.tuple.InMemoryValueGenerationStrategy` from `EntityMetamodel`.
|
||||||
// this requires unification of the read and write (insert/update) aspects of
|
// this requires unification of the read and write (insert/update) aspects of
|
||||||
// value generation which we'll circle back to as we convert write operations to
|
// value generation which we'll circle back to as we convert write operations to
|
||||||
// use the "runtime mapping" (`org.hibernate.metamodel.mapping`) model
|
// use the "runtime mapping" (`org.hibernate.metamodel.mapping`) model
|
||||||
|
else if ( valueGeneration.generatedByDatabase() ) {
|
||||||
if ( valueGeneration.generatedByDatabase() ) {
|
|
||||||
// in-db generation (column-default, function, etc)
|
// in-db generation (column-default, function, etc)
|
||||||
return new InDatabaseGeneratedValueResolver( requestedTiming, dbSelectionPosition );
|
return new InDatabaseGeneratedValueResolver( requestedTiming, dbSelectionPosition );
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
return new InMemoryGeneratedValueResolver( valueGeneration.getValueGenerator(), requestedTiming );
|
return new InMemoryGeneratedValueResolver( valueGeneration.getValueGenerator(), requestedTiming );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GenerationTiming getGenerationTiming();
|
GenerationTiming getGenerationTiming();
|
||||||
Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session);
|
Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session);
|
||||||
|
|
|
@ -66,7 +66,8 @@ public class GeneratedValuesProcessor {
|
||||||
.getEntityMetamodel()
|
.getEntityMetamodel()
|
||||||
.getInDatabaseValueGenerationStrategies();
|
.getInDatabaseValueGenerationStrategies();
|
||||||
entityDescriptor.visitAttributeMappings( mapping -> {
|
entityDescriptor.visitAttributeMappings( mapping -> {
|
||||||
final InDatabaseValueGenerationStrategy inDatabaseValueGenerationStrategy = inDatabaseValueGenerationStrategies[mapping.getStateArrayPosition()];
|
final InDatabaseValueGenerationStrategy inDatabaseValueGenerationStrategy =
|
||||||
|
inDatabaseValueGenerationStrategies[ mapping.getStateArrayPosition() ];
|
||||||
if ( inDatabaseValueGenerationStrategy.getGenerationTiming() == GenerationTiming.NEVER ) {
|
if ( inDatabaseValueGenerationStrategy.getGenerationTiming() == GenerationTiming.NEVER ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2938,10 +2938,11 @@ public abstract class AbstractEntityPersister
|
||||||
if ( valueGeneration.getGenerationTiming().includesUpdate()
|
if ( valueGeneration.getGenerationTiming().includesUpdate()
|
||||||
&& valueGeneration.generatedByDatabase()
|
&& valueGeneration.generatedByDatabase()
|
||||||
&& valueGeneration.referenceColumnInSql() ) {
|
&& valueGeneration.referenceColumnInSql() ) {
|
||||||
|
final Dialect dialect = getFactory().getJdbcServices().getDialect();
|
||||||
update.addColumns(
|
update.addColumns(
|
||||||
getPropertyColumnNames( index ),
|
getPropertyColumnNames( index ),
|
||||||
SINGLE_TRUE,
|
SINGLE_TRUE,
|
||||||
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue() }
|
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue(dialect) }
|
||||||
);
|
);
|
||||||
hasColumns = true;
|
hasColumns = true;
|
||||||
}
|
}
|
||||||
|
@ -3060,10 +3061,11 @@ public abstract class AbstractEntityPersister
|
||||||
if ( valueGeneration.getGenerationTiming().includesInsert()
|
if ( valueGeneration.getGenerationTiming().includesInsert()
|
||||||
&& valueGeneration.generatedByDatabase()
|
&& valueGeneration.generatedByDatabase()
|
||||||
&& valueGeneration.referenceColumnInSql() ) {
|
&& valueGeneration.referenceColumnInSql() ) {
|
||||||
|
final Dialect dialect = getFactory().getJdbcServices().getDialect();
|
||||||
insert.addColumns(
|
insert.addColumns(
|
||||||
getPropertyColumnNames( index ),
|
getPropertyColumnNames( index ),
|
||||||
SINGLE_TRUE,
|
SINGLE_TRUE,
|
||||||
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue() }
|
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue(dialect) }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5766,7 +5768,7 @@ public abstract class AbstractEntityPersister
|
||||||
insertGeneratedValuesProcessor = createGeneratedValuesProcessor( GenerationTiming.INSERT );
|
insertGeneratedValuesProcessor = createGeneratedValuesProcessor( GenerationTiming.INSERT );
|
||||||
}
|
}
|
||||||
if ( hasUpdateGeneratedProperties() ) {
|
if ( hasUpdateGeneratedProperties() ) {
|
||||||
updateGeneratedValuesProcessor = createGeneratedValuesProcessor( GenerationTiming.ALWAYS );
|
updateGeneratedValuesProcessor = createGeneratedValuesProcessor( GenerationTiming.UPDATE );
|
||||||
}
|
}
|
||||||
staticFetchableList = new ArrayList<>( attributeMappings.size() );
|
staticFetchableList = new ArrayList<>( attributeMappings.size() );
|
||||||
visitSubTypeAttributeMappings( attributeMapping -> staticFetchableList.add( attributeMapping ) );
|
visitSubTypeAttributeMappings( attributeMapping -> staticFetchableList.add( attributeMapping ) );
|
||||||
|
|
|
@ -53,6 +53,25 @@ public enum GenerationTiming {
|
||||||
return timing.includesInsert();
|
return timing.includesInsert();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Value generation that occurs when a row is updated in the database.
|
||||||
|
*/
|
||||||
|
UPDATE {
|
||||||
|
@Override
|
||||||
|
public boolean includesInsert() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean includesUpdate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean includes(GenerationTiming timing) {
|
||||||
|
return timing.includesUpdate();
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Value generation that occurs when a row is inserted or updated in the database.
|
* Value generation that occurs when a row is inserted or updated in the database.
|
||||||
*/
|
*/
|
||||||
|
@ -88,6 +107,9 @@ public enum GenerationTiming {
|
||||||
if ( "insert".equalsIgnoreCase( name ) ) {
|
if ( "insert".equalsIgnoreCase( name ) ) {
|
||||||
return INSERT;
|
return INSERT;
|
||||||
}
|
}
|
||||||
|
else if ( "update".equalsIgnoreCase( name ) ) {
|
||||||
|
return UPDATE;
|
||||||
|
}
|
||||||
else if ( "always".equalsIgnoreCase( name ) ) {
|
else if ( "always".equalsIgnoreCase( name ) ) {
|
||||||
return ALWAYS;
|
return ALWAYS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.tuple;
|
package org.hibernate.tuple;
|
||||||
|
|
||||||
|
import org.hibernate.dialect.Dialect;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +35,7 @@ public interface ValueGeneration extends Serializable {
|
||||||
* Specifies that the property value is generated:
|
* Specifies that the property value is generated:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@linkplain GenerationTiming#INSERT when the entity is inserted},
|
* <li>{@linkplain GenerationTiming#INSERT when the entity is inserted},
|
||||||
|
* <li>{@linkplain GenerationTiming#UPDATE when the entity is updated},
|
||||||
* <li>{@linkplain GenerationTiming#ALWAYS whenever the entity is inserted or updated}, or
|
* <li>{@linkplain GenerationTiming#ALWAYS whenever the entity is inserted or updated}, or
|
||||||
* <li>{@linkplain GenerationTiming#NEVER never}.
|
* <li>{@linkplain GenerationTiming#NEVER never}.
|
||||||
* </ul>
|
* </ul>
|
||||||
|
@ -86,6 +89,27 @@ public interface ValueGeneration extends Serializable {
|
||||||
*/
|
*/
|
||||||
String getDatabaseGeneratedReferencedColumnValue();
|
String getDatabaseGeneratedReferencedColumnValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @param dialect The {@linkplain Dialect SQL dialect}, allowing generation of an expression
|
||||||
|
* in dialect-specific SQL.
|
||||||
|
* @return The column value to be used in the generated SQL statement.
|
||||||
|
*/
|
||||||
|
default String getDatabaseGeneratedReferencedColumnValue(Dialect dialect) {
|
||||||
|
return getDatabaseGeneratedReferencedColumnValue();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the property value is generated in Java, or by the database.
|
* Determines if the property value is generated in Java, or by the database.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.hibernate.boot.spi.SessionFactoryOptions;
|
||||||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
|
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
|
||||||
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
|
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
|
||||||
import org.hibernate.cfg.NotYetImplementedException;
|
import org.hibernate.cfg.NotYetImplementedException;
|
||||||
|
import org.hibernate.dialect.Dialect;
|
||||||
import org.hibernate.engine.OptimisticLockStyle;
|
import org.hibernate.engine.OptimisticLockStyle;
|
||||||
import org.hibernate.engine.spi.CascadeStyle;
|
import org.hibernate.engine.spi.CascadeStyle;
|
||||||
import org.hibernate.engine.spi.CascadeStyles;
|
import org.hibernate.engine.spi.CascadeStyles;
|
||||||
|
@ -307,24 +308,33 @@ public class EntityMetamodel implements Serializable {
|
||||||
final ValueGenerator<?> generator = pair.getInMemoryStrategy().getValueGenerator();
|
final ValueGenerator<?> generator = pair.getInMemoryStrategy().getValueGenerator();
|
||||||
if ( generator != null ) {
|
if ( generator != null ) {
|
||||||
// we have some level of generation indicated
|
// we have some level of generation indicated
|
||||||
if ( timing == GenerationTiming.INSERT ) {
|
switch ( timing ) {
|
||||||
|
case INSERT:
|
||||||
foundPreInsertGeneratedValues = true;
|
foundPreInsertGeneratedValues = true;
|
||||||
}
|
break;
|
||||||
else if ( timing == GenerationTiming.ALWAYS ) {
|
case UPDATE:
|
||||||
|
foundPreUpdateGeneratedValues = true;
|
||||||
|
break;
|
||||||
|
case ALWAYS:
|
||||||
foundPreInsertGeneratedValues = true;
|
foundPreInsertGeneratedValues = true;
|
||||||
foundPreUpdateGeneratedValues = true;
|
foundPreUpdateGeneratedValues = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( pair.getInDatabaseStrategy() != null ) {
|
if ( pair.getInDatabaseStrategy() != null ) {
|
||||||
final GenerationTiming timing = pair.getInDatabaseStrategy().getGenerationTiming();
|
switch ( pair.getInDatabaseStrategy().getGenerationTiming() ) {
|
||||||
if ( timing == GenerationTiming.INSERT ) {
|
case INSERT:
|
||||||
foundPostInsertGeneratedValues = true;
|
foundPostInsertGeneratedValues = true;
|
||||||
}
|
break;
|
||||||
else if ( timing == GenerationTiming.ALWAYS ) {
|
case UPDATE:
|
||||||
|
foundPostUpdateGeneratedValues = true;
|
||||||
|
break;
|
||||||
|
case ALWAYS:
|
||||||
foundPostInsertGeneratedValues = true;
|
foundPostInsertGeneratedValues = true;
|
||||||
foundPostUpdateGeneratedValues = true;
|
foundPostUpdateGeneratedValues = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -487,20 +497,21 @@ public class EntityMetamodel implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InDatabaseValueGenerationStrategyImpl create(
|
public static InDatabaseValueGenerationStrategyImpl create(
|
||||||
SessionFactoryImplementor sessionFactoryImplementor,
|
SessionFactoryImplementor factory,
|
||||||
Property mappingProperty,
|
Property mappingProperty,
|
||||||
ValueGeneration valueGeneration) {
|
ValueGeneration valueGeneration) {
|
||||||
final int numberOfMappedColumns = mappingProperty.getType().getColumnSpan( sessionFactoryImplementor );
|
final int numberOfMappedColumns = mappingProperty.getType().getColumnSpan( factory );
|
||||||
|
final Dialect dialect = factory.getJdbcServices().getDialect();
|
||||||
if ( numberOfMappedColumns == 1 ) {
|
if ( numberOfMappedColumns == 1 ) {
|
||||||
return new InDatabaseValueGenerationStrategyImpl(
|
return new InDatabaseValueGenerationStrategyImpl(
|
||||||
valueGeneration.getGenerationTiming(),
|
valueGeneration.getGenerationTiming(),
|
||||||
valueGeneration.referenceColumnInSql(),
|
valueGeneration.referenceColumnInSql(),
|
||||||
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue() }
|
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue(dialect) }
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ( valueGeneration.getDatabaseGeneratedReferencedColumnValue() != null ) {
|
if ( valueGeneration.getDatabaseGeneratedReferencedColumnValue(dialect) != null ) {
|
||||||
LOG.debugf(
|
LOG.debugf(
|
||||||
"Value generator specified column value in reference to multi-column attribute [%s -> %s]; ignoring",
|
"Value generator specified column value in reference to multi-column attribute [%s -> %s]; ignoring",
|
||||||
mappingProperty.getPersistentClass(),
|
mappingProperty.getPersistentClass(),
|
||||||
|
@ -625,7 +636,7 @@ public class EntityMetamodel implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
// the base-line values for the aggregated InDatabaseValueGenerationStrategy we will build here.
|
// the base-line values for the aggregated InDatabaseValueGenerationStrategy we will build here.
|
||||||
GenerationTiming timing = GenerationTiming.INSERT;
|
GenerationTiming timing = GenerationTiming.NEVER;
|
||||||
boolean referenceColumns = false;
|
boolean referenceColumns = false;
|
||||||
String[] columnValues = new String[ composite.getColumnSpan() ];
|
String[] columnValues = new String[ composite.getColumnSpan() ];
|
||||||
|
|
||||||
|
@ -635,11 +646,28 @@ public class EntityMetamodel implements Serializable {
|
||||||
for ( Property property : composite.getProperties() ) {
|
for ( Property property : composite.getProperties() ) {
|
||||||
propertyIndex++;
|
propertyIndex++;
|
||||||
final InDatabaseValueGenerationStrategy subStrategy = inDatabaseStrategies.get( propertyIndex );
|
final InDatabaseValueGenerationStrategy subStrategy = inDatabaseStrategies.get( propertyIndex );
|
||||||
|
switch ( subStrategy.getGenerationTiming() ) {
|
||||||
if ( subStrategy.getGenerationTiming() == GenerationTiming.ALWAYS ) {
|
case INSERT:
|
||||||
// override the base-line to the more often "ALWAYS"...
|
switch ( timing ) {
|
||||||
|
case UPDATE:
|
||||||
|
timing = GenerationTiming.ALWAYS;
|
||||||
|
break;
|
||||||
|
case NEVER:
|
||||||
|
timing = GenerationTiming.INSERT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UPDATE:
|
||||||
|
switch ( timing ) {
|
||||||
|
case INSERT:
|
||||||
|
timing = GenerationTiming.ALWAYS;
|
||||||
|
break;
|
||||||
|
case NEVER:
|
||||||
|
timing = GenerationTiming.UPDATE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ALWAYS:
|
||||||
timing = GenerationTiming.ALWAYS;
|
timing = GenerationTiming.ALWAYS;
|
||||||
|
|
||||||
}
|
}
|
||||||
if ( subStrategy.referenceColumnsInSql() ) {
|
if ( subStrategy.referenceColumnsInSql() ) {
|
||||||
// override base-line value
|
// override base-line value
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.annotations;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import org.hibernate.annotations.GenerationTime;
|
||||||
|
import org.hibernate.annotations.NaturalId;
|
||||||
|
import org.hibernate.annotations.ValueGenerationType;
|
||||||
|
import org.hibernate.dialect.Dialect;
|
||||||
|
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||||
|
import org.hibernate.testing.orm.junit.Jpa;
|
||||||
|
import org.hibernate.tuple.AnnotationValueGeneration;
|
||||||
|
import org.hibernate.tuple.GenerationTiming;
|
||||||
|
import org.hibernate.tuple.ValueGenerator;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Jpa(annotatedClasses = DatabaseTimestampsColumnTest.Person.class)
|
||||||
|
public class DatabaseTimestampsColumnTest {
|
||||||
|
|
||||||
|
@Entity(name = "Person")
|
||||||
|
public class Person {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NaturalId(mutable = true)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Timestamp(GenerationTime.INSERT)
|
||||||
|
private Date creationDate;
|
||||||
|
|
||||||
|
@Column(nullable = true)
|
||||||
|
@Timestamp(GenerationTime.UPDATE)
|
||||||
|
private Date editionDate;
|
||||||
|
|
||||||
|
@Column(nullable = false, name="version")
|
||||||
|
@Timestamp(GenerationTime.ALWAYS)
|
||||||
|
private Date timestamp;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreationDate() {
|
||||||
|
return creationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getEditionDate() {
|
||||||
|
return editionDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ValueGenerationType(generatedBy = TimestampValueGeneration.class)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Timestamp { GenerationTime value(); }
|
||||||
|
|
||||||
|
public static class TimestampValueGeneration
|
||||||
|
implements AnnotationValueGeneration<Timestamp> {
|
||||||
|
|
||||||
|
private GenerationTiming timing;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(Timestamp annotation, Class<?> propertyType) {
|
||||||
|
timing = annotation.value().getEquivalent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenerationTiming getGenerationTiming() {
|
||||||
|
return timing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueGenerator<?> getValueGenerator() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean referenceColumnInSql() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDatabaseGeneratedReferencedColumnValue() {
|
||||||
|
return "current_timestamp";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDatabaseGeneratedReferencedColumnValue(Dialect dialect) {
|
||||||
|
return dialect.currentTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void generatesCurrentTimestamp(EntityManagerFactoryScope scope) {
|
||||||
|
scope.inEntityManager(
|
||||||
|
entityManager -> {
|
||||||
|
entityManager.getTransaction().begin();
|
||||||
|
Person person = new Person();
|
||||||
|
person.setName("John Doe");
|
||||||
|
entityManager.persist(person);
|
||||||
|
entityManager.getTransaction().commit();
|
||||||
|
Date creationDate = person.getCreationDate();
|
||||||
|
Assertions.assertNotNull(creationDate);
|
||||||
|
Assertions.assertNull(person.getEditionDate());
|
||||||
|
Date timestamp = person.getTimestamp();
|
||||||
|
Assertions.assertNotNull(timestamp);
|
||||||
|
|
||||||
|
try { Thread.sleep(1_000); } catch (InterruptedException ie) {};
|
||||||
|
|
||||||
|
entityManager.getTransaction().begin();
|
||||||
|
person.setName("Jane Doe");
|
||||||
|
entityManager.getTransaction().commit();
|
||||||
|
Assertions.assertNotNull(person.getCreationDate());
|
||||||
|
Assertions.assertEquals(creationDate, person.getCreationDate());
|
||||||
|
Assertions.assertNotNull(person.getEditionDate());
|
||||||
|
Assertions.assertNotNull(person.getTimestamp());
|
||||||
|
Assertions.assertNotEquals(timestamp, person.getTimestamp());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -123,8 +123,7 @@ public final class AuditMetadataGenerator extends AbstractMetadataGenerator {
|
||||||
final ValueGeneration generation = property.getValueGenerationStrategy();
|
final ValueGeneration generation = property.getValueGenerationStrategy();
|
||||||
if ( generation instanceof GeneratedValueGeneration ) {
|
if ( generation instanceof GeneratedValueGeneration ) {
|
||||||
final GeneratedValueGeneration valueGeneration = (GeneratedValueGeneration) generation;
|
final GeneratedValueGeneration valueGeneration = (GeneratedValueGeneration) generation;
|
||||||
if ( GenerationTiming.INSERT == valueGeneration.getGenerationTiming()
|
if ( valueGeneration.getGenerationTiming().includesInsert() ) {
|
||||||
|| GenerationTiming.ALWAYS == valueGeneration.getGenerationTiming() ) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@ abstract public class DialectFeatureChecks {
|
||||||
|
|
||||||
public static class UsesStandardCurrentTimestampFunction implements DialectFeatureCheck {
|
public static class UsesStandardCurrentTimestampFunction implements DialectFeatureCheck {
|
||||||
public boolean apply(Dialect dialect) {
|
public boolean apply(Dialect dialect) {
|
||||||
return dialect.currentTimestamp().startsWith( "current_timestamp" );
|
return dialect.supportsStandardCurrentTimestampFunction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue