HHH-16939 Optimistic and Pessimistic Force Increment Update Statements are not committed when using a batch

This commit is contained in:
Andrea Boriero 2023-07-24 22:01:51 +02:00 committed by Andrea Boriero
parent 6cc1f1f9e6
commit c0189c0bd4
8 changed files with 113 additions and 47 deletions

View File

@ -48,7 +48,7 @@ public class PessimisticForceIncrementLockingStrategy implements LockingStrategy
} }
final EntityEntry entry = session.getPersistenceContextInternal().getEntry( object ); final EntityEntry entry = session.getPersistenceContextInternal().getEntry( object );
final EntityPersister persister = entry.getPersister(); final EntityPersister persister = entry.getPersister();
final Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), session ); final Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), false, session );
entry.forceLocked( object, nextVersion ); entry.forceLocked( object, nextVersion );
} }

View File

@ -546,6 +546,8 @@ public class ActionQueue {
// Execute completion actions only in transaction owner (aka parent session). // Execute completion actions only in transaction owner (aka parent session).
if ( beforeTransactionProcesses != null ) { if ( beforeTransactionProcesses != null ) {
beforeTransactionProcesses.beforeTransactionCompletion(); beforeTransactionProcesses.beforeTransactionCompletion();
// `beforeTransactionCompletion()` can have added batch operations (e.g. to increment entity version)
session.getJdbcCoordinator().executeBatch();
} }
} }
} }

View File

