HHH-18815 introduce Generator.generatedBeforeExecution()

so that GeneratedGeneration does not need to implement BeforeExecutionGenerator

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-11-06 13:09:23 +01:00
parent 5fca1206b2
commit cf0ab77cf2
9 changed files with 104 additions and 53 deletions

View File

@ -103,6 +103,7 @@ public abstract class AbstractSaveEventListener<C> implements CallbackRegistryCo
final EntityPersister persister = source.getEntityPersister( entityName, entity );
final Generator generator = persister.getGenerator();
final boolean generatedOnExecution = generator.generatedOnExecution( entity, source );
final boolean generatedBeforeExecution = generator.generatedBeforeExecution( entity, source );
final Object generatedId;
if ( generatedOnExecution ) {
// the id gets generated by the database
@ -114,7 +115,7 @@ public abstract class AbstractSaveEventListener<C> implements CallbackRegistryCo
// the @PrePersist callback to happen first
generatedId = null;
}
else {
else if ( generatedBeforeExecution ) {
// go ahead and generate id, and then set it to
// the entity instance, so it will be available
// to the entity in the @PrePersist callback
@ -124,6 +125,11 @@ public abstract class AbstractSaveEventListener<C> implements CallbackRegistryCo
}
persister.setIdentifier( entity, generatedId, source );
}
else {
// the generator is refusing to generate anything
// so use the identifier currently assigned
generatedId = persister.getIdentifier( entity, source );
}
final boolean delayIdentityInserts =
!source.isTransactionInProgress()
&& !requiresImmediateIdAccess

View File

