Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Hibernate StaleObjectStateException due to new HashSet?

My code was working perfectly fine for a long period, but after a few refactors I noticed I suddenly couldn't save a Group object anymore.

I was getting the dreaded Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) error. After Googling of course I found this StackOverflow question, but it didn't help me at all as I wasn't doing anything concurrently.

After going through my refactors, the only difference I found is this change.

Before it was:

    final Collection<Sample> allByBarcode = sampleService.byBarcode(groupRequest.getSamples(), currentUser);

    if (!allByBarcode.isEmpty()) {
        Group group = new Group();

        group.setName(groupRequest.getName());
        group.setSamples(allByBarcode);
        group.setType(groupRequest.getType());
        group.setOwner(currentUser);

        group = repository.save(group);

        return Optional.ofNullable(group);
    }

after refactoring (don't remember exactly why) it became:

    final Collection<Sample> allByBarcode = sampleService.byBarcode(groupRequest.getSamples(), currentUser);

    if (!allByBarcode.isEmpty()) {
        Group group = new Group();

        group.setName(groupRequest.getName());
        group.setSamples(new HashSet<>(allByBarcode));
        group.setType(groupRequest.getType());
        group.setOwner(currentUser);

        group = repository.save(group);

        return Optional.ofNullable(group);
    }

After changing it back to the original, It suddenly started working again every time, with no errors whatsoever.

Could anyone please explain what's the reason to this error as this is literally the only difference in the code that made it work again?

Update 1:

I tried another variant:

Group group = new Group();

group.setName(groupRequest.getName());
group.setSamples(new ArrayList<>(allByBarcode));
group.setType(groupRequest.getType());
group.setOwner(currentUser);

group = repository.save(group);

Note ArrayList instead of HashSet – for some reason this code works.

I've also tried LinkedList which also works.

Any ideas?

Update 2:

The stack trace is as follows: I have cut it to remove lots of Spring and Tomcat-related trace.

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.ObjectOptimisticLockingFailureException: Object of class [uk.ac.sanger.mig.aker.domain.Group] with identifier [93]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [uk.ac.sanger.mig.aker.domain.Group#93]] with root cause

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [uk.ac.sanger.mig.aker.domain.Group#93]
        at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2541)
        at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3285)
        at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3183)
        at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3525)
        at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:159)
        at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:465)
        at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
        at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
        at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
        at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
        at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
        at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
        at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
        at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:77)
        at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:521)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
        at com.sun.proxy.$Proxy116.createGroup(Unknown Source)
        at uk.ac.sanger.mig.aker.controllers.GroupController.store(GroupController.java:107)
like image 764
Crembo Avatar asked Apr 16 '15 11:04

Crembo


2 Answers

In your new code, you are creating a new HashSet that will contain all the Sample elements in allByBarcode collection.

And I think the problem is raised here because those elements are not persisted by the session because they are new objects added to the new HashSet() instance and they're not really saved by the session, it explains the Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) Exception however the allByBarcode elements are taken from your service so they are persisted and recognized by the session.

And I don't see what's the need of setting a new HashSet instance where the elements should be saved(persisted) by the session (which will throw an Exception because you are trying to save elements with existing identifier) while it's more efficient and more safe to set the already saved elements, so better use:

   group.setSamples(allByBarcode);
like image 154
cнŝdk Avatar answered Nov 15 '22 10:11

cнŝdk


I think the problem is that you have some duplicates objects in your collection allByBarcode that disappear when you wrap your collection in a HashSet which doesn't allow duplicates. What happens after that is : your repository is trying to save the same object more than once.

like image 1
Mostafa EL BARRAK Avatar answered Nov 15 '22 11:11

Mostafa EL BARRAK