HHH-15782 make @ValueGenerationType work with the new "split" hierarchy of value generators

this was a lot easier than I thought it would be
This commit is contained in:
Gavin 2022-11-30 00:34:05 +01:00 committed by Gavin King
parent 82db252422
commit f022d6ef3b
13 changed files with 166 additions and 135 deletions

View File

@ -13,8 +13,9 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.AnnotationValueGenerationStrategy;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.InMemoryValueGenerationStrategy;
import org.hibernate.tuple.TimestampGenerators;
import org.hibernate.tuple.ValueGenerator;
import org.jboss.logging.Logger;
@ -42,7 +43,8 @@ import static org.hibernate.tuple.GenerationTiming.ALWAYS;
*/
@Deprecated(since = "6.2")
@Internal
public class SourceGeneration implements AnnotationValueGeneration<Source>, ValueGenerator<Object> {
public class SourceGeneration
implements AnnotationValueGenerationStrategy<Source>, InMemoryValueGenerationStrategy, ValueGenerator<Object> {
private static final CoreMessageLogger log = Logger.getMessageLogger(
CoreMessageLogger.class,
@ -53,6 +55,10 @@ public class SourceGeneration implements AnnotationValueGeneration<Source>, Valu
private ValueGenerator<?> valueGenerator;
@Override
public void initialize(Source annotation, Class<?> propertyType, String entityName, String propertyName) {
initialize( annotation, propertyType );
}
public void initialize(Source annotation, Class<?> propertyType) {
this.propertyType = propertyType;
switch ( annotation.value() ) {
@ -77,16 +83,6 @@ public class SourceGeneration implements AnnotationValueGeneration<Source>, Valu
return valueGenerator;
}
@Override
public boolean referenceColumnInSql() {
return true;
}
@Override
public String getDatabaseGeneratedReferencedColumnValue() {
return null;
}
@Override
public Object generateValue(Session session, Object owner) {
SharedSessionContractImplementor implementor = (SharedSessionContractImplementor) session;

View File

@ -6,25 +6,25 @@
*/
package org.hibernate.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.ValueGeneration;
import org.hibernate.tuple.ValueGenerationStrategy;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Meta-annotation used to mark another annotation as providing configuration
* for a custom {@linkplain ValueGeneration value generation strategy}. This
* is the best way to work with customized value generation in Hibernate.
* for a custom {@linkplain ValueGenerationStrategy value generation strategy}.
* This is the best way to work with customized value generation in Hibernate.
* <p>
* For example, if we have a custom value generator:
* <pre>{@code
* public class SKUGeneration implements AnnotationValueGeneration<SKU>, ValueGenerator<String> {
* public class SKUGeneration
* implements InMemoryValueGenerationStrategy,
* ValueGenerator<String> {
* ...
* }
* }</pre>
@ -43,10 +43,18 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* property to be generated when any SQL statement to {@code insert} or
* {@code update} the entity is executed.
* <p>
* Each generator annotation type has an {@link AnnotationValueGeneration}
* implementation which is responsible for generating values. The generator
* annotation may have members, which are used to configure the generator,
* when {@link AnnotationValueGeneration#initialize} is called.
* Every generator annotation type has an {@link ValueGenerationStrategy}
* implementation which is responsible for generating values. It must be either:
* <ul>
* <li>an {@link org.hibernate.tuple.InMemoryValueGenerationStrategy}, for
* values that are generated in Java code, using a
* {@link org.hibernate.tuple.ValueGenerator}, or
* <li>an {@link org.hibernate.tuple.InDatabaseValueGenerationStrategy}, for
* values which are generated by the database.
* </ul>
* A generator annotation may have members, which are used to configure the
* generation strategy, when the strategy instance in initialized via
* {@link org.hibernate.tuple.AnnotationValueGenerationStrategy#initialize}.
* <p>
* There are several excellent examples of the use of this machinery right
* here in this package. {@link TenantId} and its corresponding generator
@ -55,16 +63,22 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* A {@code @ValueGenerationType} annotation must have retention policy
* {@link RetentionPolicy#RUNTIME}.
*
* @see ValueGenerationStrategy
* @see org.hibernate.tuple.InMemoryValueGenerationStrategy
* @see org.hibernate.tuple.InDatabaseValueGenerationStrategy
* @see org.hibernate.tuple.AnnotationValueGenerationStrategy
*
* @author Gunnar Morling
*/
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
public @interface ValueGenerationType {
/**
* The type of value generation associated with the annotated value generator annotation type. The referenced
* generation type must be parameterized with the type of the given generator annotation.
*
* @return the value generation type
* A class that implements {@link ValueGenerationStrategy}.
* <p>
* If the generator annotation has members used to configure the
* generation strategy instance, the strategy should implement
* {@link org.hibernate.tuple.AnnotationValueGenerationStrategy}.
*/
Class<? extends AnnotationValueGeneration<?>> generatedBy();
Class<? extends ValueGenerationStrategy> generatedBy();
}

View File

@ -40,8 +40,8 @@ import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.tuple.AnnotationValueGenerationStrategy;
import org.hibernate.tuple.ValueGenerationStrategy;
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.AttributeBinder;
import org.hibernate.tuple.InDatabaseValueGenerationStrategy;
import org.hibernate.tuple.GenerationTiming;
@ -402,10 +402,10 @@ public class PropertyBinder {
/**
* Returns the value generation strategy for the given property, if any.
*/
private ValueGeneration getValueGenerationFromAnnotations(XProperty property) {
AnnotationValueGeneration<?> valueGeneration = null;
private ValueGenerationStrategy getValueGenerationFromAnnotations(XProperty property) {
ValueGenerationStrategy valueGeneration = null;
for ( Annotation annotation : property.getAnnotations() ) {
AnnotationValueGeneration<?> candidate = getValueGenerationFromAnnotation( property, annotation );
final ValueGenerationStrategy candidate = getValueGenerationFromAnnotation( property, annotation );
if ( candidate != null ) {
if ( valueGeneration != null ) {
throw new AnnotationException( "Property '" + qualify( holder.getPath(), name )
@ -423,16 +423,16 @@ public class PropertyBinder {
* In case the given annotation is a value generator annotation, the corresponding value generation strategy to be
* applied to the given property is returned, {@code null} otherwise.
*/
private <A extends Annotation> AnnotationValueGeneration<A> getValueGenerationFromAnnotation(
private ValueGenerationStrategy getValueGenerationFromAnnotation(
XProperty property,
A annotation) {
Annotation annotation) {
final ValueGenerationType generatorAnnotation = annotation.annotationType().getAnnotation( ValueGenerationType.class );
if ( generatorAnnotation == null ) {
return null;
}
final Class<? extends AnnotationValueGeneration<?>> generationType = generatorAnnotation.generatedBy();
final AnnotationValueGeneration<A> valueGeneration = instantiateAndInitializeValueGeneration( annotation, generationType, property );
final ValueGenerationStrategy valueGeneration =
instantiateAndInitializeValueGeneration( annotation, generatorAnnotation.generatedBy(), property );
if ( annotation.annotationType() == Generated.class && property.isAnnotationPresent(Version.class) ) {
switch ( valueGeneration.getGenerationTiming() ) {
@ -456,23 +456,26 @@ public class PropertyBinder {
* Instantiates the given generator annotation type, initializing it with the given instance of the corresponding
* generator annotation and the property's type.
*/
private <A extends Annotation> AnnotationValueGeneration<A> instantiateAndInitializeValueGeneration(
private <A extends Annotation> ValueGenerationStrategy instantiateAndInitializeValueGeneration(
A annotation,
Class<? extends AnnotationValueGeneration<?>> generationType,
Class<? extends ValueGenerationStrategy> generationType,
XProperty property) {
try {
// This will cause a CCE in case the generation type doesn't match the annotation type; As this would be a
// programming error of the generation type developer and thus should show up during testing, we don't
final ValueGenerationStrategy valueGeneration = generationType.newInstance();
if ( valueGeneration instanceof AnnotationValueGenerationStrategy ) {
// This will cause a CCE in case the generation type doesn't match the annotation type; As this would be
// a programming error of the generation type developer and thus should show up during testing, we don't
// check this explicitly; If required, this could be done e.g. using ClassMate
@SuppressWarnings("unchecked")
AnnotationValueGeneration<A> valueGeneration = (AnnotationValueGeneration<A>) generationType.newInstance();
valueGeneration.initialize(
final AnnotationValueGenerationStrategy<A> generation = (AnnotationValueGenerationStrategy<A>) valueGeneration;
generation.initialize(
annotation,
buildingContext.getBootstrapContext().getReflectionManager().toClass( property.getType() ),
entityBinder.getPersistentClass().getEntityName(),
property.getName()
);
}
return valueGeneration;
}

View File

@ -14,18 +14,19 @@ import java.lang.annotation.Annotation;
*
* @param <A> The generator annotation type supported by an implementation
*
* @see org.hibernate.annotations.ValueGenerationType
*
* @author Gunnar Morling
*/
public interface AnnotationValueGeneration<A extends Annotation> extends ValueGeneration {
public interface AnnotationValueGeneration<A extends Annotation>
extends ValueGeneration, AnnotationValueGenerationStrategy<A> {
/**
* Initializes this generation strategy for the given annotation instance.
*
* @param annotation an instance of the strategy's annotation type. Typically implementations will retrieve the
* @param annotation an instance of the strategy's annotation type. Typically, implementations will retrieve the
* annotation's attribute values and store them in fields.
* @param propertyType the type of the property annotated with the generator annotation. Implementations may use
* the type to determine the right {@link ValueGenerator} to be applied.
*
* @throws org.hibernate.HibernateException in case an error occurred during initialization, e.g. if
* an implementation can't create a value for the given property type.
*/
@ -34,7 +35,7 @@ public interface AnnotationValueGeneration<A extends Annotation> extends ValueGe
/**
* Initializes this generation strategy for the given annotation instance.
*
* @param annotation an instance of the strategy's annotation type. Typically implementations will retrieve the
* @param annotation an instance of the strategy's annotation type. Typically, implementations will retrieve the
* annotation's attribute values and store them in fields.
* @param propertyType the type of the property annotated with the generator annotation. Implementations may use
* the type to determine the right {@link ValueGenerator} to be applied.

View File

@ -0,0 +1,40 @@
/*
* 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.tuple;
import java.lang.annotation.Annotation;
/**
* A {@link ValueGenerationStrategy} based on a custom Java generator annotation type.
* Every instance must implement either {@link InMemoryValueGenerationStrategy} or
* {@link InDatabaseValueGenerationStrategy}.
*
* @param <A> The generator annotation type supported by an implementation
*
* @see org.hibernate.annotations.ValueGenerationType
*
* @author Gunnar Morling
* @author Gavin King
*
* @since 6.2
*/
public interface AnnotationValueGenerationStrategy<A extends Annotation> extends ValueGenerationStrategy {
/**
* Initializes this generation strategy for the given annotation instance.
*
* @param annotation an instance of the strategy's annotation type. Typically, implementations will retrieve the
* annotation's attribute values and store them in fields.
* @param propertyType the type of the property annotated with the generator annotation. Implementations may use
* the type to determine the right {@link ValueGenerator} to be applied.
* @param entityName the name of the entity to which the annotated property belongs
* @param propertyName the name of the annotated property
* @throws org.hibernate.HibernateException in case an error occurred during initialization, e.g. if
* an implementation can't create a value for the given property type.
*/
void initialize(A annotation, Class<?> propertyType, String entityName, String propertyName);
}

View File

@ -7,37 +7,35 @@
package org.hibernate.tuple;
import org.hibernate.annotations.GeneratedColumn;
import org.hibernate.dialect.Dialect;
/**
* For {@link GeneratedColumn}
* For {@link GeneratedColumn}.
*
* @author Gavin King
*/
public class GeneratedAlwaysValueGeneration implements AnnotationValueGeneration<GeneratedColumn> {
public class GeneratedAlwaysValueGeneration
implements InDatabaseValueGenerationStrategy {
public GeneratedAlwaysValueGeneration() {}
@Override
public void initialize(GeneratedColumn annotation, Class<?> propertyType) {}
@Override
public GenerationTiming getGenerationTiming() {
return GenerationTiming.ALWAYS;
}
@Override
public ValueGenerator<?> getValueGenerator() {
// database generated values do not have a value generator
return null;
}
@Override
public boolean referenceColumnInSql() {
public boolean writePropertyValue() {
return false;
}
@Override
public String getDatabaseGeneratedReferencedColumnValue() {
public boolean referenceColumnsInSql() {
return false;
}
@Override
public String[] getReferencedColumnValues(Dialect dialect) {
return null;
}
}

View File

@ -7,6 +7,7 @@
package org.hibernate.tuple;
import org.hibernate.annotations.Generated;
import org.hibernate.dialect.Dialect;
import static org.hibernate.internal.util.StringHelper.isEmpty;
@ -16,11 +17,12 @@ import static org.hibernate.internal.util.StringHelper.isEmpty;
* @author Steve Ebersole
* @author Gunnar Morling
*/
public class GeneratedValueGeneration implements AnnotationValueGeneration<Generated> {
public class GeneratedValueGeneration
implements AnnotationValueGenerationStrategy<Generated>, InDatabaseValueGenerationStrategy {
private GenerationTiming timing;
private boolean writable;
private String sql;
private String[] sql;
public GeneratedValueGeneration() {
}
@ -30,9 +32,9 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration<Gener
}
@Override
public void initialize(Generated annotation, Class<?> propertyType) {
public void initialize(Generated annotation, Class<?> propertyType, String entityName, String propertyName) {
timing = annotation.value().getEquivalent();
sql = isEmpty( annotation.sql() ) ? null : annotation.sql();
sql = isEmpty( annotation.sql() ) ? null : new String[] { annotation.sql() };
writable = annotation.writable() || sql != null;
}
@ -42,19 +44,18 @@ public class GeneratedValueGeneration implements AnnotationValueGeneration<Gener
}
@Override
public ValueGenerator<?> getValueGenerator() {
// database generated values do not have a value generator
return null;
}
@Override
public boolean referenceColumnInSql() {
public boolean referenceColumnsInSql() {
return writable;
}
@Override
public String getDatabaseGeneratedReferencedColumnValue() {
public String[] getReferencedColumnValues(Dialect dialect) {
return sql;
}
@Override
public boolean writePropertyValue() {
return writable && sql==null;
}
}

View File

@ -16,6 +16,8 @@ import org.hibernate.dialect.Dialect;
* {@code select}.
*
* @author Steve Ebersole
*
* @since 6.2
*/
public interface InDatabaseValueGenerationStrategy extends ValueGenerationStrategy {

View File

@ -12,6 +12,8 @@ package org.hibernate.tuple;
* or property value.
*
* @author Steve Ebersole
*
* @since 6.2
*/
public interface InMemoryValueGenerationStrategy extends ValueGenerationStrategy {

View File

@ -19,7 +19,8 @@ import org.hibernate.type.descriptor.java.JavaType;
*
* @author Gavin King
*/
public class TenantIdGeneration implements AnnotationValueGeneration<TenantId>, ValueGenerator<Object> {
public class TenantIdGeneration
implements AnnotationValueGenerationStrategy<TenantId>, InMemoryValueGenerationStrategy, ValueGenerator<Object> {
private String entityName;
private String propertyName;
@ -32,11 +33,6 @@ public class TenantIdGeneration implements AnnotationValueGeneration<TenantId>,
this.propertyType = propertyType;
}
@Override
public void initialize(TenantId annotation, Class<?> propertyType) {
throw new UnsupportedOperationException();
}
@Override
public GenerationTiming getGenerationTiming() {
return GenerationTiming.INSERT;
@ -79,14 +75,4 @@ public class TenantIdGeneration implements AnnotationValueGeneration<TenantId>,
public Object generateValue(Session session, Object owner) {
throw new UnsupportedOperationException();
}
@Override
public boolean referenceColumnInSql() {
return false;
}
@Override
public String getDatabaseGeneratedReferencedColumnValue() {
return null;
}
}

View File

@ -10,7 +10,9 @@ import java.io.Serializable;
/**
* 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.
* value might be generated in Java, or by the database. Every instance must implement either
* {@link InMemoryValueGenerationStrategy} or {@link InDatabaseValueGenerationStrategy}
* depending on whether values are generated in Java code, 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
@ -21,6 +23,9 @@ import java.io.Serializable;
* statement. In this case, the generated value is retrieved from the database using a SQL
* {@code select}.
* </ul>
* If an implementation of {@code ValueGenerationStrategy} may be associated with an entity
* via a {@linkplain org.hibernate.annotations.ValueGenerationType custom annotation}, it
* should implement {@link AnnotationValueGenerationStrategy}.
*
* @see org.hibernate.annotations.ValueGenerationType
* @see org.hibernate.annotations.Generated
@ -28,6 +33,8 @@ import java.io.Serializable;
*
* @author Steve Ebersole
* @author Gavin King
*
* @since 6.2
*/
public interface ValueGenerationStrategy extends Serializable {
/**

View File

@ -13,21 +13,20 @@ import org.hibernate.annotations.GeneratorType;
import org.hibernate.internal.util.ReflectHelper;
/**
* A {@link AnnotationValueGeneration} which allows to specify the {@link ValueGenerator} to be used to determine the
* value of the annotated property.
* An {@link AnnotationValueGeneration} which delegates to a {@link ValueGenerator}.
*
* @author Gunnar Morling
*/
public class VmValueGeneration implements AnnotationValueGeneration<GeneratorType> {
public class VmValueGeneration
implements AnnotationValueGenerationStrategy<GeneratorType>, InMemoryValueGenerationStrategy {
private GenerationTiming generationTiming;
private Constructor<? extends ValueGenerator<?>> constructor;
@Override
public void initialize(GeneratorType annotation, Class<?> propertyType) {
Class<? extends ValueGenerator<?>> generatorType = annotation.type();
constructor = ReflectHelper.getDefaultConstructor( generatorType );
this.generationTiming = annotation.when().getEquivalent();
public void initialize(GeneratorType annotation, Class<?> propertyType, String entityName, String propertyName) {
constructor = ReflectHelper.getDefaultConstructor( annotation.type() );
generationTiming = annotation.when().getEquivalent();
}
@Override
@ -44,14 +43,4 @@ public class VmValueGeneration implements AnnotationValueGeneration<GeneratorTyp
throw new HibernateException( "Couldn't instantiate value generator", e );
}
}
@Override
public boolean referenceColumnInSql() {
return false;
}
@Override
public String getDatabaseGeneratedReferencedColumnValue() {
return null;
}
}

View File

@ -19,8 +19,9 @@ import jakarta.persistence.Table;
import org.hibernate.Session;
import org.hibernate.annotations.ValueGenerationType;
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.AnnotationValueGenerationStrategy;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.InMemoryValueGenerationStrategy;
import org.hibernate.tuple.ValueGenerator;
import org.hibernate.testing.orm.junit.DomainModel;
@ -86,11 +87,12 @@ public class GeneratedUuidTests {
//end::mapping-generated-custom-ex2[]
//tag::mapping-generated-custom-ex3[]
public static class UuidValueGeneration implements AnnotationValueGeneration<GeneratedUuidValue>, ValueGenerator<UUID> {
public static class UuidValueGeneration
implements AnnotationValueGenerationStrategy<GeneratedUuidValue>, InMemoryValueGenerationStrategy, ValueGenerator<UUID> {
private GenerationTiming timing;
@Override
public void initialize(GeneratedUuidValue annotation, Class<?> propertyType) {
public void initialize(GeneratedUuidValue annotation, Class<?> propertyType, String entityName, String propertyName) {
timing = annotation.timing();
}
@ -104,16 +106,6 @@ public class GeneratedUuidTests {
return this;
}
@Override
public boolean referenceColumnInSql() {
return false;
}
@Override
public String getDatabaseGeneratedReferencedColumnValue() {
return null;
}
@Override
public UUID generateValue(Session session, Object owner) {
return UUID.randomUUID();