офлайн
офлайн
ГлавнаяSpring Data и транзакции.md
---
tags: [spring, spring-data, transactions]
created: 2026-11-05
updated: 
---

Spring Data и транзакции

Содержание

› @Transactional

Аннотация @Transactional — пример использования Spring AOP (Aspect-Oriented Programming).

  1. Когда ты вешаешь @Transactional на метод (или класс), Spring не отдает вызывающему коду оригинальный объект твоего сервиса. Он создает Proxy (Обертку).
  2. Когда кто-то вызывает твой метод, вызов попадает в Proxy.
  3. Логика Proxy (Transaction Interceptor):
    • Берет Connection из пула (HikariCP).
    • Отключает автокоммит: connection.setAutoCommit(false).
    • Вызывает твой реальный метод бизнес-логики.
    • Если метод завершился успешно -> connection.commit().
    • Если вылетело исключение -> connection.rollback().
    • Возвращает Connection обратно в пул.

Важные ограничения (Частые вопросы на собесе):

  • Self-invocation (Самовызов): Если метод A() (без транзакции) вызывает метод B() (с @Transactional) внутри одного и того же класса, транзакция НЕ откроется. Вызов идет через this, минуя Proxy-объект.
  • Модификатор доступа: @Transactional работает только на public методах (при использовании стандартного Spring AOP на базе динамических прокси).
  • Checked Exceptions: По умолчанию Spring делает rollback только при RuntimeException (Unchecked) и Error. Если вылетит проверяемое исключение (например, IOException), транзакция закоммитится! Чтобы изменить это, нужно писать: @Transactional(rollbackFor = Exception.class).

› Propagation

Что делать Spring’у, если метод с @Transactional вызывает другой метод, который тоже помечен @Transactional? За это отвечает параметр propagation.

Существует 7 уровней пропагации:

  1. REQUIRED (По умолчанию):
    • Если текущей транзакции нет — создает новую.
    • Если транзакция уже есть — присоединяется к ней (выполняется в рамках того же физического Connection).
    • Нюанс: Если вложенный метод выбросит исключение, вся общая транзакция пометится как rollback-only. Даже если внешний метод поймает исключение в try-catch, при попытке коммита Spring выбросит UnexpectedRollbackException.
  2. REQUIRES_NEW:
    • Всегда создает новую транзакцию (берет новый Connection из пула).
    • Если текущая транзакция уже существует, она приостанавливается (suspend), пока не завершится новая.
    • Сценарий: Логирование действий пользователя в БД. Даже если основной бизнес-процесс упал с ошибкой (rollback), запись в лог должна сохраниться (commit).
  3. SUPPORTS:
    • Если транзакция есть — выполняется в ней.
    • Если транзакции нет — выполняется без транзакции (в режиме автокоммита).
  4. NOT_SUPPORTED:
    • Выполняется без транзакции.
    • Если транзакция есть, она приостанавливается на время выполнения метода.
  5. MANDATORY:
    • Обязательно требует наличия транзакции. Если её нет — выбрасывает исключение IllegalTransactionStateException.
  6. NEVER:
    • Требует, чтобы транзакции не было. Если она есть — выбрасывает исключение.
  7. NESTED (Вложенная):
    • Если транзакции нет — работает как REQUIRED (создает новую).
    • Если транзакция есть — создает Savepoint (Точку сохранения) внутри текущей транзакции. Если вложенный метод падает, откатывается только до этого Savepoint’а, а внешняя транзакция может продолжить работу и успешно закоммититься. (Требует поддержки Savepoints со стороны JDBC драйвера).

› Isolation (Уровни изоляции в Spring)

Параметр isolation в @Transactional позволяет задать уровень изоляции для конкретной транзакции, переопределяя дефолтные настройки БД.

  • @Transactional(isolation = Isolation.READ_COMMITTED)
  • @Transactional(isolation = Isolation.REPEATABLE_READ)
  • @Transactional(isolation = Isolation.SERIALIZABLE)
  • Isolation.DEFAULT — использует уровень изоляции, настроенный по умолчанию в самой СУБД (например, READ COMMITTED для PostgreSQL).

› Spring Data Repositories

Spring Data JPA предоставляет интерфейсы (CrudRepository, JpaRepository), которые автоматически реализуются фреймворком.

  • Транзакционность по умолчанию: Все методы в SimpleJpaRepository (дефолтная реализация) уже помечены аннотацией @Transactional.
    • Методы чтения (findById, findAll) помечены @Transactional(readOnly = true).
    • Методы записи (save, delete) помечены обычным @Transactional.
  • Поэтому, если ты вызываешь repository.save() из контроллера (без сервиса), транзакция всё равно откроется и закроется на уровне самого репозитория.

Ссылаются сюда 1

main48 заметок481 слов3 мин чтения