【问题标题】:Using spring data jpa in order to update an entity (without copying fields one by one)使用 spring data jpa 来更新实体(无需一一复制字段)
【发布时间】:2013-03-30 07:46:00
【问题描述】:

我有一个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!

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

【问题讨论】:

    标签: hibernate jpa spring-data-jpa


    【解决方案1】:

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

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

    address.setId(addressId)

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

    这是非法的。如果您希望更新预先存在的实体,您必须加载它、启动事务、修改属性并提交事务。如果你想添加一个新实体,你必须启动一个事务,创建一个新实例,填充它的属性(包括一个新的唯一主键),保存它并提交事务。

    您可以通过使用 JPA @Id 注释加上 PK 属性上的 @GeneratedValue 注释(加上可选的 @SequenceGenerator/@TableGenerator 注释之一在代码中的其他位置 - 通常在类上)来自动填充新的唯一 PK 值。这里有两个教程:http://www.oracle.com/technetwork/middleware/ias/id-generation-083058.htmlhttp://www.objectdb.com/java/jpa/entity/generated

    希望这会有所帮助! :^)

    【讨论】:

      【解决方案2】:

      Glen Best 所说的是 JPA 数据的正确行为。所以你无能为力。

      但是,Jonas Geiregat 推荐了一种明确的方法 -> https://stackoverflow.com/a/25155104/2951619。而不是检查哪些字段已被一一修改,因此手动更新这些字段。处理那些繁琐的空指针检查只是丑陋的。

      使您的实体类实现 Persistable 并相应地覆盖 isNew()。

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

      之后,不再检查每个更新的字段,而是让前端发送回整个对象,包括字段没有被修改,即使只是一个字段修改。然后,在您的服务中,您可以获取现有的 myClass.getId() 并将该 id 设置在前端发回的对象中。

        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);
        }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-09-21
        • 2012-08-06
        • 1970-01-01
        • 2015-05-25
        • 2020-05-11
        • 2011-03-29
        • 1970-01-01
        相关资源
        最近更新 更多