Preventing the hibernate StaleObjectStateException from occurring

hibernate, jpa, spring, spring-data-jpa

Solution

I had a similar problem. This is the error that I got when running `mvn test`:

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

It was because, by mistake, I initialised the `version` field of the entity as `NULL`:

mysql> select * from lap;
+----+-------------+---------------------+------+------------------+---------+---------+
| id | code        | cumulative_distance | name | partial_distance | version | race_id |
+----+-------------+---------------------+------+------------------+---------+---------+
|  1 | AF0CUL00011 |                1000 | LAP1 |             1000 |    NULL |       1 |
|  2 | AQ000000012 |                2000 | LAP2 |             1000 |    NULL |       2 |
|  3 | AR000000013 |                3000 | LAP3 |             1000 |    NULL |       3 |
|  4 | AR0000N0114 |                4000 | LAP4 |             1000 |    NULL |       4 |
+----+-------------+---------------------+------+------------------+---------+---------+
4 rows in set (0.00 sec)

Once, I initialised the `version` field with 0, everything worked:

mysql> select * from lap;
+----+-------------+---------------------+------+------------------+---------+---------+
| id | code        | cumulative_distance | name | partial_distance | version | race_id |
+----+-------------+---------------------+------+------------------+---------+---------+
|  1 | AF0CUL00011 |                1000 | LAP1 |             1000 |       0 |       1 |
|  2 | AQ000000012 |                2000 | LAP2 |             1000 |       0 |       2 |
|  3 | AR000000013 |                3000 | LAP3 |             1000 |       0 |       3 |
|  4 | AR0000N0114 |                4000 | LAP4 |             1000 |       0 |       4 |
+----+-------------+---------------------+------+------------------+---------+---------+
4 rows in set (0.00 sec)

I hope it helps, Agustin

Problem

