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的区别