【问题标题】:Why @Transactional does not roll back one commit when another failed?为什么@Transactional 在另一个提交失败时不回滚一个提交?
【发布时间】:2020-01-31 08:46:37
【问题描述】:

所以我有课:

@Service
public class MyService {

  @Autowired
  private RepositoryA repoA;

  @Autowired
  private RepositoryB repoB;

  @Transactional
  public void storeEntity(SomeEntity e) {
    repoA.save(e);

    OtherEntity o = doSomethingWithEntity(e);

    repoB.save(o);
  }
}

我的方法storeEntity 两次保存到两个不同的数据源。我希望如果保存到 repoB 失败,或者 doSomethingWithEntity 失败,repoA.save(e) 将被回滚。

我想编写一个小测试来确保这种行为:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceForTransactionTest {
  @Autowired
  private MyService subject;

  @Autowired
  private RepositoryA repoA;

  @MockBean
  private RepositoryB repoB;

  @Test
  public void repoBShouldNotHaveEntries() {
    // given
    when(repoB.save(any())).thenThrow(new IllegalStateException("Something wrong with db"));
    assertThat(repoB.count()).isEqualTo(0);

    // when
    SomeEntity e = ...
    subject.storeEntity(e);

    // then
    assertThat(repoA.count()).isEqualTo(0);
  }
}

这不起作用,因为抛出异常并且测试失败。当我用 try/catch 包围调用时,我的断言失败,并显示 repoA 有 1 个条目的消息。如何解决这个问题?

我也试过这个:

  @Test
  public void repoBShouldNotHaveEntries() {
    // given
    when(repoB.save(any())).thenThrow(new IllegalStateException("Something wrong with db"));
    assertThat(repoB.count()).isEqualTo(0);

    // when
    SomeEntity e = ...
    try {
      subject.storeEntity(e);
    } catch (Exception e) {
      // some output here
    }
    // then
    assertThat(repoA.count()).isEqualTo(0);
  }

断言失败。我也试过这个:

  @Test
  public void repoBShouldNotHaveEntries() {
    // given
    when(repoB.save(any())).thenThrow(new IllegalStateException("Something wrong with db"));
    assertThat(repoB.count()).isEqualTo(0);

    // when
    SomeEntity e = ...
    subject.storeEntity(e);
  }

  @After
  public void tearDown() {
    // then
    assertThat(repoA.count()).isEqualTo(0);
  }
}

同样失败。找到了 1 条记录,但我希望 @Transactional 应该回滚。

【问题讨论】:

标签: java junit transactions spring-transactions


【解决方案1】:

当您通过 Spring 管理事务时,您实际上是在使用抽象。对于单个数据源(通常称为资源本地事务)事务启动/提交/回滚将按预期工作,但如果使用两个不同的数据源,因此您需要能够执行分布式事务的事务管理器,例如 BitronixAtomikos Essentials (在开源世界中)。在 EE 应用程序服务器环境中,此功能由服务器本身提供。 Here 是一篇很老的文章,值得一读(它使用一个数据库和一个消息代理参与分布式事务,但概念相同——关键是多资源)。有关 Bitrionix 和 Spring 的示例配置,请查看this out。

【讨论】:

  • 哦,拜托,我的情况真的那么具体吗?常规的 Spring 配置无法处理?
  • Spring 事务管理只是一个抽象和策略层。对于单个数据库,它委托给您使用的任何机制(JDBC、JPA 等),但是对于分布式事务,您必须告诉 Spring 您的事务管理器是什么。看看这个以获得更多解释stackoverflow.com/questions/9552718/…
  • 在多个资源的情况下的事务协调不是一件容易的事,这就是为什么你有非常详细的标准 X/OpenXA 来描述所涉及的协议。这些标准的实现已经在 EE 服务器(weblogic/Jboss/TomEE 等)中可用,因此 Spring 不会重新发明轮子。还有一些可用的开源实现,如 Bitronix。还有一个很好的文章是javaworld.com/article/2077714/xa-transactions-using-spring.html
猜你喜欢
  • 2014-08-07
  • 1970-01-01
  • 2017-11-16
  • 2021-12-07
  • 2012-03-04
  • 1970-01-01
  • 1970-01-01
  • 2021-09-15
  • 2021-05-27
相关资源
最近更新 更多