【问题标题】:Is there a simple way to update a Spring-Data based object with Spring MVC & JSON有没有一种简单的方法来使用 Spring MVC 和 JSON 更新基于 Spring-Data 的对象
【发布时间】:2014-03-11 12:07:17
【问题描述】:

我正在使用带有 RESTful 接口的 Spring-Data JPA 和 Spring-MVC。我正在尝试实现一个基本的 CRUD 控制器。我在找出实施“更新”的最佳方式时遇到了一些困难。

我的基本控制器方法很简单:

@RequestMapping( method=RequestMethod.POST, value="updateUser", produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public User update( @RequestBody final User user){
    userRepository.save(user);
    return user;
}

但是,这似乎只提供了“创建”,而不是“更新”。每次调用该方法时,都会在数据库中创建一个新用户,即使我在 JSON 对象中指定了用户 PK。

快速查看SimpleJpaRepository 类,只要“版本”字段丢失,它就会创建一个新对象。但是,如果我强制“版本”字段有一个值,我会得到一个异常(不足为奇):

Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.ia.domain.User#5]
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:898)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:902)
    at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:889)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)

我意识到一种选择是首先查询数据库中现有的用户对象(基于提交的 PK),然后复制所有字段,然后保存该对象,但这似乎不是正确的方法。我想一定有办法让我“合并”我的 User 对象并更新它,但我不完全确定如何。

有简单的方法吗?

【问题讨论】:

  • 考虑先查找再更新
  • @Koitoer - 我绝对可以先查找然后更新,但是我需要复制 POSTed JSON 对象中的所有字段,并且不确定是否有快速/简单/高效这样做的方式。

标签: java json rest spring-mvc spring-data-jpa


【解决方案1】:

跟踪到hibernate代码的错误发现版本有问题

else if ( isVersionChanged( entity, source, persister, target ) ) {
299             if ( source.getFactory().getStatistics().isStatisticsEnabled() ) {
300                 source.getFactory().getStatisticsImplementor()
301                         .optimisticFailure( entityName );
302             }
303             throw new StaleObjectStateException( entityName, id );
304         }

/VersionChanges 是

342     boolean changed = ! persister.getVersionType().isSame(
343             persister.getVersion( target ),
344             persister.getVersion( entity )
345     );

错误来自不同版本的实体, 考虑使用 JPARepository 的标准方法来保存。

@Transactional
public <S extends T> S save(S entity)

还使用 userRepository 的代码更新您的问题,因为我认为 saveUser 是您自己的实现

【讨论】:

  • 这实际上是我原来问题中的一个错字。我已经编辑以反映适当的调用。我还注意到,如果我在数据库中将版本设置为相同的版本,那么 save() 就可以工作。我最初试图从 JSON 中增加版本,这实际上是 Hibernate 过程的一部分。
【解决方案2】:

经过更多的研究、调试和反复试验,结果证明解决方案非常简单。被持久化实体的version 字段的值必须与数据库中当前的值相匹配。如果值不匹配,Hibernate 会假定其他东西更新了数据库并因此抛出 org.hibernate.StaleObjectStateException。

确保提交的 JSON 具有匹配的 PK 和版本值作为 DB 中的行,然后 repo.save(user) 将更新现有行。如果版本字段为空,则实体将作为新行保留。

【讨论】:

    【解决方案3】:

    我认为您不应该“强制版本字段具有价值”。它们的存在是为了乐观锁定。其他事务可能正在修改您的记录,并且您正在使用的版本变得陈旧。

    由于您的实体对象没有更新,Spring 可能无法检测到它是一个分离的实体,因为主键字段映射错误/未设置。这也可能是由于实体从一开始就没有被持久化(因此它处于新状态)

    【讨论】:

    • 同意;我只是想确定 spring-data 如何确定它是否是新实体。 SimpleJpaRepository 检查版本字段是否为空。如果它为空,则根据定义,它被视为一个新实体。如果不为空,它会尝试合并它。鉴于spring-data 没有将合并作为其回购的一部分,我不确定我应该如何将实体附加到事务中。
    【解决方案4】:

    我相信你想使用 userRepository.merge(user)。

    【讨论】:

    • 我认为 SimpleJpaRepository 没有合并,它不是 EnityManager
    • Spring JpaRepository 没有这样的方法(虽然原生 JPA / Hibernate 有)
    • 啊,我错过了它是 SimpleJpaRepo。这就是我阅读太快而让我的大脑填补太多空白的结果。
    • 这种情况更多是在尝试快速回答时发生,在我身上发生了好几次哈哈。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-01
    • 2010-09-19
    • 2015-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多