Что происходит при RuntimeException в методе с транзакцией
Что проверяют
Знаете ли вы правила отката в Spring и понимаете ли, что откатывает не сам метод, а прокси вокруг него. Отсюда растут почти все реальные баги с транзакциями, и спрашивают вопрос именно ради них.
Как отвечать
По умолчанию @Transactional откатывает транзакцию на RuntimeException и Error и не откатывает на проверяемых исключениях. Меняется это явно: rollbackFor и noRollbackFor.
Механика важнее правила. Аннотация работает через прокси: он открывает транзакцию до вызова метода, а на выходе либо фиксирует её, либо, увидев непроверяемое исключение, откатывает. Отсюда два условия: исключение должно выйти за границу метода, и вызов должен пройти через прокси.
Если исключение надо обработать на месте, но транзакцию всё равно откатить, пометьте её сами:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();Стоит упомянуть распространение. Вложенный метод с настройкой по умолчанию работает в той же транзакции, поэтому его откат утаскивает всю; отдельная транзакция получается только при REQUIRES_NEW. И про JPA: после отката контекст персистентности считать пригодным нельзя.
Где спотыкаются
Ловят исключение внутри транзакционного метода и ждут отката. Поймали и не пробросили, значит прокси видит нормальный выход и фиксирует изменения, включая половину работы, сделанную до ошибки.
Вызывают транзакционный метод из соседнего метода того же бина. Внутренний вызов идёт мимо прокси, аннотация не действует, транзакции нет вовсе. Лечится выносом в отдельный бин. По той же причине не работает аннотация на приватном методе.
Ловят исключение уровнем выше, уже после того как вложенный вызов пометил общую транзакцию на откат, и удивляются исключению при фиксации. Помеченную транзакцию продолжать нельзя, её остаётся только откатить.