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

Как лучше использовать аннотацию Transactional в Spring

2.3 Middle🔥 171 комментариев
#Spring Boot и Spring Data#Spring Framework

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

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

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

Использование @Transactional в Spring

Аннотация @Transactional обеспечивает управление транзакциями в Spring. Она гарантирует ACID свойства при работе с БД: либо все операции выполняются успешно, либо все откатываются.

Основной механизм

Spring использует proxy pattern для перехвата методов с @Transactional:

// ВЫ пишете:
@Service
public class UserService {
    @Transactional
    public void createUser(String name) {
        // Операции с БД
        userRepository.save(name);
        // Если Exception — откат
    }
}

// Spring создаёт proxy:
UserService proxy = new UserServiceProxy(userService);

// Proxy обёртывает вызов:
public void createUser(String name) {
    transaction.begin();
    try {
        realService.createUser(name);
        transaction.commit();
    } catch (Exception e) {
        transaction.rollback();
        throw e;
    }
}

Основные атрибуты

1. propagation — как работает с внешней транзакцией

// REQUIRED (по умолчанию)
// Использует существующую транзакцию, если нет — создаёт новую
@Transactional(propagation = Propagation.REQUIRED)
public void method() { }

// REQUIRES_NEW
// Всегда создаёт новую транзакцию, приостанавливая старую
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method() { }

// NESTED
// Создаёт savepoint внутри существующей транзакции
@Transactional(propagation = Propagation.NESTED)
public void method() { }

// SUPPORTS
// Работает с транзакцией если она есть, иначе без неё
@Transactional(propagation = Propagation.SUPPORTS)
public void method() { }

Пример использования:

@Service
public class OrderService {
    @Autowired private OrderRepository orderRepo;
    @Autowired private PaymentService paymentService;
    
    @Transactional
    public void createOrder(Order order) {
        orderRepo.save(order);
        
        // Если платёж провалится, заказ тоже откатится
        paymentService.processPayment(order.getId());
    }
}

@Service
public class PaymentService {
    // REQUIRES_NEW: отдельная транзакция для платежа
    // Платёж может быть сохранён даже если заказ откатится
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void processPayment(Long orderId) {
        // Логирование платежа в отдельной транзакции
    }
}

2. isolation — уровень изоляции

// READ_UNCOMMITTED
// Грязное чтение: видим незакоммиченные данные
@Transactional(isolation = Isolation.READ_UNCOMMITTED)

// READ_COMMITTED (по умолчанию в большинстве БД)
// Видим только закоммиченные данные
@Transactional(isolation = Isolation.READ_COMMITTED)

// REPEATABLE_READ
// Одно значение не меняется в течение транзакции
@Transactional(isolation = Isolation.REPEATABLE_READ)

// SERIALIZABLE
// Полная изоляция, как будто транзакции выполняются последовательно
@Transactional(isolation = Isolation.SERIALIZABLE)

3. rollbackFor — при каких исключениях откатывать

// По умолчанию откатывается только при RuntimeException
@Transactional
public void defaultRollback() {
    // Откатится при RuntimeException
    throw new RuntimeException(); // Откат
}

// Checked exceptions НЕ откатываются по умолчанию!
@Transactional
public void withCheckedException() throws IOException {
    throw new IOException(); // НЕ откатится!
}

// Явно указываем, при каких исключениях откатывать
@Transactional(rollbackFor = Exception.class)
public void rollbackForAll() throws Exception {
    throw new Exception(); // Откатится
}

// Можно указать несколько типов
@Transactional(rollbackFor = {IOException.class, SQLException.class})
public void rollbackForMultiple() { }

4. noRollbackFor — исключения, при которых НЕ откатывать

@Transactional(noRollbackFor = ValidationException.class)
public void noRollbackOnValidation() {
    // Если ValidationException — отката не будет
    throw new ValidationException();
}

5. readOnly — только чтение

// Оптимизация для методов только с SELECT запросами
@Transactional(readOnly = true)
public User getUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}

// Spring отключит flush режим, улучшив производительность

Лучшие практики

1. Аннотируйте методы, а не классы

// ❌ Плохо: аннотируется весь класс
@Service
@Transactional
public class UserService {
    public void readUser() { } // Не нужна транзакция для чтения
}

// ✅ Хорошо: аннотируйте только нужные методы
@Service
public class UserService {
    @Transactional(readOnly = true)
    public User readUser() { }
    
    @Transactional
    public void createUser() { }
}

2. Используйте readOnly = true для чтения

@Transactional(readOnly = true)
public List<User> getAllUsers() {
    // Spring отключает flush, снижает overhead
    return userRepository.findAll();
}

3. Контролируйте scopе транзакции

// ❌ Плохо: длинная транзакция
@Transactional
public void longOperation() {
    User user = userRepository.findById(1L);
    // Долгие вычисления (не касающиеся БД)
    // Транзакция держится открытой!
    Thread.sleep(5000);
    userRepository.save(user);
}

// ✅ Хорошо: маленькие транзакции
public void longOperation() {
    User user = findUser(); // Отдельная транзакция
    // Долгие вычисления БЕЗ открытой транзакции
    Thread.sleep(5000);
    saveUser(user); // Отдельная транзакция
}

@Transactional
private User findUser() { return ...; }

@Transactional
private void saveUser(User user) { ... }

4. Помните о self-invocation problem

@Service
public class UserService {
    @Transactional
    public void publicMethod() {
        // Создаётся транзакция
        privateMethod(); // @Transactional НЕ сработает!
    }
    
    @Transactional
    private void privateMethod() {
        // Proxy не перехватывает, транзакция не создаётся
    }
}

// Решение: внедрить ApplicationContext
@Service
public class UserService {
    @Autowired private ApplicationContext context;
    
    public void publicMethod() {
        UserService proxy = context.getBean(UserService.class);
        proxy.privateMethod(); // Теперь сработает @Transactional
    }
    
    @Transactional
    public void privateMethod() { }
}

5. Обработка исключений

@Transactional
public void safeOperation() {
    try {
        userRepository.save(user);
    } catch (DataIntegrityViolationException e) {
        // Транзакция уже помечена на откат!
        // Даже если вы поймали исключение, откат всё равно произойдёт
        log.error("Error saving user");
    }
}

Этой информации достаточно для грамотного использования @Transactional в production коде.