Как работает Transactional в тестах и что такое автоматический rollback?
Когда тестовый метод или класс аннотирован @Transactional, Spring Test автоматически оборачивает каждый тест в транзакцию и откатывает её после завершения. Это гарантирует, что тестовые данные не загрязняют базу для последующих тестов.
Аналогия из жизни: это как черновик в текстовом редакторе — вы пишете, проверяете результат, а потом нажимаете «отменить» (Ctrl+Z), и документ возвращается в исходное состояние.
Пример
@SpringBootTest
@Transactional
class UserServiceTransactionalTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
void shouldCreateUserAndRollback() {
userService.create(new UserDto("Иван", "ivan@example.com"));
assertEquals(1, userRepository.count());
// После теста транзакция откатится — данные не сохранятся
}
@Test
void shouldHaveEmptyDatabase() {
// Благодаря rollback предыдущего теста, база чиста
assertEquals(0, userRepository.count());
}
}
Отключение отката
Пример
@Test
@Commit // или @Rollback(false)
void shouldPersistData() {
userService.create(new UserDto("Иван", "ivan@example.com"));
// Данные останутся в базе после теста
}
Важные нюансы
-
@DataJpaTestавтоматически применяет@Transactional— дополнительно аннотировать не нужно. -
@Transactionalв тестах не работает сRANDOM_PORTиDEFINED_PORT, потому что HTTP-запросы выполняются в отдельном потоке, а транзакция привязана к потоку теста:
Пример
// Rollback НЕ сработает — запросы идут через реальный HTTP
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Transactional
class WontRollbackTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void dataWillPersist() {
restTemplate.postForEntity("/api/users",
new UserDto("Иван"), UserDto.class);
// Данные останутся в базе!
}
}
- Для очистки данных при интеграционных тестах с реальным сервером используйте
@SqlсAFTER_TEST_METHODили@DirtiesContext:
Пример
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTest {
@Test
@Sql(scripts = "/cleanup.sql",
executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
void integrationTest() {
// ...
}
}
На собеседовании: ключевой момент — объяснить, почему rollback не работает с
RANDOM_PORT(разные потоки). Частая ошибка — не знать про эту особенность и ожидать откат при использованииTestRestTemplate.