На что нужно обращать внимание при использовании аннотации @Transactional в Spring?

Ответ

Ключевые аспекты использования @Transactional:

  1. Механизм проксирования (AOP): Аннотация работает через Spring AOP-прокси. Поэтому:

    • Вызов метода с @Transactional из другого метода того же класса не будет перехвачен прокси, и транзакция не начнется.
    • Метод должен иметь модификатор public.
  2. Обработка исключений и откат (Rollback): По умолчанию откат транзакции происходит только при RuntimeException и Error. Для отката при checked-исключениях (Exception) необходимо указать это явно.

    @Transactional(rollbackFor = Exception.class)
    public void saveData() throws IOException { ... }
  3. Распространение (Propagation): Определяет поведение транзакции, если она вызывается в контексте другой транзакции. Например, Propagation.REQUIRED (по умолчанию) присоединится к существующей, а Propagation.REQUIRES_NEW создаст новую, независимую транзакцию.

  4. Изоляция (Isolation): Определяет уровень изоляции транзакции (например, Isolation.READ_COMMITTED). Важно выбирать в соответствии с требованиями к согласованности данных и параллелизму.

  5. Производительность: Долгие транзакции удерживают соединения с БД и блокировки, что может стать узким местом. Следует минимизировать логику внутри транзакции и избегать операций ввода-вывода.

Ответ 18+ 🔞

А, слушай, смотри, вот эта вся ваша магия с @Transactional в Spring — она, конечно, пиздец как удобна, пока не наступишь на грабли, блядь. Тут есть нюансы, о которых в документации мелким шрифтом пишут, а потом вся команда ночами дебажит, ебать мои старые костыли.

Вот, например, первый подводный камень, классика жанра. Эта аннотация работает через прокси, которые Spring навешивает, как мартышлюшка гирлянды на ёлку. Так вот, если ты внутри одного класса вызовешь @Transactional-метод из другого метода — нихуя не сработает! Прокси-то смотрит на вызовы извне, а не внутренние. И метод должен быть public, иначе какой нахуй прокси, ты его даже не увидишь.

Дальше, про откаты. Тут народ постоянно обжигается. По дефолту транзакция откатывается только на RuntimeException и Error. А если у тебя какое-нибудь checked-исключение, IOException там — так он его проглотит, коммит сделает, и ты останешься с неконсистентными данными, в рот меня чих-пых! Надо явно указать:

@Transactional(rollbackFor = Exception.class)
public void saveData() throws IOException { ... }

Теперь про распространение, Propagation. Это, блядь, целый театр абсурда. Допустим, у тебя уже есть транзакция, и ты вызываешь метод с @Transactional. Так вот, REQUIRED (это по умолчанию) — он как родственник нахаляву: "О, я к вам присоединюсь, чо". А REQUIRES_NEW — это уже самостоятельный мужик: "Отъебись, я свою транзакцию заведу, независимую!". И вот если в родительской транзакции потом ошибка, то твоя новая — хуй с горы, она уже закоммичена и ей похуй. Красота, да?

Изоляция, Isolation — это вообще про доверие в распределённых системах, а доверия, как известно, ебать ноль. Выбираешь READ_COMMITTED — видишь только коммитнутые данные, но могут быть фантомные чтения. Выбираешь SERIALIZABLE — всё последовательно, красиво, но производительность накрывается медным тазом, потому что блокировки на всё. Тут надо думать, э бошка думай, что важнее: абсолютная точность или чтобы всё летало.

И да, про производительность — это святое. Если ты внутри транзакции, кроме работы с БД, ещё и файлы качаешь, или в сторонний API стучишься, или просто хуйню какую-то долгую считаешь — ты удерживаешь соединение с базой, а оно не резиновое. Представь, что ты в сортире сидишь, а за дверью очередь из двадцати человек. Вот так и база смотрит на твою долгую транзакцию. Минимизируй всё, что не связано с SQL, внутри неё, а то пидары налетят (операционщики) с вопросами, почему всё легло.

Короче, инструмент мощный, но если не понимать, как он устроен внутри, можно такого наворотить, что потом пол-команды волосы на жопе рвать будут.