1

I have a fresh/unmanaged instance of Address and a managed instance of Member passed in as arguments to the following method:

@Override
public void modifyAddress(Member member, Address address){
    long addressId = member.getAddress().getId();//retrieving id of managed address instance
    address.setId(addressId);//setting id on unmanaged instance
    updateAddress(address);//updating unmanaged instance
}

Implementation of updateAddress method:

 public Address PreferencesServiceImpl.updateAddress(Address address) {
        return addressRepository.save(address);
 }

As you can see, I am trying to update the address and JPA balks with the following exception:

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bignibou.domain.Address#5]
    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.$Proxy122.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.$Proxy121.merge(Unknown Source)
    org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:353)
    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:333)
    org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:318)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
    org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
    org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
    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:92)
    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.$Proxy137.save(Unknown Source)
    com.bignibou.service.PreferencesServiceImpl_Roo_Service.ajc$interMethod$com_bignibou_service_PreferencesServiceImpl_Roo_Service$com_bignibou_service_PreferencesServiceImpl$updateAddress(PreferencesServiceImpl_Roo_Service.aj:81)
    com.bignibou.service.PreferencesServiceImpl.updateAddress(PreferencesServiceImpl.java:1)

I am not sure how to get this right apart from tediously copying fields from the unmanaged instance (address argument) to the managed instance (member.getAddress()) one by one and updating the managed instance.

Can anyone please advise?

edit 1:

I have set up a sample application that reproduces the problem. Anyone wishing to reproduce the problem using the sample github app needs:

  • Maven
  • Git
  • JDK 6
  • MySQL

They can reproduce the problem by following the steps below:

  • git clone git@github.com:balteo/StaleObjectStateException.git
  • In mysql create a database schema called sose create database sose;
  • mvn test
  • and voila: BOOM!

Can anyone please explain to me why this exception is occurring in my case and how to update the address instance without getting this exception?

balteo
  • 23,602
  • 63
  • 219
  • 412

2 Answers2

2

long addressId = member.getAddress().getId();

There is an instance of Member already saved in the database - and it has a foreign key relationship to an instance of Address already saved in the database. The FK relationship is navigated in the object model via member.getAddress(). The primary key of the Address instance is member.getAddress().getId().

address.setId(addressId)

You have created another new instance of Address in the object model, and manually set to the same primary key as the pre-existing instance saved in the database, then attempted to save the new entity.

This is illegal. If you wish to update a pre-existing entity you must load it, start a transaction, modify the attributes and commit the transaction. If you wish to add a new entity, you must start a transaction, create a new instance, populate its attributes (including a NEW unique primary key), save it and commit the transaction.

You can automatically populate new unique PK values by using the JPA @Id annotation plus the @GeneratedValue annotation on the PK attribute (plus optionally one of @SequenceGenerator/@TableGenerator annotation somewhere else in your code - usually on a class). Here are two tutorials: http://www.oracle.com/technetwork/middleware/ias/id-generation-083058.html http://www.objectdb.com/java/jpa/entity/generated

Hope this helps! :^)

Glen Best
  • 22,769
  • 3
  • 58
  • 74
0

What Glen Best said is the correct behaviour of JPA data. So there is not much you can do about it.

However, there is a clear way of doing which was recommended by Jonas Geiregat -> https://stackoverflow.com/a/25155104/2951619. Instead of checking which fields has been modified one by one and therefore updating those fields manually. Handling those tedious null pointer checking is just ugly.

Making your entity class to implemented Persistable< T > and override isNew() accordingly.

public class MyClass implements Persistable<Integer> {
  @JsonIgnore
  @Override
  public boolean isNew() {
    return this.id  == null;
  }
}

After that, instead of checking each updated field, making the front-end to send back the whole object including fields have not been modified even just one field modification. In your service, you can then just get the existing myClass.getId() and set that id in the object that front-end send back.

  public MyClass updateClass(Integer id, MyClass modifiedClass) {
    MyClass existingClass = repository.findById(id);

    // you can also prevent id parameter does not exist in the database
    // get the existing object incase you have nested objects
    modifiedClass.getNestedObject().setId(
     existingClass.getNestedObject().getId());
    modifiedClass.setId(existingClass.getId());

    return repository.save(modifiedClass);
  }
Alan Ho
  • 646
  • 9
  • 5