Hi! After migration to spring-boot 3.4.0 and hibernate 6.6.2.Final we faced with error
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect):[SolvencyEntity#186500]
I have two entities with one-to-one relationship. In the child entity I have a field that is reference to parent entity
Please share the full entity mappings so we can have a better understanding of what is happening (e.g. are the entities versioned?). Also, we don’t know what the getCurrentParty() method does, and what state the PartyEntity returned by it is in - the error message is pretty self-explanatory, might be that one of your entities is transient, thus triggering the exception during merge (which is what Spring does when calling save on a repository AFAIK).
Thanks for reply!
Only PartyEntity is versioned and getCurrentParty returns entity that was saved in previous cucumber step and solvencyEntity is just created and wanted to be saved.
solvencyEntity is transient but it is annotated as cascade = CascadeType.ALL in PartyEntity so why error is thrown?
Once again, with this little context and without seeing the full mappings and logic of your application it’s very hard to give you an answer. Please try reproducing this error using Hibernate only, you can start from our test case templates, and if you think this is a bug please open a new report in our issue tracker.
@Entity
data class Uploader (
@Id
@UuidGenerator
val uuid: String = "",
@field:Email
val emailAddress: String
)
and a service:
@Service
class UploadService(private val uploaderRepository: UploaderRepository) {
fun getOrCreateUploader(emailAddress: String): Uploader =
uploaderRepository.findByEmailAddress(emailAddress)
?: uploaderRepository.save(Uploader(emailAddress = emailAddress))
}
But when getOrCreateUploader is executed with an email that is not already associated with an Uploader, then uploaderRepository.save(Uploader(emailAddress = emailAddress)) throws this exception:
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bright.externaluploader.model.Uploader#]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:325) ~[spring-orm-6.2.0.jar:6.2.0]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) ~[spring-orm-6.2.0.jar:6.2.0]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:560) ~[spring-orm-6.2.0.jar:6.2.0]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:343) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:160) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.0.jar:6.2.0]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:165) ~[spring-data-jpa-3.4.0.jar:3.4.0]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.0.jar:6.2.0]
at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:96) ~[spring-data-commons-3.4.0.jar:3.4.0]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.0.jar:6.2.0]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) ~[spring-aop-6.2.0.jar:6.2.0]
at jdk.proxy2/jdk.proxy2.$Proxy150.save(Unknown Source) ~[na:na]
at [...]UploadService.getOrCreateUploader(UploadService.kt:64) ~[main/:na]
I’ve read the migration guide for Hibernate 6.6 which mentions this:
Previously, merging a detached entity resulted in a SQL insert whenever there was no matching row in the database (for example, if the object had been deleted in another transaction). This behavior was unexpected and violated the rules of optimistic locking.
An OptimisticLockException is now thrown when it is possible to determine that an entity is definitely detached, but there is no matching row. For this determination to be possible, the entity must have either:
a generated @Id field, or
a non-primitive @Version field.
For entities which have neither, it’s impossible to distinguish a new instance from a deleted detached instance, and there is no change from the previous behavior.
But I don’t really understand why it’s throwing the exception for a new instance of the entity. How else are we supposed to insert new values in the database?
I don’t know how Kotlin works, but Hibernate ORM will instantiate a default object instance to determine the “default” value. This is used to differentiate a “new” object from a “detached” during EntityManager#merge.
It was just a guess that this could be the reason for Hibernate ORM to assume the object passed as uploaderRepository.save(Uploader(emailAddress = emailAddress)) is detached. Not sure if it’s a bug or an unfortunate side effect of Kotlin, but at least assigning an empty string is fishy.
We are facing the same error after spring update from 3.1.1 → 3.4.1:
ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
We are removing all rows from repository using repo.deleteAll() and then saving new data with repo.saveAll(data). Some of the data might be similar to what was previously in DB (as we use it only in a method that copies data from prod to local db. We are okay with removing everything from DB before inserting new records).
Before the update it would work just fine but after the update the ObjectOptimisticLockingFailureException is thrown.
We do not use @Version field
Tried to use repo.deleteAllInBatch() method or to write method in repo with @Query to delete everything but still facing the error.
Please advise how to resolve it.
EDIT:
We save new entities with already defined ids. Removing ids from entities before saving them removes the exception. Is there an option to save new entities with already predefined ids and not get this exception?
Thank you your reply!
We wouldn’t want to change the strategy to generate ids (currently we are using @GeneratedValue(strategy = GenerationType.IDENTITY))
As mentioned, we use this only to align local DB with prod, so its easier for us just to remove everything from localDB and save again (as local and prod ids might not match to match the entities). This is not used in prod environment.
I would like to confirm that error comes from the fact that we are trying to save entities with ids to an empty table. Is that causing the error? Or is it something else?
We wouldn’t want to change the strategy to generate ids (currently we are using @GeneratedValue(strategy = GenerationType.IDENTITY) )
Well, then what happens when you simply insert the prod data to your local database? The next insert to the local database will fail, because the internal sequence used for the identity generator within the database still is set to the initial value. This is exactly the reason why it’s dangerous to do this sort of thing.
So you have to reset the internal sequence of the identity generator on your database to the max value + 1 to not run into this problem.
It would be easiest if you just use native SQL for transferring data or maybe even some sort of replication or backup/restore tool.
I would like to confirm that error comes from the fact that we are trying to save entities with ids to an empty table. Is that causing the error? Or is it something else?
The basic reason is that you can’t assign a value to a column that is marked as generated, to avoid allowing insertions that can lead to unique constraint violations later.
@Monika_Matak_Plantic you should not call setId since the entity has an identity-generated identifier @GeneratedValue(strategy = GenerationType.IDENTITY) .
Hi, we do have a similar issue after migrating to SpringBoot 3.4, and the exception org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [my.entity.MyDataEntity#MY_KEY] on new entity creation. We do set id manually but it is not generated value: