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



我有一个方法

@Transactional
public void updateSharedStateByCommunity(List[]idList)

此方法是从以下REST API调用的:

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

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

所以,我想把它分成两个通话,每个通话的列表大小为100000。

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

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

我需要确保对一笔交易进行两次单独的调用。如果这两个调用中的任何一个失败,那么它应该回滚到所有操作。

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

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不会做任何事情,所以它可以工作。如果您将此代码放入其他类中,那么它将起作用,因为spring事务的默认传播级别是"Required">

因此,它解决了苛刻的要求:你想要一个事务——你已经得到了。现在所有的代码都在同一个事务中运行。现在,每个方法都运行100000个ID,而不是所有的ID,一切都是同步的:)

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

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

  2. 它假设网络是可靠的,等待30分钟在技术上不是问题(更不用说用户体验和"紧张"的用户将不得不等待:)

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

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

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

因此,与客户的互动将发生变化:

  1. 客户端使用200000个id调用"updateUser">
  2. 服务会"立即"回复,比如"我收到你的请求,这是一个请求Id,偶尔给我打一次ping,看看会发生什么。">
  3. 服务启动异步任务,并在单个事务中逐块处理数据
  4. 客户端使用该id调用"get"方法,服务器从共享状态读取进度
  5. 一旦准备好,"Get"方法将响应"done">

如果事务执行过程中出现故障,则回滚将完成,进程将用"failure"更新数据库状态。

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

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

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

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

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

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

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

相关内容

最新更新