使用@Async方法的JUnit回滚事务

dty*_*dty 4 java junit spring-test spring-transactions spring-async

我正在使用编写集成测试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;
}
}
Run Code Online (Sandbox Code Playgroud)

然后,我将其扩展并使用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();
}
Run Code Online (Sandbox Code Playgroud)

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

@Component
public class AsyncWriter {

    @Autowired
    private HistoryWriter historyWriter;

    @Async
    public void saveHistoryAsync(final Context context) {
        History history = historyWriter.saveHistory(context);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后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;
    }
}
Run Code Online (Sandbox Code Playgroud)

所有这些的问题是,在完成测试之后,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"/>

Sam*_*nen 5

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

不幸的是,这是不可能的。

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

这意味着,您所使用的交易@Async方法是永远不会一样的测试管理的事务,其被由Spring TestContext框架自动回滚。

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

问候,

Sam(Spring TestContext Framework的作者