本文介绍了使用@Async方法的JUnit回滚事务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am writing an integration test using SpringJUnit4ClassRunner.I have a base class:

@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创建测试:

Then I extend it and create a test using 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();
}

最后,将实体保存到DB中.但是这里的要求是应该异步完成.因此,请考虑将此方法称为:

In the end of the flow, an entity is saved into DB. But the requirement here is that is should be done asynchronously. So consider this method is called:

@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对象留在了数据库中.我需要进行测试事务以最终回滚所有更改.

The problem with all this is that after test is done, History object is left in the DB. I need to make test transaction to rollback all changes in the end.

现在,我到目前为止已经尝试过:

Now, what I've tried so far:

  1. 删除@Async批注.显然,这不是解决方案,但是这样做是为了确认没有它就可以执行回滚.确实是这样.
  2. @Async注释移至HistoryWriter.saveHistory()方法,以将其与@Transactional放在一个位置.本文 https://dzone.com/articles/spring-async-and-transaction建议它应该以这种方式工作,但对我来说,测试后不进行回滚.
  3. 交换这两个注释的位置.它也无法提供理想的结果.
  1. Remove @Async annotation. Obviously, this cannot be the solution, but was done to confirm rollback will be perform without it. Indeed, it is.
  2. Move @Async annotation to HistoryWriter.saveHistory() method to have it in one place with @Transactional. This article https://dzone.com/articles/spring-async-and-transaction suggests it should work this way, but for me, no rollback is done after test.
  3. Swap places of these two annotations. It does not give the desired result as well.

是否有人知道如何强制回退以异步方法进行的数据库更改?

旁注:

交易配置:

<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"/>

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

推荐答案

不幸的是,这不可能.

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

Spring manages transaction state via ThreadLocal variables. A transaction started in another thread (e.g., the one created for your @Async method invocation) can therefore not participate in a transaction managed for the parent thread.

这意味着您的@Async方法使用的交易与测试管理的交易,它会由Spring TestContext Framework自动回滚.

This means that the transaction used by your @Async method is never the same as the test-managed transaction which gets automatically rolled back by the Spring TestContext Framework.

因此,解决问题的唯一可能方法是手动撤消对数据库的更改.您可以使用JdbcTestUtils来执行此操作,以在@AfterTransaction方法内以编程方式执行SQL脚本,或者您也可以配置SQL脚本以通过Spring的@Sql注释以声明方式执行(使用之后执行阶段).对于后者,请参见如何在@Before方法之前执行@Sql 了解详情.

Thus, the only possible solution to your problem is to manually undo the changes to the database. You can do this using JdbcTestUtils to execute an SQL script programmatically within an @AfterTransaction method, or you can alternatively configure an SQL script to be executed declaratively via Spring's @Sql annotation (using the after execution phase). For the latter, see How to execute @Sql before a @Before method for details.

此致

Sam(Spring TestContext Framework的作者)

Sam (author of the Spring TestContext Framework)

这篇关于使用@Async方法的JUnit回滚事务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-04 06:51
查看更多