HHH-15789 make IdentifierGenerator into a sort of value generator

This commit is contained in:
Gavin 2022-11-30 10:02:48 +01:00 committed by Gavin King
parent 1c083a5863
commit 82c68d93e9
19 changed files with 126 additions and 138 deletions

View File

@ -79,8 +79,8 @@ public class SourceGeneration
}
@Override
public ValueGenerator<?> getValueGenerator() {
return valueGenerator;
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue) {
return valueGenerator.generateValue( (Session) session, owner, currentValue );
}
@Override

View File

@ -9,7 +9,6 @@ package org.hibernate.id;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.boot.model.relational.Database;
@ -17,8 +16,6 @@ import org.hibernate.boot.model.relational.ExportableProducer;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.factory.spi.StandardGenerator;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type;
/**
* For composite identifiers, defines a number of "nested" generations that

View File

@ -7,7 +7,6 @@
package org.hibernate.id;
import java.util.Properties;
import jakarta.persistence.GeneratedValue;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
@ -16,19 +15,23 @@ import org.hibernate.boot.model.relational.ExportableProducer;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.InMemoryValueGenerationStrategy;
import org.hibernate.type.Type;
import static org.hibernate.tuple.GenerationTiming.INSERT;
/**
* The general contract between a class that generates unique
* identifiers and the {@code Session}. It is not intended that
* this interface ever be exposed to the application. It <b>is</b>
* intended that users implement this interface to provide
* custom identifier generation strategies.
* identifiers and the {@link org.hibernate.Session}. It is not
* intended that this interface ever be exposed to the application.
* It <em>is</em> intended that users implement this interface to
* provide custom identifier generation strategies.
* <p>
* Implementors should provide a public default constructor.
* <p>
* Implementations that accept configuration parameters should
* also implement {@code Configurable}.
* Implementations that accept configuration parameters should also
* implement {@link Configurable}.
* <p>
* Implementors <em>must</em> be thread-safe
*
@ -36,7 +39,7 @@ import org.hibernate.type.Type;
*
* @see PersistentIdentifierGenerator
*/
public interface IdentifierGenerator extends Configurable, ExportableProducer {
public interface IdentifierGenerator extends InMemoryValueGenerationStrategy, ExportableProducer, Configurable {
/**
* The configuration parameter holding the entity name
*/
@ -48,8 +51,11 @@ public interface IdentifierGenerator extends Configurable, ExportableProducer {
String JPA_ENTITY_NAME = "jpa_entity_name";
/**
* Used as a key to pass the name used as {@link GeneratedValue#generator()} to the
* {@link IdentifierGenerator} as it is configured.
* The configuration parameter holding the name of this
* identifier generator.
*
* @see org.hibernate.annotations.GenericGenerator#name()
* @see jakarta.persistence.GeneratedValue#generator().
*/
String GENERATOR_NAME = "GENERATOR_NAME";
@ -62,7 +68,8 @@ public interface IdentifierGenerator extends Configurable, ExportableProducer {
* Configure this instance, given the value of parameters
* specified by the user as {@code &lt;param&gt;} elements.
* <p>
* This method is called just once, following instantiation, and before {@link #registerExportables(Database)}.
* This method is called just once, following instantiation,
* and before {@link #registerExportables(Database)}.
*
* @param type The id property type descriptor
* @param params param values, keyed by parameter name
@ -70,13 +77,15 @@ public interface IdentifierGenerator extends Configurable, ExportableProducer {
* @throws MappingException If configuration fails.
*/
@Override
default void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
default void configure(Type type, Properties params, ServiceRegistry serviceRegistry) {
}
/**
* Register database objects used by this identifier generator, e.g. sequences, tables, etc.
* Register database objects used by this identifier generator,
* for example, a sequence or tables.
* <p>
* This method is called just once, after {@link #configure(Type, Properties, ServiceRegistry)}.
* This method is called just once, after
* {@link #configure(Type, Properties, ServiceRegistry)}.
*
* @param database The database instance
*/
@ -85,9 +94,12 @@ public interface IdentifierGenerator extends Configurable, ExportableProducer {
}
/**
* Initializes this instance, in particular pre-generates SQL as necessary.
* Initializes this instance, in particular pre-generates
* SQL as necessary.
* <p>
* This method is called after {@link #registerExportables(Database)}, before first use.
* This method is called after
* {@link #registerExportables(Database)},
* and before first use.
*
* @param context A context to help generate SQL strings
*/
@ -104,13 +116,26 @@ public interface IdentifierGenerator extends Configurable, ExportableProducer {
*
* @throws HibernateException Indicates trouble generating the identifier
*/
Object generate(SharedSessionContractImplementor session, Object object) throws HibernateException;
Object generate(SharedSessionContractImplementor session, Object object);
@Override
default Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue) {
return generate( session, owner );
}
@Override
default GenerationTiming getGenerationTiming() {
return INSERT;
}
/**
* Check if JDBC batch inserts are supported.
*
* @return JDBC batch inserts are supported.
*
* @deprecated this method is no longer called
*/
@Deprecated(since="6.2")
default boolean supportsJdbcBatchInserts() {
return true;
}

View File

@ -8,16 +8,14 @@ package org.hibernate.id;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type;
/**
* An {@code IdentifierGenerator} that requires creation of database objects.
* An {@link IdentifierGenerator} that requires creation of database objects.
* <p>
* All {@code PersistentIdentifierGenerator}s have access to a special mapping parameter
* in their {@link #configure(Type, Properties, ServiceRegistry)} method: schema
* All instances have access to a special mapping parameter in their
* {@link #configure(Type, Properties, ServiceRegistry)} method: schema
*
* @author Gavin King
* @author Steve Ebersole

View File

@ -40,10 +40,10 @@ public interface GeneratedValueResolver {
}
else {
InMemoryValueGenerationStrategy generation = (InMemoryValueGenerationStrategy) valueGeneration;
return new InMemoryGeneratedValueResolver( generation.getValueGenerator(), requestedTiming );
return new InMemoryGeneratedValueResolver( generation, requestedTiming );
}
}
GenerationTiming getGenerationTiming();
Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session);
Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session, Object currentValue);
}

View File

@ -32,7 +32,7 @@ public class InDatabaseGeneratedValueResolver implements GeneratedValueResolver
}
@Override
public Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session) {
public Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session, Object currentValue) {
return row[resultPosition];
}
}

View File

@ -7,10 +7,9 @@
package org.hibernate.metamodel.mapping;
import org.hibernate.Internal;
import org.hibernate.Session;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.ValueGenerator;
import org.hibernate.tuple.InMemoryValueGenerationStrategy;
/**
* GeneratedValueResolver impl for in-memory generation
@ -20,9 +19,9 @@ import org.hibernate.tuple.ValueGenerator;
@Internal
public class InMemoryGeneratedValueResolver implements GeneratedValueResolver {
private final GenerationTiming generationTiming;
private final ValueGenerator valueGenerator;
private final InMemoryValueGenerationStrategy valueGenerator;
public InMemoryGeneratedValueResolver(ValueGenerator valueGenerator, GenerationTiming generationTiming) {
public InMemoryGeneratedValueResolver(InMemoryValueGenerationStrategy valueGenerator, GenerationTiming generationTiming) {
this.valueGenerator = valueGenerator;
this.generationTiming = generationTiming;
}
@ -33,7 +32,7 @@ public class InMemoryGeneratedValueResolver implements GeneratedValueResolver {
}
@Override
public Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session) {
return valueGenerator.generateValue( (Session) session, entity );
public Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session, Object currentValue) {
return valueGenerator.generate( session, entity, currentValue );
}
}

View File

@ -175,7 +175,7 @@ public class GeneratedValuesProcessor {
for ( int i = 0; i < valueDescriptors.size(); i++ ) {
final GeneratedValueDescriptor descriptor = valueDescriptors.get( i );
final Object generatedValue = descriptor.resolver.resolveGeneratedValue( dbSelectionResults, entity, session );
final Object generatedValue = descriptor.resolver.resolveGeneratedValue( dbSelectionResults, entity, session, state[i] );
state[ descriptor.attribute.getStateArrayPosition() ] = generatedValue;
descriptor.attribute.getAttributeMetadataAccess()
.resolveAttributeMetadata( entityDescriptor )

View File

@ -26,7 +26,7 @@ public class NoGeneratedValueResolver implements GeneratedValueResolver {
}
@Override
public Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session) {
public Object resolveGeneratedValue(Object[] row, Object entity, SharedSessionContractImplementor session, Object currentValue) {
throw new UnsupportedMappingException( "NoGeneratedValueResolver does not support generated values" );
}
}

View File

@ -10,7 +10,6 @@ import java.util.ArrayList;
import java.util.List;
import org.hibernate.Internal;
import org.hibernate.Session;
import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey;
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.MutationExecutor;
@ -104,7 +103,7 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
final InMemoryValueGenerationStrategy[] strategies = entityMetamodel.getInMemoryValueGenerationStrategies();
for ( int i = 0; i < strategies.length; i++ ) {
if ( strategies[i] != null && strategies[i].getGenerationTiming().includesInsert() ) {
values[i] = strategies[i].getValueGenerator().generateValue( (Session) session, entity, values[i] );
values[i] = strategies[i].generate( session, entity, values[i] );
entityPersister().setPropertyValue( entity, i, values[i] );
}
}

View File

@ -14,7 +14,6 @@ import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.Internal;
import org.hibernate.Session;
import org.hibernate.engine.OptimisticLockStyle;
import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey;
import org.hibernate.engine.jdbc.batch.spi.BatchKey;
@ -427,10 +426,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
for ( int i = 0; i < valueGenerationStrategies.length; i++ ) {
if ( valueGenerationStrategies[i] != null
&& valueGenerationStrategies[i].getGenerationTiming().includesUpdate() ) {
newValues[i] = valueGenerationStrategies[i].getValueGenerator().generateValue(
(Session) session,
object
);
newValues[i] = valueGenerationStrategies[i].generate( session, object, newValues[i] );
entityPersister().setPropertyValue( object, i, newValues[i] );
fieldsPreUpdateNeeded[count++] = i;
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.tuple;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
/**
* 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
@ -16,14 +18,16 @@ package org.hibernate.tuple;
* @since 6.2
*/
public interface InMemoryValueGenerationStrategy extends ValueGenerationStrategy {
/**
* 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.
* Generate a value.
*
* @return The value generator
* @param session The session from which the request originates.
* @param owner The instance of the object owning the attribute for which we are generating a value.
* @param currentValue The current value assigned to the property, or {@code null}
*
* @return The generated value
*/
ValueGenerator<?> getValueGenerator();
Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue);
default boolean generatedByDatabase() {
return false;

View File

@ -8,10 +8,10 @@ package org.hibernate.tuple;
import org.hibernate.MappingException;
import org.hibernate.PropertyValueException;
import org.hibernate.Session;
import org.hibernate.annotations.TenantId;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.descriptor.java.JavaType;
/**
@ -20,7 +20,7 @@ import org.hibernate.type.descriptor.java.JavaType;
* @author Gavin King
*/
public class TenantIdGeneration
implements AnnotationValueGenerationStrategy<TenantId>, InMemoryValueGenerationStrategy, ValueGenerator<Object> {
implements AnnotationValueGenerationStrategy<TenantId>, InMemoryValueGenerationStrategy {
private String entityName;
private String propertyName;
@ -39,13 +39,8 @@ public class TenantIdGeneration
}
@Override
public ValueGenerator<?> getValueGenerator() {
return this;
}
@Override
public Object generateValue(Session session, Object owner, Object currentValue) {
SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) session.getSessionFactory();
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue) {
SessionFactoryImplementor sessionFactory = session.getSessionFactory();
JavaType<Object> descriptor = sessionFactory.getTypeConfiguration().getJavaTypeRegistry()
.findDescriptor(propertyType);
if ( descriptor==null ) {
@ -70,9 +65,4 @@ public class TenantIdGeneration
}
return tenantId == null ? null : descriptor.fromString(tenantId); //convert to the model type
}
@Override
public Object generateValue(Session session, Object owner) {
throw new UnsupportedOperationException();
}
}

View File

@ -6,7 +6,9 @@
*/
package org.hibernate.tuple;
import org.hibernate.Session;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
/**
* A value generator that can adapt to both Java value generation and database value
@ -16,6 +18,19 @@ import org.hibernate.dialect.Dialect;
* @author Gavin King
*/
public interface ValueGeneration extends InMemoryValueGenerationStrategy, InDatabaseValueGenerationStrategy {
/**
* 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 value generator
*/
ValueGenerator<?> getValueGenerator();
@Override
default Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue) {
return getValueGenerator().generateValue( (Session) session, owner, currentValue );
}
/**
* 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

View File

@ -52,8 +52,9 @@ public interface ValueGenerationStrategy extends Serializable {
/**
* Determines if the property value is generated in Java, or by the database.
*
* @return {@code true} if the value is generated by the database, or false if it is
* generated in Java using a {@link ValueGenerator}.
* generated in Java using a {@link ValueGenerator}.
*/
boolean generatedByDatabase();
}

View File

@ -34,6 +34,6 @@ public interface ValueGenerator<T> {
* @return The generated value
*/
default T generateValue(Session session, Object owner, Object currentValue) {
return generateValue(session, owner);
return generateValue( session, owner );
}
}

View File

@ -9,7 +9,9 @@ package org.hibernate.tuple;
import java.lang.reflect.Constructor;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.annotations.GeneratorType;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.ReflectHelper;
/**
@ -21,11 +23,17 @@ public class VmValueGeneration
implements AnnotationValueGenerationStrategy<GeneratorType>, InMemoryValueGenerationStrategy {
private GenerationTiming generationTiming;
private Constructor<? extends ValueGenerator<?>> constructor;
ValueGenerator<?> generator;
@Override
public void initialize(GeneratorType annotation, Class<?> propertyType, String entityName, String propertyName) {
constructor = ReflectHelper.getDefaultConstructor( annotation.type() );
Constructor<? extends ValueGenerator<?>> constructor = ReflectHelper.getDefaultConstructor(annotation.type());
try {
generator = constructor.newInstance();
}
catch (Exception e) {
throw new HibernateException( "Couldn't instantiate value generator", e );
}
generationTiming = annotation.when().getEquivalent();
}
@ -35,12 +43,7 @@ public class VmValueGeneration
}
@Override
public ValueGenerator<?> getValueGenerator() {
try {
return constructor.newInstance();
}
catch (Exception e) {
throw new HibernateException( "Couldn't instantiate value generator", e );
}
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue) {
return generator.generateValue( (Session) session, owner, currentValue );
}
}

View File

@ -29,6 +29,7 @@ import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadeStyles;
import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
@ -47,7 +48,6 @@ import org.hibernate.tuple.IdentifierProperty;
import org.hibernate.tuple.InMemoryValueGenerationStrategy;
import org.hibernate.tuple.NonIdentifierAttribute;
import org.hibernate.tuple.PropertyFactory;
import org.hibernate.tuple.ValueGenerator;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.ComponentType;
@ -304,28 +304,25 @@ public class EntityMetamodel implements Serializable {
inMemoryValueGenerationStrategies[i] = pair.getInMemoryStrategy();
inDatabaseValueGenerationStrategies[i] = pair.getInDatabaseStrategy();
if ( pair.getInMemoryStrategy() != null ) {
if ( pair.getInMemoryStrategy() != null
&& !pair.getInMemoryStrategy().generatedByDatabase() ) {
final GenerationTiming timing = pair.getInMemoryStrategy().getGenerationTiming();
if ( timing != GenerationTiming.NEVER ) {
final ValueGenerator<?> generator = pair.getInMemoryStrategy().getValueGenerator();
if ( generator != null ) {
// we have some level of generation indicated
switch ( timing ) {
case INSERT:
foundPreInsertGeneratedValues = true;
break;
case UPDATE:
foundPreUpdateGeneratedValues = true;
break;
case ALWAYS:
foundPreInsertGeneratedValues = true;
foundPreUpdateGeneratedValues = true;
break;
}
}
// we have some level of generation indicated
switch ( timing ) {
case INSERT:
foundPreInsertGeneratedValues = true;
break;
case UPDATE:
foundPreUpdateGeneratedValues = true;
break;
case ALWAYS:
foundPreInsertGeneratedValues = true;
foundPreUpdateGeneratedValues = true;
break;
}
}
if ( pair.getInDatabaseStrategy() != null ) {
if ( pair.getInDatabaseStrategy() != null
&& pair.getInDatabaseStrategy().generatedByDatabase() ) {
switch ( pair.getInDatabaseStrategy().getGenerationTiming() ) {
case INSERT:
foundPostInsertGeneratedValues = true;
@ -463,9 +460,7 @@ public class EntityMetamodel implements Serializable {
// the property is generated in full. build the generation strategy pair.
if ( !valueGeneration.generatedByDatabase() ) {
// in-memory generation
return new GenerationStrategyPair(
FullInMemoryValueGenerationStrategy.create( (InMemoryValueGenerationStrategy) valueGeneration )
);
return new GenerationStrategyPair( (InMemoryValueGenerationStrategy) valueGeneration );
}
else {
// in-db generation
@ -503,7 +498,7 @@ public class EntityMetamodel implements Serializable {
this( NoInMemoryValueGenerationStrategy.INSTANCE, NoInDatabaseValueGenerationStrategy.INSTANCE );
}
public GenerationStrategyPair(FullInMemoryValueGenerationStrategy inMemoryStrategy) {
public GenerationStrategyPair(InMemoryValueGenerationStrategy inMemoryStrategy) {
this( inMemoryStrategy, NoInDatabaseValueGenerationStrategy.INSTANCE );
}
@ -685,38 +680,11 @@ public class EntityMetamodel implements Serializable {
}
@Override
public ValueGenerator<?> getValueGenerator() {
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue) {
return null;
}
}
private static class FullInMemoryValueGenerationStrategy implements InMemoryValueGenerationStrategy {
private final GenerationTiming timing;
private final ValueGenerator<?> generator;
private FullInMemoryValueGenerationStrategy(GenerationTiming timing, ValueGenerator<?> generator) {
this.timing = timing;
this.generator = generator;
}
public static FullInMemoryValueGenerationStrategy create(InMemoryValueGenerationStrategy valueGeneration) {
return new FullInMemoryValueGenerationStrategy(
valueGeneration.getGenerationTiming(),
valueGeneration.getValueGenerator()
);
}
@Override
public GenerationTiming getGenerationTiming() {
return timing;
}
@Override
public ValueGenerator<?> getValueGenerator() {
return generator;
}
}
private static class NoInDatabaseValueGenerationStrategy implements InDatabaseValueGenerationStrategy {
/**
* Singleton access

View File

@ -19,10 +19,10 @@ import jakarta.persistence.Table;
import org.hibernate.Session;
import org.hibernate.annotations.ValueGenerationType;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
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;
import org.hibernate.testing.orm.junit.SessionFactory;
@ -66,9 +66,7 @@ public class GeneratedUuidTests {
assertThat( merged ).isNotNull();
// lastly, make sure we can load it..
final GeneratedUuidEntity loaded = scope.fromTransaction( (session) -> {
return session.get( GeneratedUuidEntity.class, 1 );
} );
final GeneratedUuidEntity loaded = scope.fromTransaction( (session) -> session.get( GeneratedUuidEntity.class, 1 ));
assertThat( loaded ).isNotNull();
@ -88,7 +86,7 @@ public class GeneratedUuidTests {
//tag::mapping-generated-custom-ex3[]
public static class UuidValueGeneration
implements AnnotationValueGenerationStrategy<GeneratedUuidValue>, InMemoryValueGenerationStrategy, ValueGenerator<UUID> {
implements AnnotationValueGenerationStrategy<GeneratedUuidValue>, InMemoryValueGenerationStrategy {
private GenerationTiming timing;
@Override
@ -102,12 +100,7 @@ public class GeneratedUuidTests {
}
@Override
public ValueGenerator<?> getValueGenerator() {
return this;
}
@Override
public UUID generateValue(Session session, Object owner) {
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue) {
return UUID.randomUUID();
}
}