Откат транзакции JUnit с помощью метода @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;
}
}
Затем я расширяю его и создаю тест, используя 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"/>
1 ответ
У кого-нибудь есть идеи, как принудительно откатить изменения БД, сделанные асинхронным методом?
К сожалению, это невозможно.
Spring управляет состоянием транзакции через ThreadLocal
переменные. Транзакция началась в другом потоке (например, созданном для вашего @Async
поэтому вызов метода) не может участвовать в транзакции, управляемой для родительского потока.
Это означает, что транзакция, используемая вашим @Async
метод никогда не совпадает с транзакцией, управляемой тестом, которая автоматически откатывается Spring TestContext Framework.
Таким образом, единственно возможное решение вашей проблемы - вручную отменить изменения в базе данных. Вы можете сделать это используя JdbcTestUtils
выполнить сценарий SQL программно в @AfterTransaction
метод, или вы можете альтернативно настроить сценарий SQL для выполнения декларативно через Spring @Sql
аннотация (с использованием фазы после выполнения). Для получения дополнительной информации см. Как выполнить @Sql перед методом @Before.
С Уважением,
Сэм (автор Spring TestContext Framework)