Ответ
Аннотация @Transactional декларативно управляет границами транзакций в Spring. Она применяется на уровне метода или класса.
Базовый пример:
@Service
public class TransferService {
@Autowired
private AccountRepository accountRepository;
@Transactional // Весь метод выполняется в одной транзакции
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
Account fromAccount = accountRepository.findById(fromId).orElseThrow();
Account toAccount = accountRepository.findById(toId).orElseThrow();
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException(); // RuntimeException вызовет rollback
}
fromAccount.debit(amount);
toAccount.credit(amount);
// Сохранение не требуется явно, если используется контекст persistence с flush-mode AUTO (по умолчанию в @Transactional)
}
}
Ключевые атрибуты для настройки:
propagation: Определяет поведение транзакции относительно существующего контекста (например,REQUIRED,REQUIRES_NEW).isolation: Уровень изоляции (например,READ_COMMITTED,SERIALIZABLE).rollbackFor/noRollbackFor: Какие исключения вызывают или не вызывают откат.readOnly: Флаг только для чтения (оптимизация дляtrue).timeout: Таймаут транзакции в секундах.
Важно: @Transactional работает через AOP-прокси, поэтому вызовы методов внутри одного класса не будут перехвачены прокси. По умолчанию откат происходит только при непроверяемых исключениях (RuntimeException и его наследниках).
Ответ 18+ 🔞
Слушай, а вот эта ваша аннотация @Transactional — это ж просто магия какая-то, ей-богу. Накропал её над методом, и всё — Spring сам, как нянька, оборачивает твой код в транзакцию. Не нужно руками открывать, коммитить, откатывать — красота, пиздец.
Смотри, как это просто выглядит:
@Service
public class TransferService {
@Autowired
private AccountRepository accountRepository;
@Transactional // Вот этот волшебный пиздёж. Весь метод — одна транзакция.
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
Account fromAccount = accountRepository.findById(fromId).orElseThrow();
Account toAccount = accountRepository.findById(toId).orElseThrow();
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException(); // Кинул RuntimeException — и всё, блядь, откат!
}
fromAccount.debit(amount);
toAccount.credit(amount);
// Сохранять вручную не надо, если всё настроено. Живёшь как в сказке.
}
}
Но это, конечно, база. А если копнуть, там же овердохуища настроек, на любой вкус. Главные ручки, за которые можно крутить:
propagation: Это про то, как твоя транзакция будет вести себя, если её уже кто-то начал. Например,REQUIRED— «ой, есть транзакция? Отлично, буду в ней работать». АREQUIRES_NEW— это «пошёл нахуй, я свою заведу, отдельную». Очень важная штука, если не хочешь неожиданных сюрпризов.isolation: Уровень изоляции, ну ты понял.READ_COMMITTEDобычно хватает, но если у тебя там какие-то гонки ебаные, можно иSERIALIZABLEвкрутить — будет медленнее, но надёжнее, как швейцарские часы, блядь.rollbackFor/noRollbackFor: А вот это ключевое! По умолчанию откат — только на непроверяемых исключениях (RuntimeExceptionи дети). А если твой метод кидает какое-нибудь своёCheckedException, и ты хочешь откат — указывай его вrollbackFor. И наоборот, если какой-нибудьNullPointerExceptionпо твоей бизнес-логике не должен всё ломать — пиши вnoRollbackFor. Иначе — пиздец и неразбериха.readOnly: Выставляешь вtrue, если метод только читает. Это как флажок для базы и фреймворка — они могут немного схитрить и оптимизироваться.timeout: Сколько секунд транзакция может болтаться. Если дольше — кидается исключение и откат. Чтобы какой-нибудь долбоёбский запрос не висел до скончания времён.
И главный подводный камень, ёпта: Вся эта магия работает через AOP-прокси. Это значит, что если ты внутри одного класса вызовешь один @Transactional-метод из другого — то второй вызов НЕ БУДЕТ обёрнут в транзакцию! Прокси-то смотрит только на вызовы извне. Так что не выёбывайся, организуй код правильно.
Короче, инструмент мощный, но с мозгом использовать надо. А то накропаешь везде @Transactional и будешь потом гадать, почему данные не сохраняются или, наоборот, откатывается то, что не должно.