improve javadoc for Generator hierarchy

and make SelectGenerator a subclass of IdentityGenerator
This commit is contained in:
Gavin 2022-12-02 17:12:05 +01:00 committed by Gavin King
parent 392b2f2364
commit 9389295281
6 changed files with 148 additions and 88 deletions

View File

@ -23,8 +23,9 @@ import static org.hibernate.tuple.GenerationTiming.INSERT;
/**
* A classic extension point from the very earliest days of Hibernate,
* this interface is now no longer the only way to generate identifiers.
* Any {@link InMemoryGenerator} may now be used.
* this interface is no longer the only way to generate identifiers. Any
* {@link InMemoryGenerator} with timing {@link GenerationTiming#INSERT}
* may now be used.
* <p>
* This interface extends {@code InMemoryGenerator} with some additional
* machinery for {@linkplain #configure configuration}, and for caching
@ -54,6 +55,7 @@ import static org.hibernate.tuple.GenerationTiming.INSERT;
*
* @author Gavin King
*
* @see PostInsertIdentifierGenerator
* @see PersistentIdentifierGenerator
*/
public interface IdentifierGenerator extends InMemoryGenerator, ExportableProducer, Configurable {
@ -94,8 +96,7 @@ public interface IdentifierGenerator extends InMemoryGenerator, ExportableProduc
* @throws MappingException If configuration fails.
*/
@Override
default void configure(Type type, Properties parameters, ServiceRegistry serviceRegistry) {
}
default void configure(Type type, Properties parameters, ServiceRegistry serviceRegistry) {}
/**
* Register database objects used by this identifier generator,
@ -107,8 +108,7 @@ public interface IdentifierGenerator extends InMemoryGenerator, ExportableProduc
* @param database The database instance
*/
@Override
default void registerExportables(Database database) {
}
default void registerExportables(Database database) {}
/**
* Initializes this instance, in particular pre-generates
@ -120,8 +120,7 @@ public interface IdentifierGenerator extends InMemoryGenerator, ExportableProduc
*
* @param context A context to help generate SQL strings
*/
default void initialize(SqlStringGenerationContext context) {
}
default void initialize(SqlStringGenerationContext context) {}
/**
* Generate a new identifier.
@ -135,11 +134,19 @@ public interface IdentifierGenerator extends InMemoryGenerator, ExportableProduc
*/
Object generate(SharedSessionContractImplementor session, Object object);
/**
* Generate a value.
* <p>
* The {@code currentValue} is usually null for id generation.
*/
@Override
default Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue) {
return generate( session, owner );
}
/**
* @return {@link GenerationTiming#INSERT}
*/
@Override
default GenerationTiming getGenerationTiming() {
return INSERT;

View File

@ -6,22 +6,29 @@
*/
package org.hibernate.id;
import org.hibernate.dialect.Dialect;
import org.hibernate.id.factory.spi.StandardGenerator;
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.tuple.InDatabaseGenerator;
/**
* A generator for use with ANSI-SQL IDENTITY columns used as the primary key.
* The IdentityGenerator for autoincrement/identity key generation.
* An {@link InDatabaseGenerator} that handles {@code IDENTITY}/"autoincrement" columns
* on those databases which support them.
* <p>
* Indicates to the {@code Session} that identity (i.e. identity/autoincrement
* column) key generation should be used.
*
* @implNote Most of the functionality of this generator is delegated to
* {@link InsertGeneratedIdentifierDelegate}.
* Delegates to the {@link org.hibernate.dialect.identity.IdentityColumnSupport} provided
* by the {@linkplain Dialect#getIdentityColumnSupport() dialect}.
*
* @author Christoph Sturm
*/
public class IdentityGenerator
implements PostInsertIdentifierGenerator, BulkInsertionCapableIdentifierGenerator, StandardGenerator {
@Override
public boolean referenceColumnsInSql(Dialect dialect) {
return dialect.getIdentityColumnSupport().hasIdentityInsertKeyword();
}
@Override
public String[] getReferencedColumnValues(Dialect dialect) {
return new String[] { dialect.getIdentityColumnSupport().getIdentityInsertString() };
}
}

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.id;
import org.hibernate.dialect.Dialect;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.InDatabaseGenerator;
@ -17,31 +16,37 @@ import java.util.Properties;
import static org.hibernate.tuple.GenerationTiming.INSERT;
/**
* The counterpart of {@link IdentifierGenerator} for values generated by the database.
* This interface is no longer the only way to handle database-generate identifiers.
* Any {@link InDatabaseGenerator} with timing {@link GenerationTiming#INSERT} may now
* be used.
*
* @see IdentifierGenerator
*
* @author Gavin King
*/
public interface PostInsertIdentifierGenerator extends InDatabaseGenerator, Configurable {
/**
* @return {@link GenerationTiming#INSERT}
*/
@Override
default GenerationTiming getGenerationTiming() {
return INSERT;
}
/**
* @return {@code false}, since we don't usually have a meaningful property value
* for generated identifiers
*/
@Override
default boolean writePropertyValue() {
return false;
}
@Override
default boolean referenceColumnsInSql(Dialect dialect) {
return dialect.getIdentityColumnSupport().hasIdentityInsertKeyword();
}
@Override
default String[] getReferencedColumnValues(Dialect dialect) {
return new String[] { dialect.getIdentityColumnSupport().getIdentityInsertString() };
}
/**
* Noop default implementation. May be overridden by subtypes.
*/
@Override
default void configure(Type type, Properties params, ServiceRegistry serviceRegistry) {}
}

View File

@ -8,28 +8,30 @@ package org.hibernate.id;
import java.util.Properties;
import org.hibernate.id.factory.spi.StandardGenerator;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type;
/**
* A generator that {@code select}s the just-inserted row to determine the identifier
* value assigned by the database. The correct row is located using a unique key of
* the entity, either:
* A generator that {@code select}s the just-{@code insert}ed row to determine the
* {@code IDENTITY} column value assigned by the database. The correct row is located
* using a unique key of the entity, either:
* <ul>
* <li>the mapped {@linkplain org.hibernate.annotations.NaturalId} of the entity, or
* <li>a property specified using the parameter named {@code "key"}.
* </ul>
* The second approach is provided for backward compatibility with older versions of
* Hibernate.
* <p>
* Arguably, this class breaks the natural separation of responsibility between the
* {@linkplain org.hibernate.tuple.InDatabaseGenerator generator} and the coordinating
* code, since it's role is to specify how the generated value is <em>retrieved</em>.
*
* @see org.hibernate.annotations.NaturalId
*
* @author Gavin King
*/
public class SelectGenerator
implements PostInsertIdentifierGenerator, BulkInsertionCapableIdentifierGenerator, StandardGenerator {
public class SelectGenerator extends IdentityGenerator {
private String uniqueKeyPropertyName;
@Override

View File

@ -37,7 +37,18 @@ import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.Generator;
import static org.hibernate.sql.results.spi.ListResultsConsumer.UniqueSemantic.FILTER;
/**
* Responsible for retrieving {@linkplain org.hibernate.tuple.InDatabaseGenerator
* database-generated} attribute values after an {@code insert} statement is executed.
* <p>
* Note that this class has responsibility for regular attributes of the entity. The
* primary key / id attribute is handled separately, being the responsibility of an
* instance of {@link org.hibernate.id.insert.InsertGeneratedIdentifierDelegate}.
*
* @see org.hibernate.tuple.InDatabaseGenerator
*
* @author Steve Ebersole
*/
@Incubating
@ -56,12 +67,7 @@ public class GeneratedValuesProcessor {
this.entityDescriptor = entityDescriptor;
this.sessionFactory = sessionFactory;
// NOTE: we only care about db-generated values here. in-memory generation
// is applied before the insert/update happens.
// todo (6.0): for now, we rely on the entity metamodel as composite attributes report
// GenerationTiming.NEVER even if they have attributes that would need generation
final List<AttributeMapping> generatedValuesToSelect = getGeneratedValues( entityDescriptor, timing );
final List<AttributeMapping> generatedValuesToSelect = getGeneratedAttributes( entityDescriptor, timing );
if ( generatedValuesToSelect.isEmpty() ) {
selectStatement = null;
}
@ -80,7 +86,14 @@ public class GeneratedValuesProcessor {
}
}
private List<AttributeMapping> getGeneratedValues(EntityMappingType entityDescriptor, GenerationTiming timing) {
/**
* Find attributes generated by a {@link org.hibernate.tuple.InDatabaseGenerator},
* populate the list of {@link GeneratedValueDescriptor}s by side effect, and
* return a list of {@link AttributeMapping}s.
*/
private List<AttributeMapping> getGeneratedAttributes(EntityMappingType entityDescriptor, GenerationTiming timing) {
// todo (6.0): For now, we rely on the entity metamodel as composite attributes report
// GenerationTiming.NEVER even if they have attributes that would need generation
final Generator[] generators = entityDescriptor.getEntityPersister().getEntityMetamodel().getGenerators();
final List<AttributeMapping> generatedValuesToSelect = new ArrayList<>();
entityDescriptor.visitAttributeMappings( mapping -> {
@ -99,15 +112,28 @@ public class GeneratedValuesProcessor {
return generatedValuesToSelect;
}
/**
* Obtain the generated values, and populate the snapshot and the fields of the entity instance.
*/
public void processGeneratedValues(Object entity, Object id, Object[] state, SharedSessionContractImplementor session) {
if ( selectStatement == null ) {
return;
if ( selectStatement != null ) {
final List<Object[]> results = executeSelect( id, session );
assert results.size() == 1;
setEntityAttributes( entity, state, session, results.get(0) );
}
}
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
private List<Object[]> executeSelect(Object id, SharedSessionContractImplementor session) {
final JdbcParameterBindings jdbcParamBindings = getJdbcParameterBindings( id, session );
final JdbcOperationQuerySelect jdbcSelect =
sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory()
.buildSelectTranslator( sessionFactory, selectStatement )
.translate( jdbcParamBindings, QueryOptions.NONE );
return session.getFactory().getJdbcServices().getJdbcSelectExecutor()
.list( jdbcSelect, jdbcParamBindings, createExecutionContext( session ), (row) -> row, FILTER );
}
private JdbcParameterBindings getJdbcParameterBindings(Object id, SharedSessionContractImplementor session) {
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
int offset = jdbcParamBindings.registerParametersForEachJdbcValue(
id,
@ -117,14 +143,29 @@ public class GeneratedValuesProcessor {
session
);
assert offset == jdbcParameters.size();
final JdbcOperationQuerySelect jdbcSelect = sqlAstTranslatorFactory
.buildSelectTranslator( sessionFactory, selectStatement )
.translate( jdbcParamBindings, QueryOptions.NONE );
return jdbcParamBindings;
}
final List<Object[]> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParamBindings,
new ExecutionContext() {
private void setEntityAttributes(
Object entity,
Object[] state,
SharedSessionContractImplementor session,
Object[] selectionResults) {
for ( int i = 0; i < valueDescriptors.size(); i++ ) {
final GeneratedValueDescriptor descriptor = valueDescriptors.get( i );
final Object generatedValue =
descriptor.resolver.resolveGeneratedValue( selectionResults, entity, session, state[i] );
state[ descriptor.attribute.getStateArrayPosition() ] = generatedValue;
descriptor.attribute.getAttributeMetadataAccess()
.resolveAttributeMetadata( entityDescriptor )
.getPropertyAccess()
.getSetter()
.set( entity, generatedValue );
}
}
private static ExecutionContext createExecutionContext(SharedSessionContractImplementor session) {
return new ExecutionContext() {
@Override
public SharedSessionContractImplementor getSession() {
return session;
@ -149,25 +190,7 @@ public class GeneratedValuesProcessor {
public Callback getCallback() {
throw new UnsupportedMappingException("Follow-on locking not supported yet");
}
},
(row) -> row,
ListResultsConsumer.UniqueSemantic.FILTER
);
assert results.size() == 1;
final Object[] dbSelectionResults = results.get( 0 );
for ( int i = 0; i < valueDescriptors.size(); i++ ) {
final GeneratedValueDescriptor descriptor = valueDescriptors.get( i );
final Object generatedValue = descriptor.resolver.resolveGeneratedValue( dbSelectionResults, entity, session, state[i] );
state[ descriptor.attribute.getStateArrayPosition() ] = generatedValue;
descriptor.attribute.getAttributeMetadataAccess()
.resolveAttributeMetadata( entityDescriptor )
.getPropertyAccess()
.getSetter()
.set( entity, generatedValue );
}
};
}
private static class GeneratedValueDescriptor {

View File

@ -19,6 +19,11 @@ import org.hibernate.persister.entity.EntityPersister;
* Implementations should override {@link #referenceColumnsInSql(Dialect)},
* {@link #writePropertyValue()}, and {@link #getReferencedColumnValues(Dialect)} as needed
* in order to achieve the desired behavior.
* <p>
* In implementation of this interface does not specify how the generated value is retrieved
* from the database after it is generated, this being the responsibility of the coordinating
* code in {@link org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor} or in an
* implementation of {@link org.hibernate.id.insert.InsertGeneratedIdentifierDelegate}.
*
* @author Steve Ebersole
*
@ -63,6 +68,17 @@ public interface InDatabaseGenerator extends Generator {
*/
String[] getReferencedColumnValues(Dialect dialect);
/**
* The name of a property of the entity which may be used to locate the just-{@code insert}ed
* row containing the generated value. Of course, the columns mapped by this property should
* form a unique key of the entity.
* <p>
* This is ignored by {@link org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor},
* which handles multiple generators at once. This method arguably breaks the separation of
* concerns between the generator and the coordinating code.
*
* @see org.hibernate.id.SelectGenerator
*/
default String getUniqueKeyPropertyName(EntityPersister persister) {
return null;
}