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");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsStandardCurrentTimestampFunction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData)
|
||||
throws SQLException {
|
||||
|
|
|
@ -25,6 +25,10 @@ public enum GenerationTime {
|
|||
* Indicates the value is generated on insert.
|
||||
*/
|
||||
INSERT( GenerationTiming.INSERT ),
|
||||
/**
|
||||
* Indicates the value is generated on update.
|
||||
*/
|
||||
UPDATE( GenerationTiming.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,
|
||||
// but just to make sure...
|
||||
if ( prop.getValueGenerationStrategy() != null ) {
|
||||
if ( prop.getValueGenerationStrategy().getGenerationTiming() == GenerationTiming.INSERT ) {
|
||||
switch ( prop.getValueGenerationStrategy().getGenerationTiming() ) {
|
||||
case INSERT:
|
||||
throw new MappingException(
|
||||
"'generated' attribute cannot be 'insert' for version/timestamp property",
|
||||
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() );
|
||||
|
||||
final GenerationTiming generationTiming = singularAttributeSource.getGenerationTiming();
|
||||
if ( generationTiming == GenerationTiming.ALWAYS || generationTiming == GenerationTiming.INSERT ) {
|
||||
if ( generationTiming != null && generationTiming != GenerationTiming.NEVER ) {
|
||||
// we had generation specified...
|
||||
// HBM only supports "database generated values"
|
||||
property.setValueGenerationStrategy( new GeneratedValueGeneration( generationTiming ) );
|
||||
|
||||
// generated properties can *never* be insertable...
|
||||
if ( property.isInsertable() ) {
|
||||
if ( property.isInsertable() && generationTiming.includesInsert() ) {
|
||||
log.debugf(
|
||||
"Property [%s] specified %s generation, setting insertable to false : %s",
|
||||
propertySource.getName(),
|
||||
|
@ -2548,7 +2554,7 @@ public class ModelBinder {
|
|||
}
|
||||
|
||||
// properties generated on update can never be updatable...
|
||||
if ( property.isUpdateable() && generationTiming == GenerationTiming.ALWAYS ) {
|
||||
if ( property.isUpdateable() && generationTiming.includesUpdate() ) {
|
||||
log.debugf(
|
||||
"Property [%s] specified ALWAYS generation, setting updateable to false : %s",
|
||||
propertySource.getName(),
|
||||
|
|
|
@ -434,14 +434,19 @@ public class PropertyBinder {
|
|||
final Class<? extends AnnotationValueGeneration<?>> generationType = generatorAnnotation.generatedBy();
|
||||
final AnnotationValueGeneration<A> valueGeneration = instantiateAndInitializeValueGeneration( annotation, generationType, property );
|
||||
|
||||
if ( annotation.annotationType() == Generated.class
|
||||
&& property.isAnnotationPresent(Version.class)
|
||||
&& valueGeneration.getGenerationTiming() == GenerationTiming.INSERT ) {
|
||||
|
||||
throw new AnnotationException( "Property '" + qualify( holder.getPath(), name )
|
||||
if ( annotation.annotationType() == Generated.class && property.isAnnotationPresent(Version.class) ) {
|
||||
switch ( valueGeneration.getGenerationTiming() ) {
|
||||
case INSERT:
|
||||
throw new AnnotationException("Property '" + qualify( holder.getPath(), name )
|
||||
+ "' 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;
|
||||
|
|
|
@ -2142,8 +2142,8 @@ public abstract class Dialect implements ConversionContext {
|
|||
|
||||
/**
|
||||
* Should the value returned by {@link #getCurrentTimestampSelectString}
|
||||
* be treated as callable. Typically this indicates that JDBC escape
|
||||
* syntax is being used...
|
||||
* be treated as callable. Typically, this indicates that JDBC escape
|
||||
* syntax is being used.
|
||||
*
|
||||
* @return True if the {@link #getCurrentTimestampSelectString} return
|
||||
* 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" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this database have an ANSI-SQL {@code current_timestamp} function?
|
||||
*/
|
||||
public boolean supportsStandardCurrentTimestampFunction() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// SQLException support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -324,6 +324,11 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
|
|||
throw new UnsupportedOperationException( "format() function not supported on Sybase");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsStandardCurrentTimestampFunction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData)
|
||||
throws SQLException {
|
||||
|
|
|
@ -25,27 +25,22 @@ public interface GeneratedValueResolver {
|
|||
int dbSelectionPosition) {
|
||||
assert requestedTiming != GenerationTiming.NEVER;
|
||||
|
||||
if ( valueGeneration == null || valueGeneration.getGenerationTiming().includes( GenerationTiming.NEVER ) ) {
|
||||
if ( valueGeneration == null || !valueGeneration.getGenerationTiming().includes( requestedTiming ) ) {
|
||||
return NoGeneratedValueResolver.INSTANCE;
|
||||
}
|
||||
|
||||
if ( requestedTiming == GenerationTiming.ALWAYS && valueGeneration.getGenerationTiming() == GenerationTiming.INSERT ) {
|
||||
return NoGeneratedValueResolver.INSTANCE;
|
||||
}
|
||||
|
||||
// todo (6.x) : incorporate `org.hibernate.tuple.InDatabaseValueGenerationStrategy`
|
||||
// and `org.hibernate.tuple.InMemoryValueGenerationStrategy` from `EntityMetamodel`.
|
||||
// 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
|
||||
// use the "runtime mapping" (`org.hibernate.metamodel.mapping`) model
|
||||
|
||||
if ( valueGeneration.generatedByDatabase() ) {
|
||||
else if ( valueGeneration.generatedByDatabase() ) {
|
||||
// in-db generation (column-default, function, etc)
|
||||
return new InDatabaseGeneratedValueResolver( requestedTiming, dbSelectionPosition );
|
||||
}
|
||||
|
||||
else {
|
||||
return new InMemoryGeneratedValueResolver( valueGeneration.getValueGenerator(), requestedTiming );
|
||||
}
|
||||
}
|
||||
|
||||
GenerationTiming getGenerationTiming();
|
||||
Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session);
|
||||
|
|
|
@ -66,7 +66,8 @@ public class GeneratedValuesProcessor {
|
|||
.getEntityMetamodel()
|
||||
.getInDatabaseValueGenerationStrategies();
|
||||
entityDescriptor.visitAttributeMappings( mapping -> {
|
||||
final InDatabaseValueGenerationStrategy inDatabaseValueGenerationStrategy = inDatabaseValueGenerationStrategies[mapping.getStateArrayPosition()];
|
||||
final InDatabaseValueGenerationStrategy inDatabaseValueGenerationStrategy =
|
||||
inDatabaseValueGenerationStrategies[ mapping.getStateArrayPosition() ];
|
||||
if ( inDatabaseValueGenerationStrategy.getGenerationTiming() == GenerationTiming.NEVER ) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -2938,10 +2938,11 @@ public abstract class AbstractEntityPersister
|
|||
if ( valueGeneration.getGenerationTiming().includesUpdate()
|
||||
&& valueGeneration.generatedByDatabase()
|
||||
&& valueGeneration.referenceColumnInSql() ) {
|
||||
final Dialect dialect = getFactory().getJdbcServices().getDialect();
|
||||
update.addColumns(
|
||||
getPropertyColumnNames( index ),
|
||||
SINGLE_TRUE,
|
||||
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue() }
|
||||
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue(dialect) }
|
||||
);
|
||||
hasColumns = true;
|
||||
}
|
||||
|
@ -3060,10 +3061,11 @@ public abstract class AbstractEntityPersister
|
|||
if ( valueGeneration.getGenerationTiming().includesInsert()
|
||||
&& valueGeneration.generatedByDatabase()
|
||||
&& valueGeneration.referenceColumnInSql() ) {
|
||||
final Dialect dialect = getFactory().getJdbcServices().getDialect();
|
||||
insert.addColumns(
|
||||
getPropertyColumnNames( index ),
|
||||
SINGLE_TRUE,
|
||||
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue() }
|
||||
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue(dialect) }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5766,7 +5768,7 @@ public abstract class AbstractEntityPersister
|
|||
insertGeneratedValuesProcessor = createGeneratedValuesProcessor( GenerationTiming.INSERT );
|
||||
}
|
||||
if ( hasUpdateGeneratedProperties() ) {
|
||||
updateGeneratedValuesProcessor = createGeneratedValuesProcessor( GenerationTiming.ALWAYS );
|
||||
updateGeneratedValuesProcessor = createGeneratedValuesProcessor( GenerationTiming.UPDATE );
|
||||
}
|
||||
staticFetchableList = new ArrayList<>( attributeMappings.size() );
|
||||
visitSubTypeAttributeMappings( attributeMapping -> staticFetchableList.add( attributeMapping ) );
|
||||
|
|
|
@ -53,6 +53,25 @@ public enum GenerationTiming {
|
|||
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.
|
||||
*/
|
||||
|
@ -88,6 +107,9 @@ public enum GenerationTiming {
|
|||
if ( "insert".equalsIgnoreCase( name ) ) {
|
||||
return INSERT;
|
||||
}
|
||||
else if ( "update".equalsIgnoreCase( name ) ) {
|
||||
return UPDATE;
|
||||
}
|
||||
else if ( "always".equalsIgnoreCase( name ) ) {
|
||||
return ALWAYS;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.tuple;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
|
@ -33,6 +35,7 @@ public interface ValueGeneration extends Serializable {
|
|||
* Specifies that the property value is generated:
|
||||
* <ul>
|
||||
* <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#NEVER never}.
|
||||
* </ul>
|
||||
|
@ -86,6 +89,27 @@ public interface ValueGeneration extends Serializable {
|
|||
*/
|
||||
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.
|
||||
* <p>
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.hibernate.boot.spi.SessionFactoryOptions;
|
|||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
|
||||
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
|
||||
import org.hibernate.cfg.NotYetImplementedException;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.OptimisticLockStyle;
|
||||
import org.hibernate.engine.spi.CascadeStyle;
|
||||
import org.hibernate.engine.spi.CascadeStyles;
|
||||
|
@ -307,24 +308,33 @@ public class EntityMetamodel implements Serializable {
|
|||
final ValueGenerator<?> generator = pair.getInMemoryStrategy().getValueGenerator();
|
||||
if ( generator != null ) {
|
||||
// we have some level of generation indicated
|
||||
if ( timing == GenerationTiming.INSERT ) {
|
||||
switch ( timing ) {
|
||||
case INSERT:
|
||||
foundPreInsertGeneratedValues = true;
|
||||
}
|
||||
else if ( timing == GenerationTiming.ALWAYS ) {
|
||||
break;
|
||||
case UPDATE:
|
||||
foundPreUpdateGeneratedValues = true;
|
||||
break;
|
||||
case ALWAYS:
|
||||
foundPreInsertGeneratedValues = true;
|
||||
foundPreUpdateGeneratedValues = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( pair.getInDatabaseStrategy() != null ) {
|
||||
final GenerationTiming timing = pair.getInDatabaseStrategy().getGenerationTiming();
|
||||
if ( timing == GenerationTiming.INSERT ) {
|
||||
switch ( pair.getInDatabaseStrategy().getGenerationTiming() ) {
|
||||
case INSERT:
|
||||
foundPostInsertGeneratedValues = true;
|
||||
}
|
||||
else if ( timing == GenerationTiming.ALWAYS ) {
|
||||
break;
|
||||
case UPDATE:
|
||||
foundPostUpdateGeneratedValues = true;
|
||||
break;
|
||||
case ALWAYS:
|
||||
foundPostInsertGeneratedValues = true;
|
||||
foundPostUpdateGeneratedValues = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -487,20 +497,21 @@ public class EntityMetamodel implements Serializable {
|
|||
}
|
||||
|
||||
public static InDatabaseValueGenerationStrategyImpl create(
|
||||
SessionFactoryImplementor sessionFactoryImplementor,
|
||||
SessionFactoryImplementor factory,
|
||||
Property mappingProperty,
|
||||
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 ) {
|
||||
return new InDatabaseValueGenerationStrategyImpl(
|
||||
valueGeneration.getGenerationTiming(),
|
||||
valueGeneration.referenceColumnInSql(),
|
||||
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue() }
|
||||
new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue(dialect) }
|
||||
|
||||
);
|
||||
}
|
||||
else {
|
||||
if ( valueGeneration.getDatabaseGeneratedReferencedColumnValue() != null ) {
|
||||
if ( valueGeneration.getDatabaseGeneratedReferencedColumnValue(dialect) != null ) {
|
||||
LOG.debugf(
|
||||
"Value generator specified column value in reference to multi-column attribute [%s -> %s]; ignoring",
|
||||
mappingProperty.getPersistentClass(),
|
||||
|
@ -625,7 +636,7 @@ public class EntityMetamodel implements Serializable {
|
|||
}
|
||||
|
||||
// the base-line values for the aggregated InDatabaseValueGenerationStrategy we will build here.
|
||||
GenerationTiming timing = GenerationTiming.INSERT;
|
||||
GenerationTiming timing = GenerationTiming.NEVER;
|
||||
boolean referenceColumns = false;
|
||||
String[] columnValues = new String[ composite.getColumnSpan() ];
|
||||
|
||||
|
@ -635,11 +646,28 @@ public class EntityMetamodel implements Serializable {
|
|||
for ( Property property : composite.getProperties() ) {
|
||||
propertyIndex++;
|
||||
final InDatabaseValueGenerationStrategy subStrategy = inDatabaseStrategies.get( propertyIndex );
|
||||
|
||||
if ( subStrategy.getGenerationTiming() == GenerationTiming.ALWAYS ) {
|
||||
// override the base-line to the more often "ALWAYS"...
|
||||
switch ( subStrategy.getGenerationTiming() ) {
|
||||
case INSERT:
|
||||
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;
|
||||
|
||||
}
|
||||
if ( subStrategy.referenceColumnsInSql() ) {
|
||||
// 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();
|
||||
if ( generation instanceof GeneratedValueGeneration ) {
|
||||
final GeneratedValueGeneration valueGeneration = (GeneratedValueGeneration) generation;
|
||||
if ( GenerationTiming.INSERT == valueGeneration.getGenerationTiming()
|
||||
|| GenerationTiming.ALWAYS == valueGeneration.getGenerationTiming() ) {
|
||||
if ( valueGeneration.getGenerationTiming().includesInsert() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -366,7 +366,7 @@ abstract public class DialectFeatureChecks {
|
|||
|
||||
public static class UsesStandardCurrentTimestampFunction implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return dialect.currentTimestamp().startsWith( "current_timestamp" );
|
||||
return dialect.supportsStandardCurrentTimestampFunction();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue