【问题标题】:How to propagate Spring transaction to another thread?如何将 Spring 事务传播到另一个线程?
【发布时间】:2011-07-11 01:56:38
【问题描述】:

也许,我做错了什么,但对于以下情况,我找不到好的出路。

我想对在下面使用Spring Batch 执行作业的服务进行单元测试。这些作业通过预先配置的AsyncTaskExecutor 在单独的线程中执行。在我的单元测试中,我想:

  1. 创建几个域对象并通过 DAO 持久化它们
  2. 调用服务方法来启动作业
  3. 等到作业完成
  4. 使用 DAO 检索域对象并检查其状态

显然,以上所有内容都应该在一个事务中执行,但不幸的是,transactions are not propagated to new threads(我理解这背后的基本原理)。

我想到的想法:

  • 在步骤 (1) 之后提交事务#1。不好,因为 DB 状态应该在单元测试后回滚。
  • 在作业配置中使用Isolation.READ_UNCOMMITTED。但这需要两种不同的测试配置和生产配置。

【问题讨论】:

  • 我知道有些人不同意这一点,但是运行一个运行后回滚的测试并不是一个很好的做法。特别是如果您使用的是 ORM,因为在您执行选择或刷新会话之前,它不会发出任何插入/更新命令。
  • @Augusto:我发现回滚能力很好,因为测试不会相互冲突。否则,每个单元测试类需要一个setUp() 和一个test()。可以使用补偿事务(例如delete form mytable),但我看不出它如何符合 Spring 哲学。
  • 再说一次,我不认为每个人都同意我的建议。而且你需要确保你实际上是在测试某些东西,否则就像在没有断言的情况下进行测试。

标签: java multithreading spring transactions propagation


【解决方案1】:

如果您确实需要单独的配置,我建议您在配置中模板化隔离策略并从属性文件中获取其值,这样您就不会因测试和生产而使用一组不同的 Spring 配置。

但我同意最好使用相同的策略生产用途。您的夹具数据有多大,如果有一个 setUp() 步骤吹走并重建您的数据(可能来自快照,如果它是大量数据),那么您不必依赖回滚?

【讨论】:

  • 感谢您的评论。由于单元测试类中每个测试的测试数据(数据库状态)应该不同,我想在每个测试开始时设置数据库并提交它,但我找不到任何方法让TransactionStatus 传递给PlatformTransactionManager#commit()。我相信,唯一的出路是让单元测试类由一个setUp() 和一个test() 方法组成,然后在setUp() 中以某种方式以编程方式启动休眠事务。我已经在想象那个丑陋的代码了……
【解决方案2】:

我认为最简单的解决方案是在测试执行期间使用 SyncTaskExecutor 配置 JobLauncher - 这样作业在与测试相同的线程中执行并共享事务。

任务执行器配置可以移动到单独的spring配置xml文件中。有两个版本 - 一个带有 SyncTaskExecutor,用于测试期间,另一个带有 AsyncTaskExecutor,用于生产运行。

【讨论】:

  • 我想我还没有完全理解你的解决方案L请更详细一点。我不处理任何文件:处理步骤从 DB 读取某些内容并将某些内容写回 DB。初始化步骤需要用一些值初始化 DB。这不能作为单独的 SQL 脚本(也许您将其称为“文件”)来完成,因为 DB 对象很复杂,应该使用 DAO 进行持久化。由于 DAO 方法是从主线程调用的,因此更改对 SUT 不可见。
  • 我说的是spring配置文件。您可以将任务执行器配置移动到单独的 spring xml 文件中。您将拥有此 xml 文件的两个版本 - 一个用于测试将使用在同一线程中启动作业的 SyncTaskExecutor - 因此测试启动的事务也会传播到作业。另一个版本将包含用于实际应用程序执行的 AsyncTaskExecutor。
  • 是的,这是可行的,感谢您注意到这一点。当我想使用生产环境/AsyncTaskExecutor 进行端到端测试时,集成步骤呢?
【解决方案3】:

虽然这不是您问题的真正解决方案,但我发现可以在工作线程中手动启动 new 事务。在某些情况下,这可能就足够了。

来源:Spring programmatic transactions

例子:

@PersistenceContext
private EntityManager entityManager;
@Autowired
private PlatformTransactionManager txManager;

/* in a worker thread... */
public void run() {
    TransactionStatus tx = txManager.getTransaction(new DefaultTransactionDefinition());
    try {
        entityManager.find(...)
        ...
        entityManager.flush(...)
        etc...
        txManager.commit(tx);
    } catch (RuntimeException e) {
        txManager.rollback(tx);
    }
}

【讨论】:

  • 如果你这样走,最好使用TransactionTemplate
  • @rustyx,但它不是将事务传播到另一个线程——而是创建新的。有没有办法让工作线程参与到主线程创建的事务中?
猜你喜欢
  • 2017-11-22
  • 2014-06-22
  • 1970-01-01
  • 2019-09-16
  • 2015-10-23
  • 1970-01-01
  • 1970-01-01
  • 2018-01-15
  • 1970-01-01
相关资源
最近更新 更多