为了账号安全,请及时绑定邮箱和手机立即绑定

如何对单个事务多次调用@Transactional方法

如何对单个事务多次调用@Transactional方法

暮色呼如 2023-10-13 10:30:54
我有一个方法@Transactionalpublic void updateSharedStateByCommunity(List[]idList)从以下 REST API 调用此方法:@RequestMapping(method = RequestMethod.POST)public ret_type updateUser(param) {  // call updateSharedStateByCommunity}现在ID列表非常大,比如200000,当我尝试处理它时,需要花费很多时间并且在客户端发生超时错误。因此,我想将其拆分为两个调用,每个调用的列表大小为 100000。但是,问题是,它被视为两个独立的交易。注意:2次呼叫只是一个例子,如果号码id更大的话,它可以分为多次。我需要确保对单个事务进行两次单独的调用。如果这 2 个调用中的任何一个失败,那么它应该回滚到所有操作。另外,在客户端,我们需要显示进度对话框,所以我不能只使用超时。
查看完整描述

4 回答

?
繁星点点滴滴

TA贡献1803条经验 获得超3个赞

IMO 对您的问题最明显的直接答案是稍微更改代码:


@RequestMapping(method = RequestMethod.POST)

public ret_type updateUser(param) {

    updateSharedStateByCommunityBlocks(resolveIds);

}


...


And in Service introduce a new method (if you can't change the code of the service provide an intermediate class that you'll call from controller with the following functionality):


@Transactional

public updateSharedStatedByCommunityBlocks(resolveIds) {

    List<String> [] blocks = split(resolveIds, 100000);  // 100000 - bulk size

    for(List<String> block :blocks) {

       updateSharedStateByCommunity(block); 

    }

}

如果此方法位于同一服务中,则@Transactional原始方法updateSharedStateByCommunity不会执行任何操作,因此它会起作用。如果您将此代码放入其他类中,那么它将起作用,因为 spring 事务的默认传播级别是“必需”

因此它满足了苛刻的要求:您想要进行一次交易 - 您已经做到了。现在所有代码都在同一个事务中运行。现在每个方法都使用 100000 个 ID 运行,而不是使用所有 id,一切都是同步的:)

然而,这种设计由于许多不同的原因而存在问题。

  1. 正如您在问题的最后一句中所说,它不允许跟踪进度(向用户显示)。REST 是同步的。

  2. 它假设网络是可靠的,并且等待 30 分钟在技术上不是问题(不考虑 UX 和必须等待的“紧张”用户:))

  3. 除此之外,网络设备可以强制关闭连接(例如具有预先配置的请求超时的负载均衡器)。

这就是为什么人们建议某种异步流。

我可以说,您仍然可以使用异步流,生成任务,并在每次批量更新后一些共享状态(在单个实例的情况下在内存中)和持久状态(如集群情况下的数据库)。

这样与客户端的交互就会改变:

  1. 客户端使用 200000 个 id 调用“updateUser”

  2. 服务会“立即”响应,例如“我收到了您的请求,这是一个请求 ID,请偶尔对我进行 ping 操作,看看会发生什么情况。

  3. 服务启动异步任务并在单个事务中逐块处理数据

  4. 客户端使用该 id 调用“get”方法,服务器从共享状态读取进度。

  5. 一旦准备好,“获取”方法将响应“完成”。

如果事务执行期间出现故障,则回滚完成,并且进程将数据库状态更新为“失败”。

您还可以使用更现代的技术来通知服务器(例如网络套接字),但这超出了这个问题的范围。

这里需要考虑的另一件事是:据我所知,处理 200000 个对象应该在不到 30 分钟的时间内完成,对于现代 RDBMS 来说还不够。当然,在不知道您的用例的情况下,很难判断那里发生了什么,但也许您可以优化流程本身(使用批量操作、减少对数据库的请求数量、缓存等)。


查看完整回答
反对 回复 2023-10-13
?
四季花海

TA贡献1811条经验 获得超5个赞

在这些场景中,我的首选方法是使调用异步(Spring Boot 允许使用注释@Async),因此客户端不会期望任何 HTTP 响应。该通知可以通过 WebSocket 完成,该 WebSocket 将向客户端推送一条消息,其中包含每个 X 项的处理进度。

当然,它会给您的应用程序增加更多的复杂性,但如果您正确设计该机制,您将能够将其重用于您将来可能面临的任何其他类似操作。


查看完整回答
反对 回复 2023-10-13
?
MYYA

TA贡献1868条经验 获得超4个赞

从技术角度来看,可以通过传播来完成org.springframework.transaction.annotation.Propagation#NESTED,NESTED 行为使嵌套 Spring 事务使用相同的物理事务,但在嵌套调用之间设置保存点,因此内部事务也可以独立于外部事务回滚,或者让它们传播。但限制仅适用于org.springframework.jdbc.datasource.DataSourceTransactionManager数据源。

但是对于非常大的数据集,它仍然需要更多的时间来处理并使客户端等待,因此从解决方案的角度来看,也许使用异步方法会更好,但这取决于您的要求。


查看完整回答
反对 回复 2023-10-13
?
一只斗牛犬

TA贡献1784条经验 获得超2个赞

@Transactional注释接受 atimeout(尽管并非所有底层实现都支持它)。我反对尝试将 ID 拆分为两个调用,而是尝试修复超时(毕竟,您真正想要的是单个、全有或全无的事务)。您可以为整个应用程序设置超时,而不是针对每个方法设置超时。



查看完整回答
反对 回复 2023-10-13
  • 4 回答
  • 0 关注
  • 176 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信