Откат транзакции 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 Объект оставлен в БД. Мне нужно сделать тестовую транзакцию, чтобы откатить все изменения в конце.

Теперь, что я пробовал до сих пор:

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

1 ответ

Решение

У кого-нибудь есть идеи, как принудительно откатить изменения БД, сделанные асинхронным методом?

К сожалению, это невозможно.

Spring управляет состоянием транзакции через ThreadLocal переменные. Транзакция началась в другом потоке (например, созданном для вашего @Async поэтому вызов метода) не может участвовать в транзакции, управляемой для родительского потока.

Это означает, что транзакция, используемая вашим @Async метод никогда не совпадает с транзакцией, управляемой тестом, которая автоматически откатывается Spring TestContext Framework.

Таким образом, единственно возможное решение вашей проблемы - вручную отменить изменения в базе данных. Вы можете сделать это используя JdbcTestUtils выполнить сценарий SQL программно в @AfterTransaction метод, или вы можете альтернативно настроить сценарий SQL для выполнения декларативно через Spring @Sql аннотация (с использованием фазы после выполнения). Для получения дополнительной информации см. Как выполнить @Sql перед методом @Before.

С Уважением,

Сэм (автор Spring TestContext Framework)

Другие вопросы по тегам