@ -8,7 +8,6 @@ package org.hibernate.event.internal;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.LockMode;
import org.hibernate.action.internal.EntityIncrementVersionProcess; import org.hibernate.action.internal.EntityIncrementVersionProcess;
import org.hibernate.action.internal.EntityVerifyVersionProcess; import org.hibernate.action.internal.EntityVerifyVersionProcess;
import org.hibernate.classic.Lifecycle; import org.hibernate.classic.Lifecycle;
@ -18,7 +17,6 @@ import org.hibernate.event.spi.PostLoadEvent;
import org.hibernate.event.spi.PostLoadEventListener; import org.hibernate.event.spi.PostLoadEventListener;
import org.hibernate.jpa.event.spi.CallbackRegistry; import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
import org.hibernate.persister.entity.EntityPersister;
/** /**
* We do 2 things here:<ul> * We do 2 things here:<ul>
@ -49,24 +47,17 @@ public class DefaultPostLoadEventListener implements PostLoadEventListener, Call
throw new AssertionFailure( "possible non-threadsafe access to the session" ); throw new AssertionFailure( "possible non-threadsafe access to the session" );
} }
final LockMode lockMode = entry.getLockMode(); switch ( entry.getLockMode() ) {
switch (lockMode) {
case PESSIMISTIC_FORCE_INCREMENT: case PESSIMISTIC_FORCE_INCREMENT:
final EntityPersister persister = entry.getPersister(); final Object nextVersion = entry.getPersister()
final Object nextVersion = persister.forceVersionIncrement( .forceVersionIncrement( entry.getId(), entry.getVersion(), false, session );
entry.getId(),
entry.getVersion(),
session
);
entry.forceLocked( entity, nextVersion ); entry.forceLocked( entity, nextVersion );
break; break;
case OPTIMISTIC_FORCE_INCREMENT: case OPTIMISTIC_FORCE_INCREMENT:
final EntityIncrementVersionProcess incrementVersion = new EntityIncrementVersionProcess(entity); session.getActionQueue().registerProcess( new EntityIncrementVersionProcess( entity ) );
session.getActionQueue().registerProcess(incrementVersion);
break; break;
case OPTIMISTIC: case OPTIMISTIC:
final EntityVerifyVersionProcess verifyVersion = new EntityVerifyVersionProcess(entity); session.getActionQueue().registerProcess( new EntityVerifyVersionProcess( entity ) );
session.getActionQueue().registerProcess(verifyVersion);
break; break;
} }
@ -76,7 +67,6 @@ public class DefaultPostLoadEventListener implements PostLoadEventListener, Call
protected void invokeLoadLifecycle(PostLoadEvent event, EventSource session) { protected void invokeLoadLifecycle(PostLoadEvent event, EventSource session) {
if ( event.getPersister().implementsLifecycle() ) { if ( event.getPersister().implementsLifecycle() ) {
//log.debug( "calling onLoad()" );
( (Lifecycle) event.getEntity() ).onLoad( session, event.getId() ); ( (Lifecycle) event.getEntity() ).onLoad( session, event.getId() );
} }
} }

View File

@ -86,7 +86,7 @@ public class LoaderHelper {
if ( persister.isVersioned() && requestedLockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) { if ( persister.isVersioned() && requestedLockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) {
// todo : should we check the current isolation mode explicitly? // todo : should we check the current isolation mode explicitly?
Object nextVersion = persister.forceVersionIncrement( Object nextVersion = persister.forceVersionIncrement(
entry.getId(), entry.getVersion(), session entry.getId(), entry.getVersion(), false, session
); );
entry.forceLocked( object, nextVersion ); entry.forceLocked( object, nextVersion );
} }

View File

@ -79,6 +79,7 @@ import org.hibernate.engine.internal.StatefulPersistenceContext;
import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService; import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.profile.internal.FetchProfileAffectee;
import org.hibernate.engine.spi.CachedNaturalIdValueSource; import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CollectionKey; import org.hibernate.engine.spi.CollectionKey;
@ -1953,32 +1954,7 @@ public abstract class AbstractEntityPersister
return superMappingType.getEntityPersister().forceVersionIncrement( id, currentVersion, session ); return superMappingType.getEntityPersister().forceVersionIncrement( id, currentVersion, session );
} }
if ( !isVersioned() ) { final Object nextVersion = calculateNextVersion( id, currentVersion, session );
throw new AssertionFailure( "cannot force version increment on non-versioned entity" );
}
if ( isVersionGeneratedOnExecution() ) {
// the difficulty here is exactly what we update in order to
// force the version to be incremented in the db...
throw new HibernateException( "LockMode.FORCE is currently not supported for generated version properties" );
}
final EntityVersionMapping versionMapping = getVersionMapping();
final Object nextVersion = getVersionJavaType().next(
currentVersion,
versionMapping.getLength(),
versionMapping.getPrecision(),
versionMapping.getScale(),
session
);
if ( LOG.isTraceEnabled() ) {
LOG.trace(
"Forcing version increment [" + infoString( this, id, getFactory() ) + "; "
+ getVersionType().toLoggableString( currentVersion, getFactory() ) + " -> "
+ getVersionType().toLoggableString( nextVersion, getFactory() ) + "]"
);
}
updateCoordinator.forceVersionIncrement( id, currentVersion, nextVersion, session ); updateCoordinator.forceVersionIncrement( id, currentVersion, nextVersion, session );
@ -2016,6 +1992,52 @@ public abstract class AbstractEntityPersister
return nextVersion; return nextVersion;
} }
@Override
public Object forceVersionIncrement(
Object id,
Object currentVersion,
boolean batching,
SharedSessionContractImplementor session) throws HibernateException {
if ( superMappingType != null ) {
return superMappingType.getEntityPersister().forceVersionIncrement( id, currentVersion, session );
}
final Object nextVersion = calculateNextVersion( id, currentVersion, session );
updateCoordinator.forceVersionIncrement( id, currentVersion, nextVersion, batching, session );
return nextVersion;
}
private Object calculateNextVersion(Object id, Object currentVersion, SharedSessionContractImplementor session) {
if ( !isVersioned() ) {
throw new AssertionFailure( "cannot force version increment on non-versioned entity" );
}
if ( isVersionGeneratedOnExecution() ) {
// the difficulty here is exactly what we update in order to
// force the version to be incremented in the db...
throw new HibernateException( "LockMode.FORCE is currently not supported for generated version properties" );
}
final EntityVersionMapping versionMapping = getVersionMapping();
final Object nextVersion = getVersionJavaType().next(
currentVersion,
versionMapping.getLength(),
versionMapping.getPrecision(),
versionMapping.getScale(),
session
);
if ( LOG.isTraceEnabled() ) {
LOG.trace(
"Forcing version increment [" + infoString( this, id, getFactory() ) + "; "
+ getVersionType().toLoggableString( currentVersion, getFactory() ) + " -> "
+ getVersionType().toLoggableString( nextVersion, getFactory() ) + "]"
);
}
return nextVersion;
}
// private String generateVersionIncrementUpdateString() { // private String generateVersionIncrementUpdateString() {
// final Update update = new Update( getFactory().getJdbcServices().getDialect() ).setTableName( getTableName( 0 ) ); // final Update update = new Update( getFactory().getJdbcServices().getDialect() ).setTableName( getTableName( 0 ) );
// if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) { // if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {

View File

@ -777,6 +777,14 @@ public interface EntityPersister extends EntityMappingType, RootTableGroupProduc
Object forceVersionIncrement(Object id, Object currentVersion, SharedSessionContractImplementor session) throws HibernateException; Object forceVersionIncrement(Object id, Object currentVersion, SharedSessionContractImplementor session) throws HibernateException;
default Object forceVersionIncrement(
Object id,
Object currentVersion,
boolean batching,
SharedSessionContractImplementor session) throws HibernateException {
return forceVersionIncrement( id, currentVersion, session );
}
/** /**
* Has the class actually been bytecode instrumented? * Has the class actually been bytecode instrumented?
*/ */

