Как заставить методы создавать транзакции в Java через аспекты и проксирование
Что проверяют
Понимаете ли вы, как @Transactional работает изнутри: кто создаёт прокси, где открывается транзакция и почему аннотация иногда молча не срабатывает. Следом почти всегда спрашивают про вызов транзакционного метода из того же класса.
Как отвечать
Механика в Spring:
- прокси: для бина с
@Transactionalконтейнер создаёт прокси. Если бин реализует интерфейс, возможен JDK dynamic proxy, иначе CGLIB-подкласс. Spring Boot по умолчанию использует CGLIB (spring.aop.proxy-target-class=true); - перехватчик: вызов идёт через
TransactionInterceptor, который по атрибутам аннотации проситPlatformTransactionManagerначать транзакцию, вызывает метод и делает commit или rollback; - привязка к потоку: соединение кладётся в
ThreadLocalчерезTransactionSynchronizationManager, поэтому все репозитории внутри метода работают в одной транзакции; - правила по умолчанию: propagation
REQUIRED, откат только наRuntimeExceptionиError. Для checked-исключений нуженrollbackFor; - включение: в Spring Boot это делает автоконфигурация, в чистом Spring нужна
@EnableTransactionManagement.
Если просят написать своё, покажите совет @Around с ProceedingJoinPoint, где вызов pjp.proceed() обёрнут в TransactionTemplate.execute(...). Совет @Before транзакцию не создаёт: он не знает, чем закончился метод, и не может сделать commit или rollback. Альтернатива прокси: AspectJ weaving через @EnableTransactionManagement(mode = AdviceMode.ASPECTJ), тогда транзакции работают и при внутренних вызовах.
Где спотыкаются
Вызывают this.save() из другого метода того же бина. Вызов идёт мимо прокси, и транзакции нет. Лечится выносом метода в отдельный бин, TransactionTemplate или внедрением самого себя через @Lazy.
Вешают аннотацию на private или final метод. CGLIB не может его переопределить, и аннотация игнорируется без ошибки. Начиная со Spring 6 для прокси на классах работают также protected и package-private методы.
Ожидают, что транзакция продолжится в @Async-методе или внутри CompletableFuture. Транзакция привязана к потоку, и в новом потоке её уже нет.