int count=0;
@SneakyThrows
@Override
public MyEntity update(@Valid MyEntity entity) {
MyEntity persisted = this.repository.save(entity);
if (count == 0) {
count++;
throw new SQLTransientConnectionException();
}
count = 0;
return persisted;
}
I have Spring retry configured with a policy that retries when SQLTransientConnectionException is encountered.
Observation in Hibernate 6.5.0
The entity does not get updated in the database in the first attempt even with exception thrown and in retry, the entry gets updated properly and response returned.
Observation in Hibernate 6.6
The entity gets updated in the database in the first attempt and in retry, the entity throws a version mismatch error.
int count=0;
@SneakyThrows
public MyEntity getOrCreateMyEntity(
MyEntity myEntity) {
count = 0;
MyEntity existingData = findById(myEntity.getId());
if (Objects.isNull(existingData)) {
MyEntity persisted = create(myEntity);
if (count == 0) {
count++;
throw new SQLTransientConnectionException();
}
return persisted;
}
return existingData;
}
Observation in Hibernate 6.5.0
The entity does not get created in the first attempt and in retry, the entry gets created properly and response returned.
Observation in Hibernate 6.6
The entity gets created in the database in the first attempt even with the exception thrown and in retry, the entity is returned as existingData.
If the entity class has e.g. a generated identifier or version, Hibernate might set these attributes while processing. You have to reset that state if you want to continue working with these objects, or better yet, recreate the objects.
Yes the entity has both a generated identifier and a version. There are a few other impacted entities which have either a generated identifier or a version. For information, it is not possible to remove these attributes.
are there any references of ‘Reset’ or ‘Recreate’ as mentioned? I am not catching the exception in the service method here because that would not trigger my spring retry logic.
did the behaviour change in Hibernate 6.6? I don’t see a change log related to this.
I don’t know how Spring works, so whatever you are testing here with throwing exceptions manually, might not be the same as when a real exception happens.
What I can tell you is that:
When an exception of any kind is encountered within Hibernate ORM code due to JDBC errors or other issues, the Session/EntityManager and all associated entities become “unusable”.
A compliant transaction manager should roll back a transaction and also rollback the associated Session/EntityManager when an exception bubbles through the transaction boundary.
When the entity is persisted even though an exception passes the transaction layer, then something must be fundamentally broken in your configuration.
are there any references of ‘Reset’ or ‘Recreate’ as mentioned? I am not catching the exception in the service method here because that would not trigger my spring retry logic.
There are none. Like I wrote before, entities that were associated with a Session/EntityManager that was rolled back due to an exception can’t not be reliably used anymore. The reason for that is, generators might have already set state (id and version values) on the objects, which influences how the “state” of these objects. When merging an entity object with an id and version value set that has a generated id, the object is assumed to be “detached” i.e. the object is assumed to exist in the database already. If the lookup for that instance fails, the merge operation fails with an optimistic lock exception, because Hibernate ORM has to assume a concurrent transaction deleted the row in the meantime.
Previously, Hibernate ORM would just insert the row again, which is wrong.
If you want your retry logic to work, you will have to set the generated id field and ideally also the version field to null when you encounter an exception.
I figured out the issue. Will share for the benefits of others facing a similar issue.
I have been using Lombok’s SneakyThrows annotation which would wrap any exception to an unchecked exception making the transaction a candidate for rollback. With new upgrade, this behaviour of SneakyThrows has changed which does not alter the nature of the exception it throws silently.