В каких случаях аннотация Transactional не сработает
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Случаи, когда @Transactional не сработает
Это один из самых коварных багов, с которыми я сталкивался в Spring приложениях. @Transactional кажется простой аннотацией, но есть множество ситуаций, когда она не работает. Расскажу о практических примерах и решениях.
1. Внутренний вызов метода (Self-invocation)
Самая частая проблема. @Transactional работает через AOP (создание прокси), но внутренний вызов обходит прокси:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void createUser(String name) { // Нет @Transactional
this.saveUser(name); // Обходит прокси!
}
@Transactional
public void saveUser(String name) {
userRepository.save(new User(name));
throw new RuntimeException("Ошибка!");
// Не откатится — @Transactional не сработала
}
}
Почему происходит: когда вызываешь this.saveUser(), Spring прокси не вмешивается. Решение — внедрить Self:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserService self; // Внедрить сам сервис
public void createUser(String name) {
self.saveUser(name); // Теперь через прокси
}
@Transactional
public void saveUser(String name) {
userRepository.save(new User(name));
throw new RuntimeException("Откатится корректно");
}
}
Или просто вынести в отдельный сервис:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserCreationService creationService;
public void createUser(String name) {
creationService.saveUser(name); // Разные классы = новый прокси
}
}
@Service
public class UserCreationService {
@Autowired
private UserRepository userRepository;
@Transactional
public void saveUser(String name) {
userRepository.save(new User(name));
}
}
2. Вызов из методов без @Transactional контекста
Если основной метод не имеет @Transactional, то и вложенные @Transactional методы могут работать странно:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private PaymentService paymentService;
public void processOrder(Order order) { // Нет @Transactional!
orderRepository.save(order);
paymentService.processPayment(order); // @Transactional здесь
}
}
@Service
public class PaymentService {
@Transactional
public void processPayment(Order order) {
// Работает, но без внешней транзакции
// Если processOrder падёт после этого, платёж останется
}
}
Решение — добавить @Transactional к основному методу:
@Transactional
public void processOrder(Order order) {
orderRepository.save(order);
paymentService.processPayment(order); // Теперь в одной транзакции
}
3. Приватные методы
@Transactional на приватных методах не работает:
@Service
public class DataService {
@Transactional
private void internalSave() { // ❌ Private!
// @Transactional не сработает
}
public void publicSave() {
internalSave(); // Нет транзакции
}
}
Причина: Spring создаёт прокси на основе public интерфейса. Решение — сделать public или protected:
@Transactional
protected void internalSave() { // ✅ Protected
// Теперь сработает
}
4. Контроль исключений (Try-Catch)
Если поймаешь исключение, транзакция не откатится:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(String name) {
userRepository.save(new User(name));
try {
riskyOperation();
} catch (Exception e) {
// ❌ Исключение поймано — откат не произойдёт
log.error("Ошибка", e);
}
}
}
Решение — либо переподнять исключение, либо использовать rollbackFor:
@Transactional(rollbackFor = Exception.class)
public void createUser(String name) {
userRepository.save(new User(name));
try {
riskyOperation();
} catch (Exception e) {
throw new RuntimeException(e); // Переподнять
}
}
5. Проверяемые исключения (Checked Exceptions)
По умолчанию @Transactional откатывает только runtime исключения:
@Transactional // ❌ IOException не откатит
public void saveToFile(User user) throws IOException {
userRepository.save(user);
fileWriter.write(user.toString()); // IOException — транзакция не откатится
}
Решение — явно указать rollbackFor:
@Transactional(rollbackFor = IOException.class)
public void saveToFile(User user) throws IOException {
userRepository.save(user);
fileWriter.write(user.toString()); // Теперь откатится
}
6. Неправильный isolation level
Если задаёшь неподходящий уровень изоляции, транзакция может не работать как ожидается:
@Service
public class ConcurrentService {
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void unsafeUpdate() {
// Очень низкий уровень изоляции = dirty reads возможны
}
}
Стандартно используй READ_COMMITTED или REPEATABLE_READ:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void safeUpdate() {
// Защита от dirty reads
}
7. Отключённая поддержка транзакций
Если DataSource не настроен или @EnableTransactionManagement отсутствует:
// ❌ В конфигурации забыли
@Configuration
public class AppConfig {
// Нет @EnableTransactionManagement
}
Решение:
@Configuration
@EnableTransactionManagement // ✅ Обязательно
public class AppConfig {
// ...
}
8. Вызов из другого потока
@Transactional работает с ThreadLocal контекстом. Если вызвать асинхронный метод без настройки, транзакция теряется:
@Service
public class EmailService {
@Async // Новый поток
@Transactional // ❌ Работать не будет корректно
public void sendEmail(String email) {
// Потеря транзакционного контекста
}
}
Решение — использовать правильный порядок аннотаций и конфигурацию:
@Transactional
@Async
public void sendEmail(String email) {
// @Transactional должна быть внешней
}
Чеклист отладки @Transactional
- Проверить, что метод public (не private)
- Убедиться, что это не self-invocation
- Проверить наличие @EnableTransactionManagement в конфигурации
- Убедиться, что не ловишь исключение
- Проверить isolation level
- Посмотреть логи — Spring должен вывести информацию о транзакции
- Включить debug логирование Spring:
logging.level.org.springframework.transaction=DEBUG
logging.level.org.hibernate.engine.transaction.internal.TransactionImpl=DEBUG
Стоит потратить время на понимание этих граничных случаев — это спасит множество часов отладки в боевых условиях.