HIbernate“StaleStateException:批量更新从更新 [0] 返回了意外的行数;实际行数:0;预期:1”

Posted

技术标签:

【中文标题】HIbernate“StaleStateException:批量更新从更新 [0] 返回了意外的行数;实际行数:0;预期:1”【英文标题】:HIbernate "StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1" 【发布时间】:2021-04-24 08:54:05 【问题描述】:

当现有用户提出请求时,该方法首先删除最旧的记录,然后再保存新请求。如果请求不是太快进入(使用 Oracle DB),则下面的代码可以正常工作。

   public Request saveRequest(Request req)
   
      String user = req.getUser();
      
      // Do a NamedQuery on the entity to pull all requests for user
      List<Request> requestList = getRequests(user);
      
      LOGGER.info("Request List size is " + requestList.size() + " for " + user);
      
      // Sort the list then delete the first/oldest request
      Comparator<Request> bySessionDate = Comparator.comparing(Request::getDate);
      Collections.sort(requestList, bySessionDate);
         
      LOGGER.info("Deleting request id " + requestList.get(0).getId());

      deleteById(requestList.get(0).getId());

      Request sreq = requestRepository.create(req);
      LOGGER.info("Saved request for user " + sreq.getUserId());
      return sreq;
   

输出:

2021-01-20 00:39:12,167 INFO  [beez.service.RequestManager] (default task-4) Request List size is 250 for Bob
2021-01-20 00:39:12,168 INFO  [beez.service.RequestManager] (default task-4) Deleting request id 757A9B21E51D49199F2E182F68BC6BF7
2021-01-20 00:39:12,171 INFO  [beez.service.RequestManager] (default task-4) Deleted: 757A9B21E51D49199F2E182F68BC6BF7
2021-01-20 00:39:12,173 INFO  [beez.service.RequestManager] (default task-4) Saved request for user Bob
2021-01-20 00:39:15,375 INFO  [beez.service.RequestManager] (default task-3) Request List size is 250 for Bob
2021-01-20 00:39:15,375 INFO  [beez.service.RequestManager] (default task-3) Deleting request id 27239B85472C45EDA5495E98523295F3
2021-01-20 00:39:15,377 INFO  [beez.service.RequestManager] (default task-3) Deleted: 27239B85472C45EDA5495E98523295F3
2021-01-20 00:39:15,380 INFO  [beez.service.RequestManager] (default task-3) Saved request for user Bob

但是,如果用户以非常快速的方式(即快乐的点击器)提交请求,则相同的代码会生成 StaleStateException 错误。

2021-01-20 00:42:31,307 INFO  [beez.service.RequestManager] (default task-3) Request List size is 250 for Bob
2021-01-20 00:42:31,307 INFO  [beez.service.RequestManager] (default task-3) Deleting request id 55E43DF4D83E4BF5AD73DE47A49B0DA9
2021-01-20 00:42:31,310 INFO  [beez.service.RequestManager] (default task-3) Deleted: 55E43DF4D83E4BF5AD73DE47A49B0DA9
2021-01-20 00:42:31,313 INFO  [beez.service.RequestManager] (default task-3) Saved request for user Bob
2021-01-20 00:42:31,332 INFO  [beez.service.RequestManager] (default task-7) Request List size is 250 for Bob
2021-01-20 00:42:31,332 INFO  [beez.service.RequestManager] (default task-7) Deleting request id 55E43DF4D83E4BF5AD73DE47A49B0DA9
2021-01-20 00:42:31,492 ERROR [org.jboss.as.ejb3.invocation] (default task-7) WFLYEJB0034: EJB Invocation failed on component RequestManager for method public beez.entity.RequestManager service.RequestManager.saveRequest(beez.entity.RequestManager): javax.ejb.EJBTransactionRolledbackException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
.
.
.
Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1

似乎代码在下一个出现之前没有时间完成更改,导致代码尝试两次删除相同的记录。除了在这个方法之前改变前端或其他东西,有没有办法解决这个问题?

我尝试了@Transactional 和@Lock 选项,但没有成功。在这个线程上花了很多时间,但解决方案要么不起作用,要么不适用: Hibernate - Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1

【问题讨论】:

【参考方案1】:

如果用户不希望快速点击,强烈建议您考虑前面的 Debounceing API 请求以跳过背靠背的请求/事件。

正如你所说的正确,由于加载到单个会话的实体与数据库的当前状态之间的不一致,问题正在发生。

这可以通过多种方式解决,例如使用带有性能瓶颈的(选择更新)的悲观锁定,同步方法......等

处理此问题的最简单方法是使用 JPA 查询删除并在数据库级别排序,因此这始终适用于记录表的当前状态。

delete from request where id= (select req.id from request req left join user usr on usr.id = req.userId where usr.userId=? order by req.date LIMIT 1)

请根据您的实体设计更正上述查询。

【讨论】:

我尝试了您的建议,但是当用户提交包含多个请求的单个提交时,存在同样的问题。每个请求都被代码作为一个请求一个接一个地接收,间隔在 20 到 30 毫秒之间。因此,在下一个请求到来之前代码还没有完成删除,这也会触发该用户的删除。在我看来,如果我尝试实现锁定或同步,我将为所有用户执行此操作,这会造成巨大的瓶颈,即用户 B 将不得不等待用户 A 的进程完成,等等。 我觉得这不应该是一个独特的问题。您有一个系统为多个用户提供频繁/多个请求,这些请求被保存/删除在数据库中。目前,我看到的唯一解决方案是修改前端,为快乐的点击者实现一个去抖动器并批量发送所有请求,而不是单独在服务中爆破它们。 @stackbacker 删除查询不应抛出陈旧状态,因为我们直接在数据库上运行它而不是删除特定实体。另一种方法是为此操作启动内部事务并立即提交,但这可以根据您的外部事务边界恢复。 你是绝对正确的。不存在相同的错误...我被一个与同一问题相关的新问题分散了注意力。我将此标记为解决方案。谢谢! 感谢@stackbacker,测试尚未创建任何请求的第一次用户的行为,这涵盖了第一次用户等边缘情况,如果数据库中有一个请求并且您正在尝试从两个不同的事务中删除

以上是关于HIbernate“StaleStateException:批量更新从更新 [0] 返回了意外的行数;实际行数:0;预期:1”的主要内容,如果未能解决你的问题,请参考以下文章

Spring和Hibernate的注解整合 hibernate3和hibernate4/5的区别

hibernate.merge()方法怎么用

hibernate 异常 怎么解决

Hibernate之Hibernate环境配置

(转)Hibernate框架基础——Hibernate API及Hibernate主配置文件

Hibernate基础学习—Hibernate相关API介绍