1

我有一个新的/非托管实例Address和一个托管实例Member作为参数传入以下方法:

@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
}

方法的实现updateAddress

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

如您所见,我正在尝试更新地址和 JPA,但有以下例外:

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)

除了繁琐地将字段从非托管实例(address参数)复制到托管实例(member.getAddress())并更新托管实例之外,我不确定如何做到这一点。

有人可以请教吗?

编辑1

我已经设置了一个重现该问题的示例应用程序。任何希望使用示例 github 应用程序重现问题的人都需要:

  • 马文
  • 吉特
  • JDK 6
  • MySQL

他们可以按照以下步骤重现问题:

  • git clone git@github.com:balteo/StaleObjectStateException.git
  • 在 mysql 中创建一个名为 sose 的数据库模式create database sose;
  • mvn test
  • 瞧:BOOM

谁能向我解释为什么在我的情况下会发生此异常以及如何更新地址实例而不会出现此异常?

4

2 回答 2

2

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

有一个Member实例已经保存在数据库中 - 它与已经保存在数据库中的Address实例具有外键关系。FK 关系通过member.getAddress()在对象模型中导航。Address 实例的主键是member.getAddress().getId()

address.setId(addressId)

您在对象模型中创建了 Address 的另一个新实例,并手动将主键设置为与数据库中保存的预先存在的实例相同的主键,然后尝试保存新实体。

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! :^)

于 2013-04-13T15:37:33.883 回答
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);
  }
于 2020-05-15T18:13:38.437 回答