繁体   English   中英

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

[英]How to make multiple call of @Transactional method to a single transaction

我有一个方法

@Transactional
public void updateSharedStateByCommunity(List[]idList)

从以下REST API调用此方法:

@RequestMapping(method = RequestMethod.POST)
public ret_type updateUser(param) {
  // call updateSharedStateByCommunity
}

现在 ID 列表非常大,比如 200000,当我尝试处理它时,它需要很多时间并且在客户端发生超时错误。

因此,我想将其拆分为两个调用,每个调用的列表大小为 100000。

但是,问题是,它被认为是 2 个独立的交易。

注意:2 次调用是一个例子,如果数字 id 更大,它可以分为多次。

我需要确保对单个事务的两个单独调用。 如果 2 个调用中的任何一个失败,则它应该回滚到所有操作。

另外,在客户端,我们需要显示进度对话框,所以我不能只使用超时。

对您的问题 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); 
    }
}

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

所以它满足了苛刻的要求:你想要一个单一的交易——你已经得到了。 现在所有代码都在同一个事务中运行。 现在每个方法都以 100000 运行,而不是所有的 id,一切都是同步的:)

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

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

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

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

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

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

这样与客户端的交互就会发生变化:

  1. 客户使用 200000 个 ID 调用“updateUser”
  2. 服务“立即”响应“我收到了您的请求,这是一个请求 ID,偶尔 ping 我看看会发生什么。
  3. 服务启动异步任务并在单个事务中逐块处理数据
  4. 客户端使用该 ID 调用“get”方法,服务器从共享的 state 读取进度。
  5. 一旦准备好,“Get”方法将响应“done”。

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

您还可以使用更现代的技术来通知服务器(例如 web sockets),但对于这个问题,它有点超出 scope 。

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM