JUnit rollback transaction with @Async method

只愿长相守 提交于 2019-12-05 01:10:18

问题


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;
}
}

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();
}

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);
    }
}

Then HistoryWriter is called:

@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;
    }
}

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. 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.

Does anyone have any idea how to force rollback of the DB changes made in asynchronous method?

Side notes:

Transaction configuration:

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

Async configuration:

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


回答1:


Does anyone have any idea how to force rollback of the DB changes made in asynchronous method?

That is unfortunately not possible.

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.

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.

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.

Regards,

Sam (author of the Spring TestContext Framework)



来源:https://stackoverflow.com/questions/39042930/junit-rollback-transaction-with-async-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!