Какие знаешь случаи, когда Transactional метод может не сработать в Spring?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Когда @Transactional не сработает в Spring
@Transactional — это мощный инструмент для управления транзакциями, но есть множество "подводных камней", которые приводят к тому, что транзакция не активируется. Это одна из самых частых ошибок в Spring приложениях.
1. Вызов через self-reference (внутри одного класса)
Это главная причина проблем. Spring создаёт прокси над бином, но если вызывать метод через this, прокси не применяется:
@Service
public class UserService {
@Transactional
public void createUser(String name) {
saveUser(name);
}
@Transactional
private void saveUser(String name) {
// Эта транзакция не сработает!
// Потому что createUser вызвал её через this
}
}
Решение: извлечь в отдельный сервис или использовать ApplicationContext:
@Service
public class UserService {
@Autowired
private ApplicationContext context;
@Transactional
public void createUser(String name) {
UserService proxy = context.getBean(UserService.class);
proxy.saveUser(name); // Теперь работает через прокси
}
@Transactional
public void saveUser(String name) {
// Теперь сработает
}
}
2. Метод вызывается из другого треда
Транзакция привязана к текущему потоку (ThreadLocal). Если запустить async операцию, транзакция не будет доступна в новом потоке:
@Service
public class UserService {
@Transactional
public void createUser(String name) {
new Thread(() -> {
saveToDatabase(name); // Нет транзакции в этом потоке!
}).start();
}
}
Решение: использовать @Async с правильной конфигурацией:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
return new SimpleAsyncTaskExecutor();
}
}
@Service
public class UserService {
@Transactional
@Async
public void createUserAsync(String name) {
saveToDatabase(name); // Теперь есть транзакция
}
}
3. Метод не public
Spring создаёт прокси только для public методов. Private, protected и package-private методы не перехватываются:
@Service
public class UserService {
@Transactional
private void saveUser(String name) {
// Транзакция НЕ активируется!
database.insert(name);
}
@Transactional
protected void updateUser(String name) {
// Транзакция тоже не сработает
}
}
4. Класс не является Spring бином
Если класс не управляется Spring контейнером (нет @Service, @Component и т.д.), прокси не будет создан:
public class UserService { // Ошибка! Нет аннотации
@Transactional
public void createUser(String name) {
// Транзакция не сработает
}
}
// Правильно:
@Service
public class UserService {
@Transactional
public void createUser(String name) {}
}
5. Использование CGLib вместо JDK прокси
Если класс не реализует интерфейс, Spring использует CGLib прокси, который может не перехватить вызовы:
// CGLib прокси может быть нестабильным
@Service
public class UserService { // Класс, а не интерфейс
@Transactional
public void createUser(String name) {}
}
// Лучше:
public interface IUserService {
void createUser(String name);
}
@Service
public class UserService implements IUserService {
@Transactional
public void createUser(String name) {}
}
6. Неправильная конфигурация TransactionManager
Если TransactionManager не настроен или настроен неправильно, транзакции просто не будут работать:
@Configuration
public class DataSourceConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
7. Exception не вызывает rollback
По умолчанию @Transactional откатывает только RuntimeException. Если выбросить checked exception, коммит произойдёт:
@Transactional
public void createUser() throws IOException {
saveUser();
throw new IOException(); // Не откатит транзакцию!
}
// Правильно:
@Transactional(rollbackFor = Exception.class)
public void createUser() throws IOException {
saveUser();
throw new IOException(); // Теперь откатит
}
8. Использование @Transactional с readOnly=true
Если установить readOnly=true, попытка написать в БД может быть проигнорирована или вызвать ошибку:
@Transactional(readOnly = true)
public void createUser(String name) {
database.insert(name); // Может быть проигнорировано!
}
Чек-лист перед использованием @Transactional
- ✅ Метод public и находится в Spring бине
- ✅ Вызов происходит через прокси (не через this)
- ✅ TransactionManager правильно настроен
- ✅ Exception правильно настроен (RuntimeException или rollbackFor)
- ✅ Вызов в одном потоке (или @Async настроена)
- ✅ БД поддерживает транзакции
Понимание этих тонкостей критично для надежной работы приложения.