使用@Async 方法的 JUnit 回滚事务
JUnit rollback transaction with @Async method
我正在使用 SpringJUnit4ClassRunner
编写集成测试。
我有一个基地 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 创建一个测试:
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
对象留在数据库中。我需要进行测试事务以回滚最后的所有更改。
现在,到目前为止我已经尝试过:
- 删除
@Async
注释。显然,这不是解决方案,但这样做是为了确认回滚将在没有它的情况下执行。的确如此。
- 将
@Async
注释移至 HistoryWriter.saveHistory()
方法,使其与 @Transactional
位于同一位置。这篇文章https://dzone.com/articles/spring-async-and-transaction建议它应该这样工作,但对我来说,测试后没有回滚。
- 交换这两个注释的位置。它也没有给出预期的结果。
有谁知道如何强制回滚异步方法中所做的数据库更改?
旁注:
事务配置:
<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"/>
Does anyone have any idea how to force rollback of the DB changes made in asynchronous method?
不幸的是,这是不可能的。
Spring 通过 ThreadLocal
变量管理交易状态。因此,在另一个线程(例如,为您的 @Async
方法调用创建的线程)中启动的事务可以不参与为父线程管理的事务。
这意味着您的 @Async
方法使用的事务 never 与 test-managed transaction 相同,后者由 [=41] 自动回滚=] TestContext 框架。
因此,解决您的问题的唯一可能方法是手动撤消对数据库的更改。您可以使用 JdbcTestUtils
在 @AfterTransaction
方法中以编程方式执行 SQL 脚本,或者您也可以将 SQL 脚本配置为通过 [=41= 以声明方式执行]的@Sql
注释(使用after执行阶段)。后者,详见How to execute @Sql before a @Before method
此致,
Sam(Spring TestContext Framework 的作者)
我正在使用 SpringJUnit4ClassRunner
编写集成测试。
我有一个基地 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 创建一个测试:
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
对象留在数据库中。我需要进行测试事务以回滚最后的所有更改。
现在,到目前为止我已经尝试过:
- 删除
@Async
注释。显然,这不是解决方案,但这样做是为了确认回滚将在没有它的情况下执行。的确如此。 - 将
@Async
注释移至HistoryWriter.saveHistory()
方法,使其与@Transactional
位于同一位置。这篇文章https://dzone.com/articles/spring-async-and-transaction建议它应该这样工作,但对我来说,测试后没有回滚。 - 交换这两个注释的位置。它也没有给出预期的结果。
有谁知道如何强制回滚异步方法中所做的数据库更改?
旁注:
事务配置:
<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"/>
Does anyone have any idea how to force rollback of the DB changes made in asynchronous method?
不幸的是,这是不可能的。
Spring 通过 ThreadLocal
变量管理交易状态。因此,在另一个线程(例如,为您的 @Async
方法调用创建的线程)中启动的事务可以不参与为父线程管理的事务。
这意味着您的 @Async
方法使用的事务 never 与 test-managed transaction 相同,后者由 [=41] 自动回滚=] TestContext 框架。
因此,解决您的问题的唯一可能方法是手动撤消对数据库的更改。您可以使用 JdbcTestUtils
在 @AfterTransaction
方法中以编程方式执行 SQL 脚本,或者您也可以将 SQL 脚本配置为通过 [=41= 以声明方式执行]的@Sql
注释(使用after执行阶段)。后者,详见How to execute @Sql before a @Before method
此致,
Sam(Spring TestContext Framework 的作者)