I have an issue with a Spring controller method. It actually does two updates on the same entity which causes the StaleObjectStateException. The problem is that when I retrieve the Member instance, I think it somehow causes an update (see `//UPDATE ONE`) of the Advertisement instances (this is not wanted actually) and when I update the Advertisement instance (see `//UPDATE TWO`), then it throws a StaleObjectStateException. My question is how can I prevent this exception from occurring in my case (bearing in mind I use Spring Data JPA)? Here is the `Member` entity class: ``` @Entity public class Member { ... @OneToMany(fetch = FetchType.LAZY, mappedBy = "member") private List<Advertisement> advertisements; ... ``` and in the `Advertisement` entity class: ``` @NotNull @ManyToOne(fetch = FetchType.LAZY) private Member member; ``` Here is the controller method: ``` @RequestMapping(value = "/family/advertisement/edit", method = RequestMethod.POST, produces = "text/html") public String editFamilyAdvertisement(@ModelAttribute @Validated(value = Validation.AdvertisementCreation.class) FamilyAdvertisementInfo familyAdvertisementInfo, BindingResult bindingResult, Model model) { Member member = memberService.retrieveCurrentMember();//UPDATE ONE if (!advertisementService.advertisementBelongsToMember(familyAdvertisementInfo.getFamilyAdvertisement(), member)) { throw new IllegalStateException("advertisement does not belong to member"); } if (bindingResult.hasErrors()) { populateModel(model, familyAdvertisementInfo); return "family/advertisement/edit"; } familyAdvertisementInfo.getFamilyAdvertisement().setMember(member); advertisementService.editFamilyAdvertisement(familyAdvertisementInfo.getFamilyAdvertisement());//UPDATE TWO return "redirect:/family/advertisement/edit/" + familyAdvertisementInfo.getFamilyAdvertisement().getId(); } ``` Here is the stacktrace: ``` org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bignibou.domain.FamilyAdvertisement#1] org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303) org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151) org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76) org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903) org.hibernate.internal.SessionImpl.merge(SessionImpl.java:887) org.hibernate.internal.SessionImpl.merge(SessionImpl.java:891) org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) java.lang.reflect.Method.invoke(Method.java:601) org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:366) com.sun.proxy.$Proxy45.merge(Unknown Source) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) java.lang.reflect.Method.invoke(Method.java:601) org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:241) com.sun.proxy.$Proxy44.merge(Unknown Source) org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:345) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) java.lang.reflect.Method.invoke(Method.java:601) org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334) org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) org.springframework.data.jpa.repository.support.LockModeRepositoryPostProcessor$LockModePopulatingMethodIntercceptor.invoke(LockModeRepositoryPostProcessor.java:91) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204) com.sun.proxy.$Proxy52.save(Unknown Source) com.bignibou.service.AdvertisementServiceImpl_Roo_Service.ajc$interMethod$com_bignibou_service_AdvertisementServiceImpl_Roo_Service$com_bignibou_service_AdvertisementServiceImpl$updateFamilyAdvertisement(AdvertisementServiceImpl_Roo_Service.aj:58) com.bignibou.service.AdvertisementServiceImpl.updateFamilyAdvertisement(AdvertisementServiceImpl.java:1) com.bignibou.service.AdvertisementServiceImpl_Roo_Service.ajc$interMethodDispatch1$com_bignibou_service_AdvertisementServiceImpl_Roo_Service$com_bignibou_service_AdvertisementServiceImpl$updateFamilyAdvertisement(AdvertisementServiceImpl_Roo_Service.aj) com.bignibou.service.AdvertisementServiceImpl.editFamilyAdvertisement(AdvertisementServiceImpl.java:27) com.bignibou.controller.AdvertisementController.editFamilyAdvertisement(AdvertisementController.java:85) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ``` edit 1: SQL logs: ``` 2013-04-06 11:23:24,339 [http-bio-8080-exec-9] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Returning handler method [public java.lang.String com.bignibou.controller.AdvertisementController.editFamilyAdvertisement(com.bignibou.controller.helpers.FamilyAdvertisementInfo,org.springframework.validation.BindingResult,org.springframework.ui.Model)] Hibernate: /* load com.bignibou.domain.GeolocationPostcode */ select geolocatio0_.id as id8_0_, geolocatio0_.postcode as postcode8_0_, geolocatio0_.version as version8_0_ from geolocation_postcode geolocatio0_ where geolocatio0_.id=? Hibernate: /* load com.bignibou.domain.Member */ select member0_.id as id6_1_, member0_.activated as activated6_1_, member0_.address as address6_1_, member0_.email as email6_1_, member0_.last_connection_date as last4_6_1_, member0_.password as password6_1_, member0_.registration_date as registra6_6_1_, member0_.role as role6_1_, member0_.token as token6_1_, member0_.version as version6_1_, address1_.id as id3_0_, address1_.formatted_address as formatted2_3_0_, address1_.latitude as latitude3_0_, address1_.longitude as longitude3_0_, address1_.version as version3_0_ from member member0_ left outer join address address1_ on member0_.address=address1_.id where member0_.id=? Hibernate: /* load one-to-many com.bignibou.domain.Member.advertisements */ select advertisem0_.member as member6_1_, advertisem0_.id as id0_1_, advertisem0_.id as id0_0_, advertisem0_.active as active0_0_, advertisem0_.creation_date as creation3_0_0_, advertisem0_.description as descript4_0_0_, advertisem0_.expiration_date as expiration5_0_0_, advertisem0_.member as member0_0_, advertisem0_.validated as validated0_0_, advertisem0_.version as version0_0_, advertisem0_.childminder_status as childmin1_10_0_, advertisem0_.clazz_ as clazz_0_ from ( select id, active, creation_date, description, expiration_date, validated, version, member, null as childminder_status, 1 as clazz_ from family_advertisement union select id, active, creation_date, description, expiration_date, validated, version, member, childminder_status, 2 as clazz_ from childminder_advertisement ) advertisem0_ where advertisem0_.member=? ``` Advertisement: ``` @RooJavaBean @RooToString @RooEquals @RooJpaEntity(inheritanceType = "TABLE_PER_CLASS") @Entity @DynamicUpdate public abstract class Advertisement { @ElementCollection private Set<ChildcareType> childcareTypes; @ManyToMany private List<DayToTimeSlot> dayToTimeSlots; @NotNull(groups = { Validation.AdvertisementCreation.class }) @ManyToMany private Set<GeolocationPostcode> postcodes; @NotNull(groups = { Default.class }) @Size(min = 6, max = 300, groups = { Default.class, Validation.AdvertisementCreation.class }) @Column(length = 300) private String description; @Temporal(TemporalType.TIMESTAMP) @DateTimeFormat(pattern = "dd/MM/yyyy HH:mm:ss") private Date creationDate; @Temporal(TemporalType.TIMESTAMP) @DateTimeFormat(pattern = "dd/MM/yyyy HH:mm:ss") private Date expirationDate; @NotNull(groups = { Default.class }) private boolean active; @NotNull(groups = { Default.class }) private boolean validated; @NotNull @ManyToOne(fetch = FetchType.LAZY) private Member member; public abstract boolean isChildcareTypesValid(); @AssertTrue(groups = { Validation.AdvertisementCreation.class }) public boolean isPostcodesValid() { return (postcodes != null && !postcodes.isEmpty()); } } ``` FamilyAdvertisement: ``` @RooJavaBean @RooToString @RooEquals @RooJpaEntity @Entity @DynamicUpdate public class FamilyAdvertisement extends Advertisement { @NotNull(groups = Validation.AdvertisementCreation.class) @ElementCollection private Set<Need> needs; @ElementCollection private Set<ChildminderStatus> childminderStatuses; @AssertTrue @Override public boolean isChildcareTypesValid() { return true; } @AssertTrue public boolean isNeedsValid() { if (needs.isEmpty()) { return false; } if (needs.contains(Need.CHILDMINDER_TO_FAMILY)) { return false; } return true; } } ``` edit 2: I must mention that I use entity versioning as provided by default by Spring Roo + JPA. I am pretty certain this must be taken into account. edit 3: I have set up a sample application that demonstrates the issue here on github. Anyone wishing to help me can clone the app as follows: `git clone https://github.com/balteo/sample-app-gab`.

Original source

Related problems