Как открыть транзакцию условно, например только при определённом параметре
Что проверяют
Понимаете ли вы, как Spring управляет транзакциями через прокси, и знаете ли программный способ на случай, когда аннотации мало. Следом почти всегда спросят, почему не сработает вызов @Transactional-метода из соседнего метода того же класса.
Как отвечать
Аннотация @Transactional декларативна и не умеет включаться по значению аргумента. Поэтому условную транзакцию делают так:
TransactionTemplate: самый прямой вариант.if (needTx) { txTemplate.executeWithoutResult(s -> doWork()); } else { doWork(); }. Шаблон внедряется как бин, а не создаётся черезnewна каждый вызов;- два метода в разных бинах: один с
@Transactional, другой без, выбор делает вызывающий код. Именно в разных бинах, потому что вызов черезthisобходит прокси и транзакция не откроется; - распространение:
Propagation.REQUIRES_NEWилиNOT_SUPPORTEDна отдельных методах, когда условие звучит как «эту часть выполнить вне общей транзакции»; - низкоуровнево:
PlatformTransactionManager.getTransaction(), затемcommit()илиrollback(). Работает, но откат легко забыть, поэтому шаблон предпочтительнее.
Проговорите, что значит «без транзакции» на практике. Методы SimpleJpaRepository уже помечены @Transactional, поэтому каждый вызов репозитория пройдёт в своей короткой транзакции. Несколько изменений перестают быть атомарными, а обращение к ленивой коллекции вне сессии бросит LazyInitializationException.
Напомните про откат: по умолчанию Spring откатывает только на RuntimeException и Error, для проверяемых исключений нужен rollbackFor. В шаблоне откат вызывает status.setRollbackOnly() или непроверяемое исключение из колбэка.
Где спотыкаются
Пишут условие внутри @Transactional-метода. Транзакция к этому моменту уже открыта, условие решает лишь, что делать внутри неё.
Создают сервис через new и ждут, что @Autowired и транзакции заработают. Без контейнера нет ни внедрения, ни прокси, будет NullPointerException.
Выносят транзакционную ветку в метод того же класса с @Transactional и вызывают его напрямую. Прокси такой вызов не видит.