Сколько транзакций создается при вызове метода с propagation REQUIRED, который вызывает метод с propagation REQUIRES_NEW в Spring
Что проверяют
Понимаете ли вы, как Spring на самом деле управляет транзакциями: через прокси, с приостановкой внешней транзакции и отдельным соединением для внутренней. Правильная цифра здесь только половина ответа. Следом спрашивают, что будет, если внутренний метод вызвать из того же класса, и что откатится при исключении.
Как отвечать
Если внутренний метод вызывается через Spring-прокси, то есть лежит в другом бине, физических транзакций будет две:
- внешняя,
REQUIRED: открывается новая или, если транзакция уже есть, метод присоединяется к ней; - внутренняя,
REQUIRES_NEW: менеджер транзакций приостанавливает внешнюю (suspend), берёт из пула второе соединение и начинает независимую транзакцию. На выходе из метода она коммитится или откатывается, после чего внешняя возобновляется.
Следствия, которые стоит проговорить:
- коммит внутренней не зависит от судьбы внешней. Типичное применение: запись в журнал аудита, которая должна сохраниться, даже если основная операция откатится;
- непойманное исключение из внутреннего метода долетит до внешнего и откатит и его. Поймали в
try/catch: внешняя продолжит работу; - по умолчанию откат идёт только на
RuntimeExceptionиError, для проверяемых исключений нуженrollbackFor; - пока работает внутренняя, внешняя держит своё соединение. Под нагрузкой это выбирает весь пул HikariCP (по умолчанию 10 соединений) и вешает сервис.
Для сравнения назовите NESTED: там физическая транзакция одна, а внутренний метод работает через savepoint и откатывается только до него.
Где спотыкаются
Не упоминают прокси. При вызове this.inner() из того же класса аннотация на inner не срабатывает (если не включён режим AspectJ), REQUIRES_NEW игнорируется, и всё идёт в одной транзакции.
Думают, что внешняя транзакция видит незакоммиченные изменения внутренней или наоборот. Это два разных соединения, видимость определяется уровнем изоляции, обычно READ COMMITTED.
Забывают про блокировки. Внешняя транзакция обновила строку, внутренняя пытается изменить ту же строку и ждёт блокировку, которую держит приостановленная внешняя. База не распознаёт это как дедлок, поток просто висит до таймаута.