简体   繁体   English

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

[英]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.然而,由于许多不同的原因,这种设计是有问题的。

  1. 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 是同步的。

  2. 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 和“紧张”用户:))

  3. 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:这样与客户端的交互就会发生变化:

  1. Client calls "updateUser" with 200000 ids客户使用 200000 个 ID 调用“updateUser”
  2. Service responds "immediately" with something like "I've got your request, here is a request Id, ping me once in a while to see what happens.服务“立即”响应“我收到了您的请求,这是一个请求 ID,偶尔 ping 我看看会发生什么。
  3. Service starts an async task and process the data chunk by chunk in a single transaction服务启动异步任务并在单个事务中逐块处理数据
  4. Client calls "get" method with that id and server reads the progress from the shared state.客户端使用该 ID 调用“get”方法,服务器从共享的 state 读取进度。
  5. Once ready, the "Get" methods will respond "done".一旦准备好,“Get”方法将响应“done”。

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.

相关问题 如何在单一方法Springboot JPA中实现多次更新的事务 - How to implement Transactional for multiple updates in single method Springboot JPA 当在类和方法中都添加了带注释的Transactional时,如何使事务仅在方法中生效? - How to make transaction only effect in a method when a annotated Transactional is added in both class and method? 如何从事务方法调用非事务方法 - How to call non-transactional methods from a Transactional method 如何确保在Spring中使用单个事务管理器处理多个DB? - How to make sure multiple DBs are transacted with a single transaction manager in spring? Spring @Transactional方法 - 参与交易 - Spring @Transactional method - participating transaction 具有@Transactional注释的多个事务管理器 - Multiple transaction managers with @Transactional annotation 如何在相同和不同服务中调用@Transactional和non-Transactional方法时回滚事务? - How to rollback transaction on calling @Transactional and non-Transactional method in same and different Service? 如何使用事务/原子进行多个REST请求? - How to make multiple REST requests transactional/atomic? 在单个事务中运行spring @Transactional注释方法 - Run spring @Transactional annotated methods in a single transaction Spring @Transactional如何“停止”交易 - Spring @Transactional how to “stop” transaction
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM