【问题标题】:JUnit rollback transaction with @Async method使用@Async 方法的JUnit 回滚事务
【发布时间】:2016-08-19 15:36:39
【问题描述】:

我正在使用SpringJUnit4ClassRunner 编写集成测试。 我有一个基类:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({ /*my XML files here*/}) 
@Ignore
public class BaseIntegrationWebappTestRunner {

@Autowired
protected WebApplicationContext wac; 

@Autowired
protected MockServletContext servletContext; 

@Autowired
protected MockHttpSession session;

@Autowired
protected MockHttpServletRequest request;

@Autowired
protected MockHttpServletResponse response;

@Autowired
protected ServletWebRequest webRequest;

@Autowired
private ResponseTypeFilter responseTypeFilter;

protected MockMvc mockMvc;

@BeforeClass
public static void setUpBeforeClass() {

}

@AfterClass
public static void tearDownAfterClass() {

}

@Before
public void setUp() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(responseTypeFilter).build();
}

@After
public void tearDown() {
    this.mockMvc = null;
}
}

然后我扩展它并使用 mockMvc 创建一个测试:

public class MyTestIT extends BaseMCTIntegrationWebappTestRunner {

@Test
@Transactional("jpaTransactionManager")
public void test() throws Exception {
    MvcResult result = mockMvc
            .perform(
                    post("/myUrl")
                            .contentType(MediaType.APPLICATION_XML)
                            .characterEncoding("UTF-8")
                            .content("content")
                            .headers(getHeaders())
            ).andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(content().encoding("ISO-8859-1"))
            .andExpect(xpath("/*[local-name() ='myXPath']/")
                    .string("result"))
            .andReturn();
}

在流程结束时,将实体保存到数据库中。但这里的要求是应该异步完成。所以考虑调用这个方法:

@Component
public class AsyncWriter {

    @Autowired
    private HistoryWriter historyWriter;

    @Async
    public void saveHistoryAsync(final Context context) {
        History history = historyWriter.saveHistory(context);
    }
}

然后HistoryWriter被调用:

@Component
public class HistoryWriter {

    @Autowired
    private HistoryRepository historyRepository;

    @Transactional("jpaTransactionManager")
    public History saveHistory(final Context context) {
        History history = null;
        if (context != null) {
            try {
                history = historyRepository.saveAndFlush(getHistoryFromContext(context));
            } catch (Throwable e) {
                LOGGER.error(String.format("Cannot save history for context: [%s] ", context), e);
            }
        }
        return history;
    }
}

所有这一切的问题是,在测试完成后,History 对象留在数据库中。我需要进行测试事务以最终回滚所有更改。

现在,我到目前为止所做的尝试:

  1. 删除@Async 注释。显然,这不是解决方案,而是为了确认没有它的情况下将执行回滚。确实如此。
  2. @Async 注释移动到HistoryWriter.saveHistory() 方法以将其与@Transactional 放在一个位置。这篇文章https://dzone.com/articles/spring-async-and-transaction 建议它应该这样工作,但对我来说,测试后没有回滚。
  3. 交换这两个注释的位置。它也没有给出预期的结果。

有人知道如何强制回滚异步方法中所做的数据库更改吗?

旁注:

交易配置:

<tx:annotation-driven proxy-target-class="true" transaction- manager="jpaTransactionManager"/>

异步配置:

<task:executor id="executorWithPoolSizeRange" pool-size="50-75" queue-capacity="1000" /> <task:annotation-driven executor="executorWithPoolSizeRange" scheduler="taskScheduler"/>

【问题讨论】:

    标签: java junit spring-transactions spring-test spring-async


    【解决方案1】:

    有人知道如何强制回滚异步方法中所做的数据库更改吗?

    很遗憾,这是不可能的。

    Spring 通过ThreadLocal 变量管理事务状态。因此,在另一个线程中启动的事务(例如,为您的 @Async 方法调用创建的事务)可以参与为父线程管理的事务。

    这意味着您的 @Async 方法使用的事务从不与 Spring TestContext Framework 自动回滚的 test-managed transaction 相同。

    因此,解决您的问题的唯一可能方法是手动撤消对数据库的更改。您可以使用JdbcTestUtils@AfterTransaction 方法中以编程方式执行SQL 脚本,或者您可以配置一个SQL 脚本以通过Spring 的@Sql 注释以声明方式执行(使用after执行阶段)。对于后者,详见How to execute @Sql before a @Before method

    问候,

    Sam(Spring TestContext 框架的作者

    【讨论】:

    • 非常感谢您的回复。由于我无法存档,现在我知道这是不可能的,我最终创建了单独的 spring config xml 进行测试,其中删除了&lt;task:annotation-driven executor="executorWithPoolSizeRange" scheduler="taskScheduler"/&gt;,因此@Async 在测试期间将不起作用,因此事务将被回滚。跨度>
    • 我现在还有一个挑战,你能看看吗?所以除了 SpringJUnit4ClassRunner 之外,我还需要将 PowerMock 添加到我的测试用例中。为此,我添加了@RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class),现在我还想使用参数化运行器。我检查了jira.spring.io/browse/SPR-5292,看来我可以使用 Spring Rules 使其与 Parameterized 一起使用。但它也适用于 PowerMock 吗?我试图简单地将 PowerMock 委托给 Parameterized 并添加 Spring 规则,但没有奏效。能做到吗?
    • 这个组合我没试过,不知道好不好用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-18
    • 2013-05-08
    • 2013-05-05
    • 1970-01-01
    • 2022-09-27
    • 1970-01-01
    相关资源
    最近更新 更多