TL;DR: I'm using hibernate 6.2.0.Final. When I ...
... then the version of the versioned entity is not actually incremented. The necessary update statement is created and added to a batch just before committing, but it is not actually sent to the DB and remains in the batch after committing, which Hibernate then warns about in an INFO log message.
Example:
I have an entity Calendar
which is versioned with a simple Integer version
:
@Entity
@Table(name = "calendar")
public class Calendar implements Serializable {
@Id
@GeneratedValue
@Column(name = "id")
private UUID id;
@NotNull
@Size(min = 3, max = 3)
@Column(name = "system_code", length = 3, nullable = false)
private String systemCode;
@NotNull
@Column(name = "name", nullable = false)
private String name;
@Version
@NotNull
@Column(name = "version", nullable = false)
private Integer version = 1;
When I change one of the attributes, the version gets automatically updated, as I would expect.
When I fetch the entity with a lock type OPTIMISTIC_FORCE_INCREMENT
, I also expect the entity to get an incremented version, even if I didn't change the entity:
@Repository
public interface CalendarRepository extends JpaRepository<Calendar, UUID> {
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
@Query("SELECT c FROM Calendar c " +
"WHERE c.systemCode = :systemCode " +
" AND c.name = :name")
Optional<Calendar> findBySystemCodeAndNameWithOptimisticForceIncrement(
@Param("systemCode") String systemCode,
@Param("name") String name);
}
And indeed, that is exactly what happens.
But, if I now turn on batching...
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 20
order_updates: true
order_inserts: true
Then something weird happens. While logging indicates that it is still attempting to increment the version, the increment doesn't actually happen. (I may have a bit too much trace logging on, but when you're digging...)
You'll notice that AFTER the committing
messsage, a new batch is still being created to do the update for the calendar entity. However, the batch is not actually executed, as shown by the informational warning HHH100503: On release of batch it still contained JDBC statements
.
2023-07-12T14:29:34+02:00 [main] DEBUG o.h.e.t.internal.TransactionImpl - committing
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.t.b.j.i.JdbcResourceLocalTransactionCoordinatorImpl - ResourceLocalTransactionCoordinatorImpl#beforeCompletionCallback
2023-07-12T14:29:34+02:00 [main] TRACE o.h.e.jdbc.internal.JdbcCoordinatorImpl - Starting after statement execution processing [ON_CLOSE]
2023-07-12T14:29:34+02:00 [main] TRACE org.hibernate.orm.jdbc.batch - Created Batch (20) - `com.***.domain.Calendar#UPDATE`
2023-07-12T14:29:34+02:00 [main] TRACE org.hibernate.orm.jdbc.batch - Adding to JDBC batch (1) - `com.***.domain.Calendar#UPDATE`
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.ResourceRegistryStandardImpl - Registering statement [HikariProxyPreparedStatement@2037475545 wrapping update ibl_***.calendar set version=? where id=? and version=?]
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.ResourceRegistryStandardImpl - Registering result set [null]
2023-07-12T14:29:34+02:00 [main] DEBUG org.hibernate.SQL - update ibl_***.calendar set version=? where id=? and version=?
2023-07-12T14:29:34+02:00 [main] TRACE org.hibernate.orm.jdbc.bind - binding parameter [1] as [INTEGER] - [2]
2023-07-12T14:29:34+02:00 [main] TRACE org.hibernate.orm.jdbc.bind - binding parameter [2] as [UUID] - [cf2027c2-1212-4d14-88d4-00c6de0500d8]
2023-07-12T14:29:34+02:00 [main] TRACE org.hibernate.orm.jdbc.bind - binding parameter [3] as [INTEGER] - [1]
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.AbstractLogicalConnectionImplementor - LogicalConnection#beforeTransactionCompletion
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.t.i.SynchronizationRegistryStandardImpl - SynchronizationRegistryStandardImpl.notifySynchronizationsBeforeTransactionCompletion
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.AbstractLogicalConnectionImplementor - Preparing to commit transaction via JDBC Connection.commit()
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.AbstractLogicalConnectionImplementor - Transaction committed via JDBC Connection.commit()
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.AbstractLogicalConnectionImplementor - LogicalConnection#afterTransaction
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.ResourceRegistryStandardImpl - Releasing JDBC resources
2023-07-12T14:29:34+02:00 [main] INFO org.hibernate.orm.jdbc.batch - HHH100503: On release of batch it still contained JDBC statements
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.ResourceRegistryStandardImpl - Releasing statement [HikariProxyPreparedStatement@2037475545 wrapping update ibl_***a.calendar set version=2 where id='cf2027c2-1212-4d14-88d4-00c6de0500d8'::uuid and version=1]
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.ResourceRegistryStandardImpl - Closing result set [null]
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.ResourceRegistryStandardImpl - Closing prepared statement [HikariProxyPreparedStatement@2037475545 wrapping update ibl_***.calendar set version=2 where id='cf2027c2-1212-4d14-88d4-00c6de0500d8'::uuid and version=1]
2023-07-12T14:29:34+02:00 [main] TRACE o.h.e.jdbc.internal.JdbcCoordinatorImpl - Starting after statement execution processing [ON_CLOSE]
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.t.b.j.i.JdbcResourceLocalTransactionCoordinatorImpl - ResourceLocalTransactionCoordinatorImpl#afterCompletionCallback(true)
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.t.i.SynchronizationRegistryStandardImpl - SynchronizationRegistryStandardImpl.notifySynchronizationsAfterTransactionCompletion(3)
2023-07-12T14:29:34+02:00 [main] TRACE o.h.e.jdbc.internal.JdbcCoordinatorImpl - Closing JDBC container [org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl@1561e62e]
2023-07-12T14:29:34+02:00 [main] DEBUG o.h.e.jdbc.internal.JdbcCoordinatorImpl - HHH000420: Closing un-released batch
2023-07-12T14:29:34+02:00 [main] DEBUG org.hibernate.orm.jdbc.batch - PreparedStatementDetails did not contain PreparedStatement on #releaseStatements : update ibl_***.calendar set version=? where id=? and version=?
2023-07-12T14:29:34+02:00 [main] TRACE o.h.e.jdbc.internal.JdbcCoordinatorImpl - Starting after statement execution processing [ON_CLOSE]
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.ResourceRegistryStandardImpl - Releasing JDBC resources
2023-07-12T14:29:34+02:00 [main] DEBUG org.hibernate.orm.jdbc.batch - PreparedStatementDetails did not contain PreparedStatement on #releaseStatements : update ibl_***.calendar set version=? where id=? and version=?
2023-07-12T14:29:34+02:00 [main] TRACE o.h.e.jdbc.internal.JdbcCoordinatorImpl - Starting after statement execution processing [ON_CLOSE]
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.LogicalConnectionManagedImpl - Closing logical connection
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.ResourceRegistryStandardImpl - Releasing JDBC resources
2023-07-12T14:29:34+02:00 [main] DEBUG org.hibernate.orm.jdbc.batch - PreparedStatementDetails did not contain PreparedStatement on #releaseStatements : update ibl_***.calendar set version=? where id=? and version=?
2023-07-12T14:29:34+02:00 [main] TRACE o.h.e.jdbc.internal.JdbcCoordinatorImpl - Starting after statement execution processing [ON_CLOSE]
2023-07-12T14:29:34+02:00 [main] TRACE o.h.r.j.i.LogicalConnectionManagedImpl - Logical connection closed
By stepping through debugger sessions, I've reached the tentative conclusion that
JdbcResourceLocalTransactionCoordinatorImpl.commit
), before the actual commit, a beforeCompletionCallback
is called.beforeCompletionCallback
will go over things that need to be done in the beforeTransactionCompletion
method of the BeforeTransactionCompletionProcessQueue
class. It will correctly see that the version increment needs to be done and call doBeforeTransactionCompletion
of the EntityIncrementVersionProcess
class.doBeforeTransactionCompletion
method calls for a MutationExecutor
. If batching is explicitly set to 1, the returned executor is of the type MutationExecutorSingleNonBatched
. But if batching is set to > 1, the returned executor is of the type MutationExecutorSingleBatched
instead.execute
method of the executor is called, in the case of the MutationExecutorSingleNonBatched
, it seems to actually execute the statement. However in the case of the MutationExecutorSingleBatched
, it only adds the statement to the batch, but it doesn't get executed before the actual commit is sent.Testing method:
@Test
@Order(1)
void findBySystemCodeAndNameWithOptimisticForceIncrement() {
// Prepare the environment
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
calendarRepository.save(new Calendar()
.name("Calendar for AAA")
.systemCode("AAA"));
}
});
// Call the tested method in a transaction
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status){
calendarRepository
.findBySystemCodeAndNameWithOptimisticForceIncrement("AAA", "Calendar for AAA")
.orElseThrow()
}
});
// Verify the result
Calendar savedCalendar = transactionTemplate.execute(status -> calendarRepository
.findBySystemCodeAndName("AAA", "Calendar for AAA")
.orElseThrow()
);
assertThat(savedCalendar.getVersion()).isEqualTo(2);
}
Did anyone else run into this strange behaviour when combining versioning with batching in Hibernate? What would be a way to resolve this without resorting to disabling batching as a whole?
I have been able to reproduce what you are seeing. I found this question while trying to figure out what was causing this message in my Spring Boot 3.1.1 project.
HHH100503: On release of batch it still contained JDBC statements
My project uses pessimistic force increment and I was noticing that some but not all of the rows, were missing their @Version column updates.
From the test case I am working on, there is nothing obviously wrong with the @Version column itself. It works in 5.6.17 and 6.1.7 but 6.2.0 and above fails.
I have linked this question to Hibernate ORM issue https://hibernate.atlassian.net/browse/HHH-16939, which will include the above test case PR once it is finished.