Ответ
Аннотация @Transactional в Spring декларативно управляет транзакциями, избавляя от необходимости писать шаблонный код для их начала, коммита или отката.
Ключевые параметры:
| Параметр | Описание | Пример значения |
|---|---|---|
propagation |
Поведение транзакции относительно существующей. | Propagation.REQUIRED (по умолчанию) — использует текущую или создаёт новую. REQUIRES_NEW — всегда создаёт новую. |
isolation |
Уровень изоляции транзакции. | Isolation.READ_COMMITTED (чаще всего) — читаются только закоммиченные данные. |
timeout |
Таймаут транзакции в секундах. | 30 |
readOnly |
Оптимизация для операций только чтения. | true |
rollbackFor / noRollbackFor |
Классы исключений для отката/игнорирования отката. | rollbackFor = Exception.class |
Пример использования:
@Service
public class TransferService {
@Autowired
private AccountRepository accountRepository;
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
rollbackFor = {InsufficientFundsException.class, DataAccessException.class}
)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepository.findById(fromId).orElseThrow();
Account to = accountRepository.findById(toId).orElseThrow();
from.debit(amount); // Списание
to.credit(amount); // Зачисление
accountRepository.save(from);
accountRepository.save(to);
// При любом исключении из указанных в rollbackFor произойдёт откат обоих save
}
}
Важные ограничения:
- Работает только на public-методах.
- Самовызов внутри класса (вызов одного
@Transactional-метода из другого метода того же класса) не работает из-за ограничений AOP-проксирования. Для этого нужно использоватьself-injectionилиAspectJmode. - По умолчанию откат происходит только для
RuntimeExceptionиError.
Ответ 18+ 🔞
А, ну это же про эту вашу магию с транзакциями в Спринге! Смотри, чтобы не писать каждый раз этот шаблонный код — открыл, закоммитил, откатил, если пиздец случился — придумали аннотацию @Transactional. Вешаешь её на метод, и фреймворк сам, как нянька, оборачивает всё это дело в транзакцию. Красота, блядь!
Но просто так повесить — это для лохов. Настоящие мужики настраивают параметры, там же их, блядь, овердохуища! Главные, чтобы не облажаться:
propagation— это как твоя транзакция будет вести себя, если вокруг уже кто-то другой транзакцию начал. По умолчаниюREQUIRED— «использую чужую, если есть, а нет — свою заведу». А вотREQUIRES_NEW— это по-пацански: «похуй на ваши, я свою, новую, создам!».isolation— уровень изоляции, чтобы не начитаться хуйни из соседних, недоделанных транзакций. Чаще всегоREAD_COMMITTEDставят — читаешь только то, что уже нормально закоммитили, а не промежуточный пиздец.timeout— чтоб транзакция не висела, как манда с ушами, вечность. Задаёшь в секундах, и если дольше — ей пиздец, откат.readOnly— если ты только читаешь из базы (не пишешь), ставьtrue. Это как сказать базе: «Расслабься, браток, я только посмотреть».rollbackFor/noRollbackFor— вот тут самое важное, ёпта! По умолчанию откат будет только если вылетитRuntimeExceptionилиError. А если твой метод выкинет обычное проверяемое исключение (типаException) — транзакция закоммитится, и ты останешься с хуем в руках. Поэтому если хочешь отката от своего кастомного исключенияInsufficientFundsException— явно прописывай его вrollbackFor!
Вот смотри, как это в коде выглядит, когда всё по-взрослому:
@Service
public class TransferService {
@Autowired
private AccountRepository accountRepository;
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
rollbackFor = {InsufficientFundsException.class, DataAccessException.class}
)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepository.findById(fromId).orElseThrow();
Account to = accountRepository.findById(toId).orElseThrow();
from.debit(amount); // Списание
to.credit(amount); // Зачисление
accountRepository.save(from);
accountRepository.save(to);
// Если тут вылетит InsufficientFundsException или что-то из DataAccessException — всё, блядь, откат! Ни одно сохранение не пройдёт.
}
}
А теперь, внимание, ебаный подвох, на котором все горят!
- Эта аннотация работает только на
public-методах! На приватные или протектед её вешать — бесполезное занятие, как дохлой кобыле гиппопотам. - Самовызов не катит! Если ты внутри одного класса вызовешь
@Transactional-метод из другого метода этого же класса — магия не сработает, прокси-обёртка не вступит в игру. Получишь пиздатый баг, а транзакции не будет. Решения: либо self-injection (заинжектить самого себя, да, звучит как бред, но работает), либо переходить на режимAspectJ. - Запомни раз и навсегда: по умолчанию откат — только для
RuntimeExceptionиError. Если не указалrollbackForдля своегоException— готовься к неожиданностям, чувак. Волнение ебать, а терпения — ноль.
Вот и вся философия. Разобрался? Теперь иди и делай правильно, а не как полупидор!