← Назад к вопросам

Какие знаешь аргументы @Transactional?

2.0 Middle🔥 181 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Аргументы аннотации @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();
    }
}

Рекомендации по использованию

  1. Используй REQUIRED как default — наиболее распространённый паттерн
  2. Указывай readOnly = true для queries — даёт подсказку для оптимизации
  3. Устанавливай timeout для длительных операций — защита от зависаний
  4. Явно указывай rollbackFor для checked exceptions — иначе не откатится
  5. Избегай NESTED без необходимости — поддерживается не всеми БД
  6. Думай о propagation при вложенных вызовах — это очень важно

Понимание всех этих параметров позволяет написать надёжный и предсказуемый код работы с БД.