Сначала ответь вслух
Скажи ответ вслух хотя бы пару минут, потом открой эталон и сравни. Эталон открыт всем и не требует входа.
Показать ответ
Эталонный ответ
Spring оборачивает бин с @Transactional в прокси (JDK-прокси по интерфейсу или наследник через CGLIB). Внешний вызов приходит в прокси, тот открывает транзакцию и вызывает метод настоящего объекта.
Внутри настоящего объекта this указывает на него самого, а не на прокси. Вызов this.save() идёт напрямую, транзакция не открывается.
@Service
class OrderService {
public void importAll(List<Order> orders) {
orders.forEach(this::save); // мимо прокси: транзакции нет
}
@Transactional
public void save(Order order) { /* ... */ }
}Решения: вынести метод в отдельный бин, использовать TransactionTemplate для явной транзакции или подключить AspectJ-вплетение вместо прокси.
Как у тебя получилось?
Уточняющие вопросы
Так обычно копают глубже. Раскрой строку, когда ответишь вслух.
Уточнение 1-го уровня: Сработает ли @Transactional на private-методе?
Ответ уровня Middle
Нет: прокси не может переопределить private-метод. В Spring 6 прокси через CGLIB работают и с protected и пакетными методами, но не с private.
Уточнение 2-го уровня: Откатится ли транзакция при checked-исключении?
Ответ уровня Middle
По умолчанию нет: откат только для RuntimeException и Error. Для checked-исключений нужно rollbackFor.
Уточнение 3-го уровня: А если исключение поймать внутри метода?
Ответ уровня Senior
Прокси исключения не увидит и закоммитит транзакцию. Чтобы откатить, исключение пробрасывают дальше или вызывают setRollbackOnly().
Типичные ошибки
- Вешать @Transactional на private-методы.
- Ловить исключение внутри транзакционного метода и ждать отката.
Первоисточники
Быстрые задания по теме
Для темы «Прокси и @Transactional» быстрых заданий пока нет
Можно проверить всю зону «Spring» тестом на 10 минут.
Проверить себя за 10 минут