@ -5,6 +5,7 @@
package org.hibernate.generator;
import org.hibernate.Internal;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import java.util.EnumSet;
@ -29,6 +30,16 @@ public class Assigned implements Generator {
return false;
}
@Override
public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) {
return false;
}
@Override
public boolean generatedBeforeExecution(Object entity, SharedSessionContractImplementor session) {
return false;
}
@Override
public boolean allowAssignedIdentifiers() {
return true;

View File

@ -32,9 +32,10 @@ import static org.hibernate.generator.EventType.UPDATE;
* SQL {@code select}, though in certain cases this additional round trip may be avoided.
* An important example is id generation using an identity column.
* </ul>
* A Generator may implement both interfaces and determine the timing of ID generation at runtime.
* Furthermore, this condition can be based on the state of the owner entity, see
* {@link #generatedOnExecution(Object, SharedSessionContractImplementor) generatedOnExecution}.
* A {@code Generator} may implement both interfaces and determine the timing of identifier
* generation at runtime. Furthermore, this condition can be based on the state of the owner entity,
* see {@link #generatedOnExecution(Object, SharedSessionContractImplementor) generatedOnExecution} and
* {@link #generatedBeforeExecution(Object, SharedSessionContractImplementor) generatedBeforeExecution}.
* <p>
* Generically, a generator may be integrated with the program using the meta-annotation
* {@link org.hibernate.annotations.ValueGenerationType}, which associates the generator with
@ -95,20 +96,16 @@ public interface Generator extends Serializable {
boolean generatedOnExecution();
/**
* Determines if the property value is generated when a row is written to the database,
* or in Java code that executes before the row is written.
* Determines if the property value is generated when a row is written to the database.
* <p>
* Defaults to {@link #generatedOnExecution()}, but can be overloaded allowing conditional
* value generation timing (on/before execution) based on the current state of the owner entity.
* Note that a generator <b>must</b> implement both {@link BeforeExecutionGenerator} and
* {@link OnExecutionGenerator} to achieve this behavior.
* Defaults to {@link #generatedOnExecution()}, but may be overridden to allow conditional
* on-execution value generation based on the current state of the owner entity.
*
* @param entity The instance of the entity owning the attribute for which we are generating a value.
* @param session The session from which the request originates.
*
* @return {@code true} if the value is generated by the database as a side effect of
* the execution of an {@code insert} or {@code update} statement, or false if
* it is generated in Java code before the statement is executed via JDBC.
* the execution of an {@code insert} or {@code update} statement.
*
* @see #generatedOnExecution()
* @see BeforeExecutionGenerator
@ -120,6 +117,30 @@ public interface Generator extends Serializable {
return generatedOnExecution();
}
/**
* Determines if the property value is generated before in Java code that executes before
* the row is written.
* <p>
* Defaults to {@link #generatedOnExecution() !generatedOnExecution()}, but may be overridden
* to allow conditional before-execution value generation based on the current state of the
* owner entity.
*
* @param entity The instance of the entity owning the attribute for which we are generating a value.
* @param session The session from which the request originates.
*
* @return {@code true} if the value is generated in Java code before the statement is
* executed via JDBC.
*
* @see #generatedOnExecution()
* @see BeforeExecutionGenerator
* @see OnExecutionGenerator
*
* @since 7.0
*/
default boolean generatedBeforeExecution(Object entity, SharedSessionContractImplementor session) {
return !generatedOnExecution();
}
/**
* The {@linkplain EventType event types} for which this generator should be called
* to produce a new value.

View File

@ -8,7 +8,6 @@ import org.hibernate.AnnotationException;
import org.hibernate.annotations.Generated;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.EventType;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.persister.entity.EntityPersister;
@ -27,7 +26,7 @@ import static org.hibernate.internal.util.StringHelper.isEmpty;
* @author Steve Ebersole
* @author Gunnar Morling
*/
public class GeneratedGeneration implements OnExecutionGenerator, BeforeExecutionGenerator {
public class GeneratedGeneration implements OnExecutionGenerator {
private final EnumSet<EventType> eventTypes;
private final boolean writable;
@ -72,11 +71,6 @@ public class GeneratedGeneration implements OnExecutionGenerator, BeforeExecutio
return writable;
}
@Override
public boolean generatedOnExecution() {
return true;
}
@Override
public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) {
if ( writable ) {
@ -90,13 +84,6 @@ public class GeneratedGeneration implements OnExecutionGenerator, BeforeExecutio
}
}
@Override
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
final EntityPersister entityPersister = session.getEntityPersister( null, owner );
assert entityPersister.getGenerator() == this;
return entityPersister.getIdentifier( owner, session );
}
@Override
public boolean allowAssignedIdentifiers() {
return writable;

View File

@ -146,32 +146,47 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
}
}
final Generator generator = persister.getGenerator();
if ( !generator.generatedOnExecution( entity, this ) ) {
if ( generator.generatesOnInsert() ) {
if ( generator.generatedBeforeExecution( entity, this ) ) {
if ( !generator.generatesOnInsert() ) {
throw new IdentifierGenerationException( "Identifier generator must generate on insert" );
}
id = ( (BeforeExecutionGenerator) generator).generate( this, entity, null, INSERT );
if ( firePreInsert(entity, id, state, persister) ) {
return id;
}
else {
getInterceptor().onInsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() );
persister.getInsertCoordinator().insert( entity, id, state, this );
persister.setIdentifier( entity, id, this );
}
}
else if ( generator.generatedOnExecution( entity, this ) ) {
if ( !generator.generatesOnInsert() ) {
throw new IdentifierGenerationException( "Identifier generator must generate on insert" );
}
if ( firePreInsert(entity, null, state, persister) ) {
return null;
}
else {
getInterceptor().onInsert( entity, null, state, persister.getPropertyNames(), persister.getPropertyTypes() );
final GeneratedValues generatedValues = persister.getInsertCoordinator().insert( entity, state, this );
id = castNonNull( generatedValues ).getGeneratedValue( persister.getIdentifierMapping() );
persister.setIdentifier( entity, id, this );
}
}
else { // assigned identifier
id = persister.getIdentifier( entity, this );
if ( id == null ) {
throw new IdentifierGenerationException( "Identifier of entity '" + persister.getEntityName() + "' must be manually assigned before calling 'insert()'" );
}
}
if ( firePreInsert(entity, id, state, persister) ) {
return id;
}
else {
getInterceptor().onInsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() );
persister.getInsertCoordinator().insert( entity, id, state, this );
}
else {
if ( firePreInsert(entity, null, state, persister) ) {
return null;
}
getInterceptor()
.onInsert( entity, null, state, persister.getPropertyNames(), persister.getPropertyTypes() );
final GeneratedValues generatedValues = persister.getInsertCoordinator().insert( entity, state, this );
id = castNonNull( generatedValues ).getGeneratedValue( persister.getIdentifierMapping() );
}
persister.setIdentifier( entity, id, this );
forEachOwnedCollection( entity, id, persister,
(descriptor, collection) -> {
descriptor.recreate( collection, id, this);

View File

@ -776,7 +776,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
@Override
public Object execute(SharedSessionContractImplementor session, Object incomingObject) {
if ( !generator.generatedOnExecution( incomingObject, session ) ) {
if ( generator.generatedBeforeExecution( incomingObject, session ) ) {
return generator.generate( session, incomingObject, null, INSERT );
}
else {

View File

@ -140,7 +140,7 @@ public class InsertCoordinatorStandard extends AbstractMutationCoordinator imple
final Generator generator = generators[i];
if ( generator != null
&& generator.generatesOnInsert()
&& !generator.generatedOnExecution( entity, session ) ) {
&& generator.generatedBeforeExecution( entity, session ) ) {
values[i] = ( (BeforeExecutionGenerator) generator ).generate( session, entity, values[i], INSERT );
persister.setPropertyValue( entity, i, values[i] );
foundStateDependentGenerator = foundStateDependentGenerator || generator.generatedOnExecution();
@ -407,7 +407,7 @@ public class InsertCoordinatorStandard extends AbstractMutationCoordinator imple
else {
final Generator generator = attributeMapping.getGenerator();
if ( isValueGenerated( generator ) ) {
if ( session != null && !generator.generatedOnExecution( object, session ) ) {
if ( session != null && generator.generatedBeforeExecution( object, session ) ) {
attributeInclusions[attributeIndex] = true;
attributeMapping.forEachInsertable( insertGroupBuilder );
}

View File

@ -540,7 +540,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
final Generator generator = generators[i];
if ( generator != null
&& generator.generatesOnUpdate()
&& !generator.generatedOnExecution( object, session ) ) {
&& generator.generatedBeforeExecution( object, session ) ) {
newValues[i] = ( (BeforeExecutionGenerator) generator ).generate( session, object, newValues[i], UPDATE );
entityPersister().setPropertyValue( object, i, newValues[i] );
fieldsPreUpdateNeeded[count++] = i;
@ -667,7 +667,8 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
final Generator generator = attributeMapping.getGenerator();
final boolean generated = isValueGenerated( generator );
final boolean needsDynamicUpdate = generated && session != null && !generator.generatedOnExecution( entity, session );
final boolean needsDynamicUpdate =
generated && session != null && generator.generatedBeforeExecution( entity, session );
final boolean generatedInSql = generated && isValueGenerationInSql( generator, dialect );
if ( generatedInSql && !needsDynamicUpdate && !( (OnExecutionGenerator) generator ).writePropertyValue() ) {
analysis.registerValueGeneratedInSqlNoWrite();

View File

@ -306,8 +306,13 @@ public class MixedTimingGeneratorsTest {
}
@Override
public boolean generatedOnExecution(Object owner, SharedSessionContractImplementor session) {
return !( (RandomEntity) owner ).getName().contains( "random" );
public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) {
return !generatedBeforeExecution( entity, session );
}
@Override
public boolean generatedBeforeExecution(Object entity, SharedSessionContractImplementor session) {
return ( (RandomEntity) entity ).getName().contains( "random" );
}
}
@ -333,8 +338,13 @@ public class MixedTimingGeneratorsTest {
}
@Override
public boolean generatedOnExecution(Object owner, SharedSessionContractImplementor session) {
return !( (StringGeneratedEntity) owner ).getName().contains( "generated" );
public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) {
return !generatedBeforeExecution( entity, session );
}
@Override
public boolean generatedBeforeExecution(Object entity, SharedSessionContractImplementor session) {
return ( (StringGeneratedEntity) entity ).getName().contains( "generated" );
}
@Override