View File

@ -37,4 +37,13 @@ public interface UpdateCoordinator {
Object currentVersion, Object currentVersion,
Object nextVersion, Object nextVersion,
SharedSessionContractImplementor session); SharedSessionContractImplementor session);
default void forceVersionIncrement(
Object id,
Object currentVersion,
Object nextVersion,
boolean batching,
SharedSessionContractImplementor session){
forceVersionIncrement( id, currentVersion, nextVersion, session );
}
} }

View File

@ -152,6 +152,19 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
doVersionUpdate( null, id, nextVersion, currentVersion, session ); doVersionUpdate( null, id, nextVersion, currentVersion, session );
} }
@Override
public void forceVersionIncrement(
Object id,
Object currentVersion,
Object nextVersion,
boolean batching,
SharedSessionContractImplementor session) {
if ( versionUpdateGroup == null ) {
throw new HibernateException( "Cannot force version increment relative to sub-type; use the root type" );
}
doVersionUpdate( null, id, nextVersion, currentVersion, batching, session );
}
@Override @Override
public void coordinateUpdate( public void coordinateUpdate(
Object entity, Object entity,
@ -465,11 +478,21 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
Object version, Object version,
Object oldVersion, Object oldVersion,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
doVersionUpdate( entity, id, version, oldVersion, true, session );
}
protected void doVersionUpdate(
Object entity,
Object id,
Object version,
Object oldVersion,
boolean batching,
SharedSessionContractImplementor session) {
assert versionUpdateGroup != null; assert versionUpdateGroup != null;
final EntityTableMapping mutatingTableDetails = (EntityTableMapping) versionUpdateGroup.getSingleOperation().getTableDetails(); final EntityTableMapping mutatingTableDetails = (EntityTableMapping) versionUpdateGroup.getSingleOperation().getTableDetails();
final MutationExecutor mutationExecutor = updateVersionExecutor( session, versionUpdateGroup, false ); final MutationExecutor mutationExecutor = updateVersionExecutor( session, versionUpdateGroup, false, batching );
final EntityVersionMapping versionMapping = entityPersister().getVersionMapping(); final EntityVersionMapping versionMapping = entityPersister().getVersionMapping();
@ -998,6 +1021,18 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
.createExecutor( resolveUpdateVersionBatchKeyAccess( dynamicUpdate, session ), group, session ); .createExecutor( resolveUpdateVersionBatchKeyAccess( dynamicUpdate, session ), group, session );
} }
private MutationExecutor updateVersionExecutor(
SharedSessionContractImplementor session,
MutationOperationGroup group,
boolean dynamicUpdate,
boolean batching) {
if ( batching ) {
return updateVersionExecutor(session, group,dynamicUpdate);
}
return mutationExecutorService.createExecutor( NoBatchKeyAccess.INSTANCE, group, session );
}
protected BatchKeyAccess resolveUpdateVersionBatchKeyAccess(boolean dynamicUpdate, SharedSessionContractImplementor session) { protected BatchKeyAccess resolveUpdateVersionBatchKeyAccess(boolean dynamicUpdate, SharedSessionContractImplementor session) {
if ( !dynamicUpdate if ( !dynamicUpdate
&& session.getTransactionCoordinator() != null && session.getTransactionCoordinator() != null