Сколько транзакций создается при вызове методов с разными propagation в @Transactional
Что проверяют
Понимаете ли вы, что propagation описывает не «сколько транзакций», а что делать с уже открытой транзакцией при входе в метод. Обычно дают задачу: метод a() с REQUIRED вызывает b() другого бина с иным режимом, в b() летит исключение, что откатится. Следом спрашивают про вызов внутри одного класса.
Как отвечать
Режимов семь, все в перечислении Propagation. Считайте физические транзакции для случая, когда внешняя уже открыта:
- REQUIRED (по умолчанию): присоединяется к текущей, итого одна. Если текущей нет, открывает новую;
- REQUIRES_NEW: приостанавливает внешнюю и открывает свою, итого две, у каждой свой коммит и откат. Вторая берёт отдельное соединение из пула;
- NESTED: транзакция одна, но внутри ставится savepoint, и откат
b()возвращает только к нему.DataSourceTransactionManagerподдерживает это сразу, уJpaTransactionManagerфлагnestedTransactionAllowedпо умолчанию выключен, с JTA режим не работает; - SUPPORTS: есть транзакция, работает в ней, нет, выполняется без неё;
- MANDATORY: без внешней транзакции бросает
IllegalTransactionStateException; - NOT_SUPPORTED: приостанавливает внешнюю и выполняется без транзакции;
- NEVER: при активной транзакции бросает исключение.
Главный сценарий: b() с REQUIRED бросил RuntimeException, a() его поймал и продолжил работу. Транзакция общая и уже помечена rollback-only, поэтому на коммите a() получит UnexpectedRollbackException. С REQUIRES_NEW откатится только b(), а a() закоммитится.
Где спотыкаются
Забывают про прокси. Вызов this.b() из того же бина идёт мимо прокси, и аннотация на b() игнорируется целиком: никакого REQUIRES_NEW не будет.
Злоупотребляют REQUIRES_NEW. Под нагрузкой каждый поток держит два соединения, и при пуле HikariCP по умолчанию (10 соединений) потоки начинают ждать друг друга до таймаута.
Путают NESTED и REQUIRES_NEW, считая, что вложенная коммитится независимо. Нет: её изменения фиксируются только вместе с внешней транзакцией.