Какие знаешь аргументы @Transactional?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Аргументы аннотации @Transactional в Spring
Аннотация @Transactional — один из ключевых инструментов Spring Framework для управления транзакциями в приложениях. Она предоставляет множество параметров для тонкой настройки поведения транзакций.
Основные аргументы @Transactional
1. propagation (Распространение транзакций)
Определяет, как транзакция должна взаимодействовать с уже существующей транзакцией.
public enum Propagation {
REQUIRED, // Использует текущую транзакцию или создаёт новую (по умолчанию)
SUPPORTS, // Использует текущую, если есть; без неё работает без транзакции
MANDATORY, // Требует существующей транзакции, иначе бросает исключение
REQUIRES_NEW, // Создаёт НОВУЮ транзакцию, приостанавливая текущую
NOT_SUPPORTED, // Выполняется БЕЗ транзакции, приостанавливая существующую
NEVER, // Требует отсутствия транзакции, иначе ошибка
NESTED // Создаёт вложенную транзакцию (savepoint)
}
@Service
public class TransferService {
// Пример 1: REQUIRED (по умолчанию)
@Transactional(propagation = Propagation.REQUIRED)
public void transferMoney(int fromId, int toId, BigDecimal amount) {
debit(fromId, amount);
credit(toId, amount);
// Если есть текущая транзакция, используется она
// Если нет - создаётся новая
}
// Пример 2: REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logTransaction(String details) {
// Создаётся НОВАЯ независимая транзакция
// Текущая транзакция приостанавливается
// Логирование может произойти даже при откате основной транзакции
}
// Пример 3: NESTED
@Transactional(propagation = Propagation.NESTED)
public void updateWithFallback(int accountId, BigDecimal amount) {
try {
updatePrimary(accountId, amount);
} catch (RuntimeException e) {
// Откатывается только NESTED транзакция, основная продолжает работу
updateFallback(accountId, amount);
}
}
}
Матрица поведения propagation:
Propagation | Текущей транзакции нет | Текущая транзакция есть
---|---|---
REQUIRED | Создаёт новую | Использует существующую
SUPPORTS | Нет транзакции | Использует существующую
MANDATORY | ОШИБКА | Использует существующую
REQUIRES_NEW | Создаёт новую | Создаёт новую (приостанавливает)
NOT_SUPPORTED | Нет транзакции | Приостанавливает (без транзакции)
NEVER | Нет транзакции | ОШИБКА
NESTED | Создаёт новую | Создаёт savepoint
2. isolation (Уровень изоляции)
Определяет уровень изоляции транзакции, защищая от SQL-феноменов.
public enum Isolation {
DEFAULT, // Уровень БД по умолчанию
READ_UNCOMMITTED, // Самый низкий, быстро, но небезопасно
READ_COMMITTED, // Стандартный уровень
REPEATABLE_READ, // Средний уровень безопасности
SERIALIZABLE // Максимальный, но медленный
}
@Service
public class AccountService {
// Стандартный уровень (READ_COMMITTED)
@Transactional(
isolation = Isolation.READ_COMMITTED
)
public void transferFunds(int fromId, int toId, BigDecimal amount) {
// Защита от Dirty Read, но возможны Non-Repeatable Read
// и Phantom Read
}
// Максимальная безопасность (но медленно)
@Transactional(
isolation = Isolation.SERIALIZABLE
)
public void criticalOperation(int accountId) {
// Полная защита от всех феноменов
// Может быть медленнее из-за блокировок
}
}
3. timeout (Таймаут транзакции)
Максимальное время выполнения транзакции в секундах. По умолчанию -1 (без таймаута).
@Service
public class PaymentService {
// Транзакция не должна длиться более 30 секунд
@Transactional(timeout = 30)
public void processPayment(String orderId) throws TimeoutException {
// Если метод выполняется дольше 30 секунд,
// транзакция откатывается и выбрасывается TransactionTimedOutException
externalPaymentService.charge(orderId);
}
// Без таймаута (может выполняться бесконечно долго)
@Transactional(timeout = -1)
public void longRunningOperation() {
// ...
}
}
4. readOnly (Только чтение)
Указывает, что транзакция только читает данные, не изменяет их. Позволяет БД оптимизировать выполнение.
@Service
public class ReportService {
// Оптимизирует для только чтения
@Transactional(readOnly = true)
public List<Account> getAccountsByCustomer(int customerId) {
// Spring может:
// - Не создавать savepoints
// - Оптимизировать кэширование
// - Использовать read-only коннекшны
return accountRepository.findByCustomerId(customerId);
}
// С модификациями данных
@Transactional(readOnly = false) // По умолчанию
public void updateAccount(Account account) {
accountRepository.save(account);
}
}
5. rollbackFor и noRollbackFor (Откат по исключениям)
Определяет, при каких исключениях откатывать транзакцию.
public class PaymentException extends Exception {}
public class WarningException extends Exception {}
@Service
public class TransactionService {
// По умолчанию откатывает только при RuntimeException
// Это явно указывает откатываться при checked исключении
@Transactional(rollbackFor = PaymentException.class)
public void processPayment(String orderId) throws PaymentException {
if (paymentFailed()) {
throw new PaymentException("Payment processing failed");
// Транзакция откатится несмотря на то, что это checked исключение
}
}
// Откатывается при multiple исключениях
@Transactional(rollbackFor = {PaymentException.class, TimeoutException.class})
public void complexOperation() throws PaymentException, TimeoutException {
// ...
}
// НЕ откатывается при WarningException
@Transactional(noRollbackFor = WarningException.class)
public void operationWithWarning() throws WarningException {
updateData();
if (warningCondition()) {
throw new WarningException("Warning occurred");
// Транзакция COMMIT-ится, исключение пройдёт дальше
}
}
// Комбинированный пример
@Transactional(
rollbackFor = {IOException.class, SQLException.class},
noRollbackFor = {ValidationException.class}
)
public void complexScenario() throws Exception {
// Откатывается на IOException и SQLException
// НЕ откатывается на ValidationException
}
}
6. value / transactionManager (Менеджер транзакций)
Указывает, какой TransactionManager использовать (важно при нескольких БД).
@Configuration
public class TransactionManagerConfig {
@Bean
public PlatformTransactionManager primaryTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public PlatformTransactionManager secondaryTransactionManager(DataSource secondaryDataSource) {
return new DataSourceTransactionManager(secondaryDataSource);
}
}
@Service
public class MultiDatabaseService {
// Использует primaryTransactionManager (по умолчанию)
@Transactional
public void updatePrimary() {
// ...
}
// Явно указываем secondaryTransactionManager
@Transactional("secondaryTransactionManager")
public void updateSecondary() {
// ...
}
}
Полный пример с все аргументами
@Service
public class ComplexTransactionService {
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 60,
readOnly = false,
rollbackFor = {SQLException.class, IOException.class},
noRollbackFor = {IllegalArgumentException.class},
transactionManager = "primaryTransactionManager"
)
public void complexBusinessOperation(String data)
throws SQLException, IOException {
// Логика с полным контролем над транзакцией:
// - Использует существующую или создаёт новую транзакцию
// - Уровень изоляции READ_COMMITTED
// - Должна завершиться за 60 секунд
// - Может изменять данные (readOnly = false)
// - Откатывается на SQL и IO ошибках
// - НЕ откатывается на IllegalArgumentException
}
}
Важные особенности и gotchas
1. @Transactional работает только через Proxy
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// Работает: вызов через Spring-контекст
}
public void processOrders() {
Order order = new Order();
createOrder(order); // ❌ НЕ работает! Транзакция не применяется
// Прямой вызов метода, не через proxy
this.createOrder(order); // ❌ Тоже НЕ работает
}
}
// ✅ Правильно: внедрить сервис через DI
@Service
public class OrderProcessor {
@Autowired
private OrderService orderService; // Через proxy
public void processOrders() {
orderService.createOrder(order); // ✅ Работает!
}
}
2. Проверенные исключения не откатывают по умолчанию
@Service
public class DataService {
@Transactional // По умолчанию откатывает ТОЛЬКО RuntimeException!
public void saveData(String data) throws IOException {
repository.save(data);
if (someCondition) {
throw new IOException("IO Error"); // НЕ откатится!
}
}
@Transactional(rollbackFor = IOException.class) // Явно указываем
public void saveDataSafely(String data) throws IOException {
repository.save(data);
if (someCondition) {
throw new IOException("IO Error"); // Теперь откатится
}
}
}
3. readOnly с JPA может оптимизировать флаш
@Service
public class ReportService {
@Transactional(readOnly = true)
public List<Report> getReports() {
// Spring может пропустить flush перед commit
// Это экономит время на large object graphs
return reportRepository.findAll();
}
}
Рекомендации по использованию
- Используй REQUIRED как default — наиболее распространённый паттерн
- Указывай readOnly = true для queries — даёт подсказку для оптимизации
- Устанавливай timeout для длительных операций — защита от зависаний
- Явно указывай rollbackFor для checked exceptions — иначе не откатится
- Избегай NESTED без необходимости — поддерживается не всеми БД
- Думай о propagation при вложенных вызовах — это очень важно
Понимание всех этих параметров позволяет написать надёжный и предсказуемый код работы с БД.