[英]How to make multiple call of @Transactional method to a single transaction
I have a method我有一个方法
@Transactional
public void updateSharedStateByCommunity(List[]idList)
This method is called from the following REST API:从以下REST API调用此方法:
@RequestMapping(method = RequestMethod.POST)
public ret_type updateUser(param) {
// call updateSharedStateByCommunity
}
Now the ID lists are very large, like 200000, When I try to process it, then it takes lots of time and on client side timeout error occurred.现在 ID 列表非常大,比如 200000,当我尝试处理它时,它需要很多时间并且在客户端发生超时错误。
So, I want to split it to two calls with list size of 100000 each.因此,我想将其拆分为两个调用,每个调用的列表大小为 100000。
But, the problem is, it is considered as 2 independent transactions.但是,问题是,它被认为是 2 个独立的交易。
NB: The 2 calls is an example, it can be divided to many times, if number ids are more larger.注意:2 次调用是一个例子,如果数字 id 更大,它可以分为多次。
I need to ensure two separate call to a single transaction.我需要确保对单个事务的两个单独调用。 If any one of the 2 calls fails, then it should rollback to all operation.
如果 2 个调用中的任何一个失败,则它应该回滚到所有操作。
Also, in the client side, we need to show progress dialog, so I can't use only timeout.另外,在客户端,我们需要显示进度对话框,所以我不能只使用超时。
The most obvious direct answer to your question IMO is to slightly change the code:对您的问题 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);
}
}
If this method is in the same service, the @Transactional
in the original updateSharedStateByCommunity
won't do anything so it will work.如果此方法在同一个服务中,则原始
updateSharedStateByCommunity
中的@Transactional
不会做任何事情,因此它会起作用。 If you'll put this code into some other class, then it will work since the default propagation level of spring transaction is "Required"如果您将此代码放入其他一些 class 中,那么它将起作用,因为 spring 事务的默认传播级别是“必需的”
So it addresses harsh requirements: you wanted to have a single transaction - you've got it.所以它满足了苛刻的要求:你想要一个单一的交易——你已经得到了。 Now all the code runs in the same transaction.
现在所有代码都在同一个事务中运行。 Each method now runs with 100000 and not with all the ids, everything is synchronous:)
现在每个方法都以 100000 运行,而不是所有的 id,一切都是同步的:)
However, this design is problematic for many different reasons.然而,由于许多不同的原因,这种设计是有问题的。
It doesn't allow to track the progress (show it to the user) as you've stated by yourself in the last sentence of the question.正如您在问题的最后一句中所说的那样,它不允许跟踪进度(向用户显示)。 REST is synchronous.
REST 是同步的。
It assumes that network is reliable and waiting for 30 minutes is technically not a problem (leaving alone the UX and 'nervous' user that will have to wait:) )它假设网络是可靠的并且等待 30 分钟在技术上不是问题(更不用说必须等待的 UX 和“紧张”用户:))
In addition to that, the network equipment can force closing the connection (like load balancers with pre-configured request timeout).除此之外,网络设备可以强制关闭连接(例如具有预先配置的请求超时的负载平衡器)。
That's why people suggest some kind of asyncrhonous flow.这就是为什么人们建议某种异步流程。
I can say that you still can use the async flow, spawn the task, and after each bulk update some shared state (in-memory in the case of a single instance) and persistent (like database in the case of cluster).我可以说您仍然可以使用异步流,生成任务,并在每次批量更新一些共享 state(在单个实例的情况下在内存中)和持久性(如在集群的情况下的数据库)。
So that the interaction with the client will change:这样与客户端的交互就会发生变化:
If something fails during the transaction execution, the rollback is done, and the process updates the database status with "failure".如果在事务执行过程中出现故障,则回滚完成,进程将数据库状态更新为“失败”。
You can also use more modern technologies to notify the server (web sockets for example), but it's kind of out of scope for this question.您还可以使用更现代的技术来通知服务器(例如 web sockets),但对于这个问题,它有点超出 scope 。
Another thing to consider here: from what I know, processing 200000 objects should be done in much less then 30 minutes, its not that much for modern RDBMSs.这里要考虑的另一件事:据我所知,处理 200000 个对象应该在 30 分钟内完成,这对于现代 RDBMS 来说并没有那么多。 Of course, without knowing your use case its hard to tell what happens there, but maybe you can optimize the flow itself (using bulk operations, reducing the number of requests to db, caching and so forth).
当然,在不了解您的用例的情况下很难说出那里发生了什么,但也许您可以优化流程本身(使用批量操作,减少对数据库的请求数量,缓存等等)。
My preferred approach in those scenarios is make the call asynchronous (Spring Boot allow this using the @Async
annotation), hence the client won't expect for any HTTP response.在这些情况下,我首选的方法是使调用异步(Spring Boot 允许使用
@Async
注释进行此操作),因此客户端不会期望任何 HTTP 响应。 The notification could be done via a WebSocket that will push a message to the client with the progress each X items processed.通知可以通过 WebSocket 完成,它将向客户端推送一条消息,其中包含每个 X 项目处理的进度。
Surely it will add more complexity to your application, but if you design the mechanism properly, you'll be able to reuse it for any other similar operation you may face in the future.当然,它会给您的应用程序增加更多复杂性,但是如果您正确设计该机制,您将能够将其重用于您将来可能面临的任何其他类似操作。
The @Transactional
annotation accepts a timeout
(although not all underlying implementations will support it). @Transactional
注释接受timeout
(尽管并非所有底层实现都支持它)。 I would argue against trying to split the IDs into two calls, and instead try to fix the timeout (after all, what you really want is a single, all-or-nothing transaction).我反对尝试将 ID 拆分为两个调用,而是尝试修复超时(毕竟,您真正想要的是一个单一的、全有或全无的事务)。 You can set timeouts for the whole application instead of on a per-method basis.
您可以为整个应用程序设置超时,而不是基于每个方法。
From technical point, it can be done with the org.springframework.transaction.annotation.Propagation#NESTED
Propagation, The NESTED behavior makes nested Spring transactions to use the same physical transaction but sets savepoints between nested invocations so inner transactions may also rollback independently of outer transactions, or let them propagate.从技术角度来看,它可以通过
org.springframework.transaction.annotation.Propagation#NESTED
传播来完成,NESTED 行为使嵌套的 Spring 事务使用相同的物理事务,但在嵌套调用之间设置保存点,因此内部事务也可以独立回滚外部事务,或者让它们传播。 But the limitation is only works with org.springframework.jdbc.datasource.DataSourceTransactionManager
datasource.但该限制仅适用于
org.springframework.jdbc.datasource.DataSourceTransactionManager
数据源。
But for really large dataset, it still need more time to processing and make the client waiting, so from solution point of view, maybe using async approach will be more better but it depends on your requirement.但是对于非常大的数据集,它仍然需要更多的时间来处理并使客户端等待,所以从解决方案的角度来看,也许使用异步方法会更好,但这取决于您的要求。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.