HHH-17322 Generator method that allows state dependent value generation
This commit is contained in:
parent
2bfaa419f3
commit
49eab3fca2
|
@ -105,7 +105,7 @@ public abstract class AbstractSaveEventListener<C>
|
|||
boolean requiresImmediateIdAccess) {
|
||||
final EntityPersister persister = source.getEntityPersister( entityName, entity );
|
||||
final Generator generator = persister.getGenerator();
|
||||
final boolean generatedOnExecution = generator.generatedOnExecution();
|
||||
final boolean generatedOnExecution = generator.generatedOnExecution( entity, source );
|
||||
final Object generatedId;
|
||||
if ( generatedOnExecution ) {
|
||||
// the id gets generated by the database
|
||||
|
|
|
@ -16,10 +16,10 @@ import static org.hibernate.generator.EventType.UPDATE;
|
|||
|
||||
/**
|
||||
* 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. Every instance must implement either
|
||||
* {@link BeforeExecutionGenerator} or {@link OnExecutionGenerator} depending on whether values
|
||||
* are generated in Java code before execution of a SQL statement, or by the database when the
|
||||
* SQL statement is executed.
|
||||
* value might be generated in Java, or by the database. Every instance of this interface must
|
||||
* implement either or both of {@link BeforeExecutionGenerator} and {@link OnExecutionGenerator}
|
||||
* depending on whether values are generated in Java code before execution of a SQL statement,
|
||||
* or by the database when the SQL statement is executed.
|
||||
* <ul>
|
||||
* <li>Value generation via arbitrary code written in Java is the responsibility of the method
|
||||
* {@link BeforeExecutionGenerator#generate(SharedSessionContractImplementor, Object, Object, EventType)}.
|
||||
|
@ -34,6 +34,9 @@ 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}.
|
||||
* <p>
|
||||
* Generically, a generator may be integrated with the program using the meta-annotation
|
||||
* {@link org.hibernate.annotations.ValueGenerationType}, which associates the generator with
|
||||
|
@ -93,6 +96,32 @@ 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.
|
||||
* <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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @see #generatedOnExecution()
|
||||
* @see BeforeExecutionGenerator
|
||||
* @see OnExecutionGenerator
|
||||
* @since 6.4
|
||||
*/
|
||||
default boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) {
|
||||
return generatedOnExecution();
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@linkplain EventType event types} for which this generator should be called
|
||||
* to produce a new value.
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.hibernate.cfg.AvailableSettings;
|
|||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.config.spi.ConfigurationService;
|
||||
import org.hibernate.engine.config.spi.StandardConverters;
|
||||
import org.hibernate.generator.BeforeExecutionGenerator;
|
||||
import org.hibernate.id.IdentifierGenerator;
|
||||
import org.hibernate.id.OptimizableGenerator;
|
||||
import org.hibernate.id.PersistentIdentifierGenerator;
|
||||
|
@ -123,11 +124,18 @@ public class IdentifierGeneratorUtil {
|
|||
);
|
||||
}
|
||||
|
||||
return identifierGeneratorFactory.createIdentifierGenerator(
|
||||
final Generator generator = identifierGeneratorFactory.createIdentifierGenerator(
|
||||
simpleValue.getIdentifierGeneratorStrategy(),
|
||||
simpleValue.getType(),
|
||||
params
|
||||
);
|
||||
|
||||
if ( generator.generatedOnExecution() && generator instanceof BeforeExecutionGenerator ) {
|
||||
// support mixed-timing generators
|
||||
simpleValue.setNullValue( "undefined" );
|
||||
}
|
||||
|
||||
return generator;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
|
|||
final Object id;
|
||||
final Object[] state = persister.getValues( entity );
|
||||
final Generator generator = persister.getGenerator();
|
||||
if ( !generator.generatedOnExecution() ) {
|
||||
if ( !generator.generatedOnExecution( entity, this ) ) {
|
||||
id = ( (BeforeExecutionGenerator) generator).generate( this, entity, null, INSERT );
|
||||
if ( persister.isVersioned() ) {
|
||||
if ( seedVersion( entity, state, persister, this ) ) {
|
||||
|
|
|
@ -647,7 +647,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
|
|||
|
||||
@Override
|
||||
public void execute(SharedSessionContractImplementor session, Object incomingObject, Object injectionContext) {
|
||||
if ( !subgenerator.generatedOnExecution() ) {
|
||||
if ( !subgenerator.generatedOnExecution( incomingObject, session ) ) {
|
||||
final Object generatedId = ( (BeforeExecutionGenerator) subgenerator)
|
||||
.generate( session, incomingObject, null, INSERT );
|
||||
injector.set( injectionContext, generatedId );
|
||||
|
|
|
@ -111,13 +111,22 @@ public class GeneratedValuesProcessor {
|
|||
* 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 ) {
|
||||
if ( selectStatement != null && hasActualGeneratedValuesToSelect( session, entity ) ) {
|
||||
final List<Object[]> results = executeSelect( id, session );
|
||||
assert results.size() == 1;
|
||||
setEntityAttributes( entity, state, results.get(0) );
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasActualGeneratedValuesToSelect(SharedSessionContractImplementor session, Object entity) {
|
||||
for ( AttributeMapping attributeMapping : generatedValuesToSelect ) {
|
||||
if ( attributeMapping.getGenerator().generatedOnExecution( entity, session ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<Object[]> executeSelect(Object id, SharedSessionContractImplementor session) {
|
||||
final JdbcParameterBindings jdbcParamBindings = getJdbcParameterBindings( id, session );
|
||||
return session.getFactory().getJdbcServices().getJdbcSelectExecutor()
|
||||
|
|
|
@ -102,32 +102,36 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
|
|||
Object entity,
|
||||
SharedSessionContractImplementor session) {
|
||||
// apply any pre-insert in-memory value generation
|
||||
preInsertInMemoryValueGeneration( values, entity, session );
|
||||
final boolean needsDynamicInsert = preInsertInMemoryValueGeneration( values, entity, session );
|
||||
|
||||
final EntityMetamodel entityMetamodel = entityPersister().getEntityMetamodel();
|
||||
if ( entityMetamodel.isDynamicInsert() ) {
|
||||
return doDynamicInserts( id, values, entity, session );
|
||||
final boolean forceIdentifierBinding = entityPersister().getGenerator().generatedOnExecution() && id != null;
|
||||
if ( entityMetamodel.isDynamicInsert() || needsDynamicInsert || forceIdentifierBinding ) {
|
||||
return doDynamicInserts( id, values, entity, session, forceIdentifierBinding );
|
||||
}
|
||||
else {
|
||||
return doStaticInserts( id, values, entity, session );
|
||||
}
|
||||
}
|
||||
|
||||
protected void preInsertInMemoryValueGeneration(Object[] values, Object entity, SharedSessionContractImplementor session) {
|
||||
protected boolean preInsertInMemoryValueGeneration(Object[] values, Object entity, SharedSessionContractImplementor session) {
|
||||
final AbstractEntityPersister persister = entityPersister();
|
||||
final EntityMetamodel entityMetamodel = persister.getEntityMetamodel();
|
||||
boolean foundStateDependentGenerator = false;
|
||||
if ( entityMetamodel.hasPreInsertGeneratedValues() ) {
|
||||
final Generator[] generators = entityMetamodel.getGenerators();
|
||||
for ( int i = 0; i < generators.length; i++ ) {
|
||||
final Generator generator = generators[i];
|
||||
if ( generator != null
|
||||
&& !generator.generatedOnExecution()
|
||||
&& !generator.generatedOnExecution( entity, session )
|
||||
&& generator.generatesOnInsert() ) {
|
||||
values[i] = ( (BeforeExecutionGenerator) generator ).generate( session, entity, values[i], INSERT );
|
||||
persister.setPropertyValue( entity, i, values[i] );
|
||||
foundStateDependentGenerator = foundStateDependentGenerator || generator.generatedOnExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundStateDependentGenerator;
|
||||
}
|
||||
|
||||
protected static class InsertValuesAnalysis implements ValuesAnalysis {
|
||||
|
@ -273,9 +277,14 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
|
|||
}
|
||||
}
|
||||
|
||||
protected Object doDynamicInserts(Object id, Object[] values, Object object, SharedSessionContractImplementor session) {
|
||||
protected Object doDynamicInserts(
|
||||
Object id,
|
||||
Object[] values,
|
||||
Object object,
|
||||
SharedSessionContractImplementor session,
|
||||
boolean forceIdentifierBinding) {
|
||||
final boolean[] insertability = getPropertiesToInsert( values );
|
||||
final MutationOperationGroup insertGroup = generateDynamicInsertSqlGroup( insertability );
|
||||
final MutationOperationGroup insertGroup = generateDynamicInsertSqlGroup( insertability, object, session, forceIdentifierBinding );
|
||||
|
||||
final MutationExecutor mutationExecutor = executor( session, insertGroup, true );
|
||||
|
||||
|
@ -330,28 +339,31 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
|
|||
return notNull;
|
||||
}
|
||||
|
||||
protected MutationOperationGroup generateDynamicInsertSqlGroup(boolean[] insertable) {
|
||||
assert entityPersister().getEntityMetamodel().isDynamicInsert();
|
||||
protected MutationOperationGroup generateDynamicInsertSqlGroup(
|
||||
boolean[] insertable,
|
||||
Object object,
|
||||
SharedSessionContractImplementor session,
|
||||
boolean forceIdentifierBinding) {
|
||||
final MutationGroupBuilder insertGroupBuilder = new MutationGroupBuilder( MutationType.INSERT, entityPersister() );
|
||||
entityPersister().forEachMutableTable(
|
||||
(tableMapping) -> insertGroupBuilder.addTableDetailsBuilder( createTableInsertBuilder( tableMapping ) )
|
||||
(tableMapping) -> insertGroupBuilder.addTableDetailsBuilder( createTableInsertBuilder( tableMapping, forceIdentifierBinding ) )
|
||||
);
|
||||
applyTableInsertDetails( insertGroupBuilder, insertable );
|
||||
applyTableInsertDetails( insertGroupBuilder, insertable, object, session, forceIdentifierBinding );
|
||||
return createOperationGroup( null, insertGroupBuilder.buildMutationGroup() );
|
||||
}
|
||||
|
||||
public MutationOperationGroup generateStaticOperationGroup() {
|
||||
final MutationGroupBuilder insertGroupBuilder = new MutationGroupBuilder( MutationType.INSERT, entityPersister() );
|
||||
entityPersister().forEachMutableTable(
|
||||
(tableMapping) -> insertGroupBuilder.addTableDetailsBuilder( createTableInsertBuilder( tableMapping ) )
|
||||
(tableMapping) -> insertGroupBuilder.addTableDetailsBuilder( createTableInsertBuilder( tableMapping, false ) )
|
||||
);
|
||||
applyTableInsertDetails( insertGroupBuilder, entityPersister().getPropertyInsertability() );
|
||||
applyTableInsertDetails( insertGroupBuilder, entityPersister().getPropertyInsertability(), null, null, false );
|
||||
return createOperationGroup( null, insertGroupBuilder.buildMutationGroup() );
|
||||
}
|
||||
|
||||
private TableInsertBuilder createTableInsertBuilder(EntityTableMapping tableMapping) {
|
||||
private TableInsertBuilder createTableInsertBuilder(EntityTableMapping tableMapping, boolean forceIdentifierBinding) {
|
||||
final InsertGeneratedIdentifierDelegate identityDelegate = entityPersister().getIdentityInsertDelegate();
|
||||
if ( tableMapping.isIdentifierTable() && identityDelegate != null ) {
|
||||
if ( tableMapping.isIdentifierTable() && identityDelegate != null && !forceIdentifierBinding ) {
|
||||
final BasicEntityIdentifierMapping mapping =
|
||||
(BasicEntityIdentifierMapping) entityPersister().getIdentifierMapping();
|
||||
return identityDelegate.createTableInsertBuilder( mapping, tableMapping.getInsertExpectation(), factory() );
|
||||
|
@ -363,7 +375,10 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
|
|||
|
||||
private void applyTableInsertDetails(
|
||||
MutationGroupBuilder insertGroupBuilder,
|
||||
boolean[] attributeInclusions) {
|
||||
boolean[] attributeInclusions,
|
||||
Object object,
|
||||
SharedSessionContractImplementor session,
|
||||
boolean forceIdentifierBinding) {
|
||||
final AttributeMappingsList attributeMappings = entityPersister().getAttributeMappings();
|
||||
|
||||
insertGroupBuilder.forEachTableMutationBuilder( (builder) -> {
|
||||
|
@ -381,8 +396,14 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
|
|||
}
|
||||
else {
|
||||
final Generator generator = attributeMapping.getGenerator();
|
||||
if ( isValueGenerationInSql( generator, factory().getJdbcServices().getDialect() ) ) {
|
||||
handleValueGeneration( attributeMapping, insertGroupBuilder, (OnExecutionGenerator) generator );
|
||||
if ( isValueGenerated( generator ) ) {
|
||||
if ( session != null && !generator.generatedOnExecution( object, session ) ) {
|
||||
attributeInclusions[attributeIndex] = true;
|
||||
attributeMapping.forEachInsertable( insertGroupBuilder );
|
||||
}
|
||||
else if ( isValueGenerationInSql( generator, factory().getJdbcServices().getDialect() ) ) {
|
||||
handleValueGeneration( attributeMapping, insertGroupBuilder, (OnExecutionGenerator) generator );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -398,7 +419,7 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
|
|||
final TableInsertBuilder tableInsertBuilder = (TableInsertBuilder) tableMutationBuilder;
|
||||
final EntityTableMapping tableMapping = (EntityTableMapping) tableInsertBuilder.getMutatingTable().getTableMapping();
|
||||
//noinspection StatementWithEmptyBody
|
||||
if ( tableMapping.isIdentifierTable() && identityDelegate != null ) {
|
||||
if ( tableMapping.isIdentifierTable() && identityDelegate != null && !forceIdentifierBinding ) {
|
||||
// nothing to do - the builder already includes the identity handling
|
||||
}
|
||||
else {
|
||||
|
@ -407,11 +428,15 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
|
|||
} );
|
||||
}
|
||||
|
||||
private static boolean isValueGenerationInSql(Generator generator, Dialect dialect) {
|
||||
private static boolean isValueGenerated(Generator generator) {
|
||||
return generator != null
|
||||
&& generator.generatesOnInsert()
|
||||
&& generator.generatedOnExecution()
|
||||
&& ( (OnExecutionGenerator) generator ).referenceColumnsInSql(dialect);
|
||||
&& generator.generatesOnInsert()
|
||||
&& generator.generatedOnExecution();
|
||||
}
|
||||
|
||||
private static boolean isValueGenerationInSql(Generator generator, Dialect dialect) {
|
||||
assert isValueGenerated( generator );
|
||||
return ( (OnExecutionGenerator) generator ).referenceColumnsInSql(dialect);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -288,11 +288,10 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
entityPersister()
|
||||
);
|
||||
|
||||
final InclusionChecker inclusionChecker =
|
||||
(position, attribute) -> isValueGenerationInSql( attribute.getGenerator(), dialect() )
|
||||
|| attributeUpdateability[position];
|
||||
final InclusionChecker inclusionChecker = (position, attribute) -> attributeUpdateability[position];
|
||||
|
||||
final UpdateValuesAnalysisImpl valuesAnalysis = analyzeUpdateValues(
|
||||
entity,
|
||||
values,
|
||||
oldVersion,
|
||||
incomingOldValues,
|
||||
|
@ -446,19 +445,15 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isValueGenerationInSql(Generator generator, Dialect dialect) {
|
||||
private static boolean isValueGenerated(Generator generator) {
|
||||
return generator != null
|
||||
&& generator.generatesOnUpdate()
|
||||
&& generator.generatedOnExecution()
|
||||
&& ((OnExecutionGenerator) generator).referenceColumnsInSql(dialect);
|
||||
&& generator.generatesOnUpdate()
|
||||
&& generator.generatedOnExecution();
|
||||
}
|
||||
|
||||
private boolean isValueGenerationInSqlNoWrite(Generator generator, Dialect dialect) {
|
||||
return generator != null
|
||||
&& generator.generatesOnUpdate()
|
||||
&& generator.generatedOnExecution()
|
||||
&& ((OnExecutionGenerator) generator).referenceColumnsInSql(dialect)
|
||||
&& !((OnExecutionGenerator) generator).writePropertyValue();
|
||||
private static boolean isValueGenerationInSql(Generator generator, Dialect dialect) {
|
||||
assert isValueGenerated( generator );
|
||||
return ( (OnExecutionGenerator) generator ).referenceColumnsInSql(dialect);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -560,9 +555,9 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
final int[] fieldsPreUpdateNeeded = new int[generators.length];
|
||||
int count = 0;
|
||||
for ( int i = 0; i < generators.length; i++ ) {
|
||||
Generator generator = generators[i];
|
||||
final Generator generator = generators[i];
|
||||
if ( generator != null
|
||||
&& !generator.generatedOnExecution()
|
||||
&& !generator.generatedOnExecution( object, session )
|
||||
&& generator.generatesOnUpdate() ) {
|
||||
newValues[i] = ( (BeforeExecutionGenerator) generator ).generate( session, object, newValues[i], UPDATE );
|
||||
entityPersister().setPropertyValue( object, i, newValues[i] );
|
||||
|
@ -611,6 +606,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
}
|
||||
|
||||
private UpdateValuesAnalysisImpl analyzeUpdateValues(
|
||||
Object entity,
|
||||
Object[] values,
|
||||
Object oldVersion,
|
||||
Object[] oldValues,
|
||||
|
@ -650,6 +646,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
&& attributeMapping instanceof SingularAttributeMapping ) {
|
||||
SingularAttributeMapping asSingularAttributeMapping = (SingularAttributeMapping) attributeMapping;
|
||||
processAttribute(
|
||||
entity,
|
||||
analysis,
|
||||
attributeIndex,
|
||||
asSingularAttributeMapping,
|
||||
|
@ -677,6 +674,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
}
|
||||
|
||||
private void processAttribute(
|
||||
Object entity,
|
||||
UpdateValuesAnalysisImpl analysis,
|
||||
int attributeIndex,
|
||||
SingularAttributeMapping attributeMapping,
|
||||
|
@ -686,10 +684,18 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
InclusionChecker lockingChecker,
|
||||
SharedSessionContractImplementor session) {
|
||||
|
||||
if ( inclusionChecker.include( attributeIndex, attributeMapping ) ) {
|
||||
final Generator generator = attributeMapping.getGenerator();
|
||||
final boolean generated = isValueGenerated( generator );
|
||||
final boolean needsDynamicUpdate = generated && session != null && !generator.generatedOnExecution( entity, session );
|
||||
final boolean generatedInSql = generated && isValueGenerationInSql( generator, dialect );
|
||||
if ( generatedInSql && !needsDynamicUpdate && !( (OnExecutionGenerator) generator ).writePropertyValue() ) {
|
||||
analysis.registerValueGeneratedInSqlNoWrite();
|
||||
}
|
||||
|
||||
if ( needsDynamicUpdate || generatedInSql || inclusionChecker.include( attributeIndex, attributeMapping ) ) {
|
||||
final int jdbcTypeCount = attributeMapping.getJdbcTypeCount();
|
||||
for ( int i = 0; i < jdbcTypeCount; i++ ) {
|
||||
processSet( analysis, attributeMapping.getSelectable( i ) );
|
||||
processSet( analysis, attributeMapping.getSelectable( i ), needsDynamicUpdate );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -713,10 +719,13 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
}
|
||||
}
|
||||
|
||||
private void processSet(UpdateValuesAnalysisImpl analysis, SelectableMapping selectable) {
|
||||
private void processSet(UpdateValuesAnalysisImpl analysis, SelectableMapping selectable, boolean needsDynamicUpdate) {
|
||||
if ( selectable != null && !selectable.isFormula() && selectable.isUpdateable() ) {
|
||||
final EntityTableMapping tableMapping = entityPersister().getPhysicalTableMappingForMutation( selectable );
|
||||
analysis.registerColumnSet( tableMapping, selectable.getSelectionExpression(), selectable.getWriteExpression() );
|
||||
if ( needsDynamicUpdate ) {
|
||||
analysis.getTablesNeedingDynamicUpdate().add( tableMapping );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -901,7 +910,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
int attributeIndex,
|
||||
AttributeMapping attributeMapping,
|
||||
IncludedAttributeAnalysis attributeAnalysis) {
|
||||
if ( isValueGenerationInSqlNoWrite( attributeMapping.getGenerator(), dialect() ) ) {
|
||||
if ( attributeAnalysis.isValueGeneratedInSqlNoWrite() ) {
|
||||
// we applied `#getDatabaseGeneratedReferencedColumnValue` earlier
|
||||
return false;
|
||||
}
|
||||
|
@ -928,6 +937,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
SharedSessionContractImplementor session) {
|
||||
// Create the JDBC operation descriptors
|
||||
final MutationOperationGroup dynamicUpdateGroup = generateDynamicUpdateGroup(
|
||||
entity,
|
||||
id,
|
||||
rowId,
|
||||
oldValues,
|
||||
|
@ -1020,6 +1030,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
}
|
||||
|
||||
protected MutationOperationGroup generateDynamicUpdateGroup(
|
||||
Object entity,
|
||||
Object id,
|
||||
Object rowId,
|
||||
Object[] oldValues,
|
||||
|
@ -1042,6 +1053,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
} );
|
||||
|
||||
applyTableUpdateDetails(
|
||||
entity,
|
||||
rowId,
|
||||
updateGroupBuilder,
|
||||
oldValues,
|
||||
|
@ -1058,6 +1070,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
}
|
||||
|
||||
private void applyTableUpdateDetails(
|
||||
Object entity,
|
||||
Object rowId,
|
||||
MutationGroupBuilder updateGroupBuilder,
|
||||
Object[] oldValues,
|
||||
|
@ -1082,12 +1095,14 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
if ( attributeAnalysis.includeInSet() ) {
|
||||
assert updateValuesAnalysis.tablesNeedingUpdate.contains( tableMapping );
|
||||
applyAttributeUpdateDetails(
|
||||
entity,
|
||||
updateGroupBuilder,
|
||||
dirtinessChecker,
|
||||
versionMapping,
|
||||
attributeIndex,
|
||||
attributeMapping,
|
||||
(TableUpdateBuilder<?>) builder
|
||||
(TableUpdateBuilder<?>) builder,
|
||||
session
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1197,14 +1212,18 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
}
|
||||
|
||||
private void applyAttributeUpdateDetails(
|
||||
Object entity,
|
||||
MutationGroupBuilder updateGroupBuilder,
|
||||
DirtinessChecker dirtinessChecker,
|
||||
EntityVersionMapping versionMapping,
|
||||
int attributeIndex,
|
||||
AttributeMapping attributeMapping,
|
||||
TableUpdateBuilder<?> tableUpdateBuilder) {
|
||||
TableUpdateBuilder<?> tableUpdateBuilder,
|
||||
SharedSessionContractImplementor session) {
|
||||
final Generator generator = attributeMapping.getGenerator();
|
||||
if ( isValueGenerationInSql( generator, dialect() ) ) {
|
||||
if ( isValueGenerated( generator )
|
||||
&& ( session == null && generator.generatedOnExecution() || generator.generatedOnExecution( entity, session ) )
|
||||
&& isValueGenerationInSql( generator, dialect ) ) {
|
||||
handleValueGeneration( attributeMapping, updateGroupBuilder, (OnExecutionGenerator) generator );
|
||||
}
|
||||
else if ( versionMapping != null
|
||||
|
@ -1336,6 +1355,10 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
return !tablesNeedingDynamicUpdate.isEmpty();
|
||||
}
|
||||
|
||||
public TableSet getTablesNeedingDynamicUpdate() {
|
||||
return tablesNeedingDynamicUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback at start of processing an attribute
|
||||
*/
|
||||
|
@ -1395,6 +1418,11 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
tablesNeedingDynamicUpdate.add( table );
|
||||
}
|
||||
}
|
||||
|
||||
public void registerValueGeneratedInSqlNoWrite() {
|
||||
final IncludedAttributeAnalysis attributeAnalysis = (IncludedAttributeAnalysis) currentAttributeAnalysis;
|
||||
attributeAnalysis.setValueGeneratedInSqlNoWrite( true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1467,6 +1495,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
private final List<ColumnLockingAnalysis> columnLockingAnalyses;
|
||||
|
||||
private DirtynessStatus dirty = DirtynessStatus.NOT_DIRTY;
|
||||
private boolean valueGeneratedInSqlNoWrite;
|
||||
|
||||
public IncludedAttributeAnalysis(SingularAttributeMapping attribute) {
|
||||
this.attribute = attribute;
|
||||
|
@ -1506,6 +1535,14 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isValueGeneratedInSqlNoWrite() {
|
||||
return valueGeneratedInSqlNoWrite;
|
||||
}
|
||||
|
||||
public void setValueGeneratedInSqlNoWrite(boolean valueGeneratedInSqlNoWrite) {
|
||||
this.valueGeneratedInSqlNoWrite = valueGeneratedInSqlNoWrite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
|
@ -1563,7 +1600,8 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
(index,attribute) -> isValueGenerationInSql( attribute.getGenerator(), dialect() )
|
||||
null,
|
||||
(index,attribute) -> isValueGenerated( attribute.getGenerator() ) && isValueGenerationInSql( attribute.getGenerator(), dialect() )
|
||||
|| entityPersister().getPropertyUpdateability()[index],
|
||||
(index,attribute) -> {
|
||||
switch ( entityPersister().optimisticLockStyle() ) {
|
||||
|
@ -1591,6 +1629,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
|
||||
// next, iterate each attribute and build the SET and WHERE clauses
|
||||
applyTableUpdateDetails(
|
||||
null,
|
||||
// row-id
|
||||
"", // pass anything here to generate the row id restriction if possible
|
||||
// the "collector"
|
||||
|
|
|
@ -333,6 +333,9 @@ public class EntityMetamodel implements Serializable {
|
|||
if ( generator.generatesOnInsert() ) {
|
||||
if ( generator.generatedOnExecution() ) {
|
||||
foundPostInsertGeneratedValues = true;
|
||||
if ( generator instanceof BeforeExecutionGenerator ) {
|
||||
foundPreInsertGeneratedValues = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
foundPreInsertGeneratedValues = true;
|
||||
|
@ -341,6 +344,9 @@ public class EntityMetamodel implements Serializable {
|
|||
if ( generator.generatesOnUpdate() ) {
|
||||
if ( generator.generatedOnExecution() ) {
|
||||
foundPostUpdateGeneratedValues = true;
|
||||
if ( generator instanceof BeforeExecutionGenerator ) {
|
||||
foundPreUpdateGeneratedValues = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
foundPreUpdateGeneratedValues = true;
|
||||
|
|
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* 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.idgen.userdefined;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.hibernate.annotations.SourceType;
|
||||
import org.hibernate.annotations.ValueGenerationType;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.dialect.SQLServerDialect;
|
||||
import org.hibernate.dialect.SybaseASEDialect;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.generator.BeforeExecutionGenerator;
|
||||
import org.hibernate.generator.EventType;
|
||||
import org.hibernate.generator.EventTypeSets;
|
||||
import org.hibernate.generator.OnExecutionGenerator;
|
||||
import org.hibernate.id.IdentityGenerator;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.Jira;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
@DomainModel( annotatedClasses = {
|
||||
MixedTimingGeneratorsTest.AssignedEntity.class,
|
||||
MixedTimingGeneratorsTest.RandomEntity.class,
|
||||
MixedTimingGeneratorsTest.StringGeneratedEntity.class,
|
||||
} )
|
||||
@SessionFactory
|
||||
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsIdentityColumns.class )
|
||||
@Jira( "https://hibernate.atlassian.net/browse/HHH-17322" )
|
||||
public class MixedTimingGeneratorsTest {
|
||||
@Test
|
||||
@SkipForDialect( dialectClass = SQLServerDialect.class, reason = "SQLServer does not support setting explicit values for identity columns" )
|
||||
@SkipForDialect( dialectClass = OracleDialect.class, reason = "Oracle does not support setting explicit values for identity columns" )
|
||||
@SkipForDialect( dialectClass = SybaseASEDialect.class, reason = "Sybase does not support setting explicit values for identity columns" )
|
||||
public void testIdentityOrAssignedId(SessionFactoryScope scope) {
|
||||
// on execution generation
|
||||
scope.inTransaction( session -> session.persist( new AssignedEntity( "identity" ) ) );
|
||||
scope.inSession( session -> assertThat( session.createQuery(
|
||||
"from AssignedEntity where name = :name",
|
||||
AssignedEntity.class
|
||||
).setParameter( "name", "identity" ).getSingleResult().getId() ).isNotEqualTo( 42L ) );
|
||||
// before execution generation
|
||||
scope.inTransaction( session -> session.persist( new AssignedEntity( 42L, "assigned" ) ) );
|
||||
scope.inSession( session -> assertThat( session.createQuery(
|
||||
"from AssignedEntity where name = :name",
|
||||
AssignedEntity.class
|
||||
).setParameter( "name", "assigned" ).getSingleResult().getId() ).isEqualTo( 42L ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect( dialectClass = SQLServerDialect.class, reason = "SQLServer does not support setting explicit values for identity columns" )
|
||||
@SkipForDialect( dialectClass = OracleDialect.class, reason = "Oracle does not support setting explicit values for identity columns" )
|
||||
@SkipForDialect( dialectClass = SybaseASEDialect.class, reason = "Sybase does not support setting explicit values for identity columns" )
|
||||
public void testIdentityOrAssignedIdStateless(SessionFactoryScope scope) {
|
||||
// on execution generation
|
||||
scope.inStatelessTransaction( session -> session.insert( new AssignedEntity( "stateless_identity" ) ) );
|
||||
scope.inStatelessSession( session -> assertThat( session.createQuery(
|
||||
"from AssignedEntity where name = :name",
|
||||
AssignedEntity.class
|
||||
).setParameter( "name", "stateless_identity" ).getSingleResult().getId() ).isNotEqualTo( 23L ) );
|
||||
// before execution generation
|
||||
scope.inStatelessTransaction( session -> session.insert( new AssignedEntity( 23L, "stateless_assigned" ) ) );
|
||||
scope.inStatelessSession( session -> assertThat( session.createQuery(
|
||||
"from AssignedEntity where name = :name",
|
||||
AssignedEntity.class
|
||||
).setParameter( "name", "stateless_assigned" ).getSingleResult().getId() ).isEqualTo( 23L ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect( dialectClass = SQLServerDialect.class, reason = "SQLServer does not support setting explicit values for identity columns" )
|
||||
@SkipForDialect( dialectClass = OracleDialect.class, reason = "Oracle does not support setting explicit values for identity columns" )
|
||||
@SkipForDialect( dialectClass = SybaseASEDialect.class, reason = "Sybase does not support setting explicit values for identity columns" )
|
||||
public void testIdentityOrRandomId(SessionFactoryScope scope) {
|
||||
// on execution generation
|
||||
scope.inTransaction( session -> session.persist( new RandomEntity( "identity" ) ) );
|
||||
scope.inSession( session -> assertThat( session.createQuery(
|
||||
"from RandomEntity where name = :name",
|
||||
RandomEntity.class
|
||||
).setParameter( "name", "identity" ).getSingleResult().getId() ).isLessThan( 100L ) );
|
||||
// before execution generation
|
||||
scope.inTransaction( session -> session.persist( new RandomEntity( "random" ) ) );
|
||||
scope.inSession( session -> assertThat( session.createQuery(
|
||||
"from RandomEntity where name = :name",
|
||||
RandomEntity.class
|
||||
).setParameter( "name", "random" ).getSingleResult().getId() ).isGreaterThanOrEqualTo( 100L ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeneratedPropInsert(SessionFactoryScope scope) {
|
||||
// on execution generation
|
||||
scope.inTransaction( session -> session.persist( new StringGeneratedEntity( 1L, "literal" ) ) );
|
||||
scope.inSession( session -> assertThat(
|
||||
session.find( StringGeneratedEntity.class, 1L ).getGeneratedProp()
|
||||
).startsWith( "literal" ) );
|
||||
// before execution generation
|
||||
scope.inTransaction( session -> session.persist( new StringGeneratedEntity( 2L, "generated" ) ) );
|
||||
scope.inSession( session -> assertThat(
|
||||
session.find( StringGeneratedEntity.class, 2L ).getGeneratedProp()
|
||||
).startsWith( "generated" ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeneratedPropUpdate(SessionFactoryScope scope) {
|
||||
// on execution generation
|
||||
final int literalCount = scope.fromTransaction( session -> {
|
||||
final StringGeneratedEntity entity = new StringGeneratedEntity( 3L, "literal_inserted" );
|
||||
session.persist( entity );
|
||||
session.flush();
|
||||
assertThat( entity.getGeneratedProp() ).startsWith( "literal" );
|
||||
entity.setName( "literal_updated" );
|
||||
return Integer.parseInt( entity.getGeneratedProp().split( "_" )[1] );
|
||||
} );
|
||||
scope.inSession( session -> {
|
||||
final StringGeneratedEntity entity = session.find( StringGeneratedEntity.class, 3L );
|
||||
final String generatedProp = entity.getGeneratedProp();
|
||||
assertThat( generatedProp ).startsWith( "literal" );
|
||||
assertThat( Integer.parseInt( generatedProp.split( "_" )[1] ) ).isGreaterThan( literalCount );
|
||||
} );
|
||||
// before execution generation
|
||||
final int generatedCount = scope.fromTransaction( session -> {
|
||||
final StringGeneratedEntity entity = new StringGeneratedEntity( 4L, "generated_inserted" );
|
||||
session.persist( entity );
|
||||
session.flush();
|
||||
assertThat( entity.getGeneratedProp() ).startsWith( "generated" );
|
||||
entity.setName( "generated_updated" );
|
||||
return Integer.parseInt( entity.getGeneratedProp().split( "_" )[1] );
|
||||
} );
|
||||
scope.inSession( session -> {
|
||||
final StringGeneratedEntity entity = session.find( StringGeneratedEntity.class, 4L );
|
||||
final String generatedProp = entity.getGeneratedProp();
|
||||
assertThat( generatedProp ).startsWith( "generated" );
|
||||
assertThat( Integer.parseInt( generatedProp.split( "_" )[1] ) ).isGreaterThan( generatedCount );
|
||||
} );
|
||||
}
|
||||
|
||||
@Entity( name = "AssignedEntity" )
|
||||
public static class AssignedEntity {
|
||||
@Id
|
||||
@GeneratedValue( generator = "identity_or_assigned" )
|
||||
@GenericGenerator( name = "identity_or_assigned", type = IdentityOrAssignedGenerator.class )
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
public AssignedEntity() {
|
||||
}
|
||||
|
||||
public AssignedEntity(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public AssignedEntity(Long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "RandomEntity" )
|
||||
public static class RandomEntity {
|
||||
@Id
|
||||
@GeneratedValue( generator = "identity_or_random" )
|
||||
@GenericGenerator( name = "identity_or_random", type = IdentityOrRandomGenerator.class )
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
public RandomEntity() {
|
||||
}
|
||||
|
||||
public RandomEntity(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@ValueGenerationType( generatedBy = LiteralOrGeneratedStringGenerator.class )
|
||||
@Retention( RUNTIME )
|
||||
@Target( { FIELD, METHOD } )
|
||||
public @interface GeneratedString {
|
||||
/**
|
||||
* Specifies how the timestamp is generated. By default, it is generated
|
||||
* in memory, which saves a round trip to the database.
|
||||
*/
|
||||
SourceType source() default SourceType.VM;
|
||||
}
|
||||
|
||||
|
||||
@Entity( name = "StringGeneratedEntity" )
|
||||
public static class StringGeneratedEntity {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@GeneratedString
|
||||
private String generatedProp;
|
||||
|
||||
public StringGeneratedEntity() {
|
||||
}
|
||||
|
||||
public StringGeneratedEntity(Long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getGeneratedProp() {
|
||||
return generatedProp;
|
||||
}
|
||||
}
|
||||
|
||||
public static class IdentityOrAssignedGenerator extends IdentityGenerator implements BeforeExecutionGenerator {
|
||||
@Override
|
||||
public Object generate(
|
||||
SharedSessionContractImplementor session,
|
||||
Object owner,
|
||||
Object currentValue,
|
||||
EventType eventType) {
|
||||
final EntityPersister entityPersister = session.getEntityPersister( null, owner );
|
||||
return entityPersister.getIdentifier( owner, session );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean generatedOnExecution() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean generatedOnExecution(Object owner, SharedSessionContractImplementor session) {
|
||||
return generate( session, owner, null, null ) == null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class IdentityOrRandomGenerator extends IdentityGenerator implements BeforeExecutionGenerator {
|
||||
@Override
|
||||
public Object generate(
|
||||
SharedSessionContractImplementor session,
|
||||
Object owner,
|
||||
Object currentValue,
|
||||
EventType eventType) {
|
||||
return ThreadLocalRandom.current().nextLong( 100, 1_000 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean generatedOnExecution() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean generatedOnExecution(Object owner, SharedSessionContractImplementor session) {
|
||||
return !( (RandomEntity) owner ).getName().contains( "random" );
|
||||
}
|
||||
}
|
||||
|
||||
public static class LiteralOrGeneratedStringGenerator implements OnExecutionGenerator, BeforeExecutionGenerator {
|
||||
private int count;
|
||||
|
||||
public LiteralOrGeneratedStringGenerator() {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object generate(
|
||||
SharedSessionContractImplementor session,
|
||||
Object owner,
|
||||
Object currentValue,
|
||||
EventType eventType) {
|
||||
return "generated_" + count++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean generatedOnExecution() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean generatedOnExecution(Object owner, SharedSessionContractImplementor session) {
|
||||
return !( (StringGeneratedEntity) owner ).getName().contains( "generated" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<EventType> getEventTypes() {
|
||||
return EventTypeSets.ALL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean referenceColumnsInSql(Dialect dialect) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writePropertyValue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getReferencedColumnValues(Dialect dialect) {
|
||||
return new String[] { "'literal_" + count++ + "'" };
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue