【问题标题】:How to make multiple call of @Transactional method to a single transaction如何对单个事务多次调用@Transactional 方法
【发布时间】:2020-02-07 14:12:29
【问题描述】:

我有办法

@Transactional
public void updateSharedStateByCommunity(List[]idList)

从以下 REST API 调用此方法:

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

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

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

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

注意:2 次调用只是一个例子,如果 number id 更大,可以分成多次。

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

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

【问题讨论】:

  • 默认情况下@Transactional 方法继承当前事务,因此只需从标记为@Transactional 的其他方法调用该方法。 注意:您必须手动flushclear 会话释放已处理对象的 RAM 内存
  • 使用 @Transactional 注释调用方法,并在您的方法上更改 Propagation 事务级别 - 如果需要。默认为REQUIRED - 支持当前事务,如果不存在则创建一个新事务。所以我相信你不必改变它。
  • 我认为传播级别无济于事。拆分将在客户端完成。因此,当时不存在先前的横断。
  • 客户是否期待它的任何回应?
  • 只需通过处理小批量和刷新和清除来改进您在updateSharedStateByCommunity 中的实现。但是,由于您没有包含实际的实现,我们无法为您提供帮助。

标签: java spring-boot server


【解决方案1】:

对您的问题 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 事务的默认传播级别是“必需的”

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

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

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

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

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

这就是人们建议某种异步流程的原因。

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

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

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 如果操作需要30分钟,那么超时也不好
      • @I.Ahmed 如果操作需要 30 分钟,那么让客户端等待是没有意义的。将其设为@Async 呼叫,并在处理完成后立即通过推送通知等通知客户。
      • 我的用例不允许使用异步。我需要阻止用户的其他操作,直到它完成。
      • 超时只是为了避免事务因为超时而中止。对于用户体验和反馈,请像 Luis says 一样使用 Async + WebSockets。阻塞 30 多分钟根本不是一个好主意。
      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 2019-03-30
        • 1970-01-01
        • 1970-01-01
        • 2023-03-20
        • 1970-01-01
        • 2011-07-03
        • 2017-02-05
        • 1970-01-01
        • 2011-02-21
        相关资源
        最近更新 更多