Изображение статьи

Как работает @Transactional в Spring Boot

Что такое транзакция?

Транзакция — это последовательность операций, которые выполняются как единое целое. Если одна из операций не выполнится, все изменения отменяются. Например:

Если на складе не хватает товара, заказ тоже не должен быть создан. В этом и заключается суть транзакций.

Как работает @Transactional?

Когда вы добавляете аннотацию @Transactional к методу, Spring создаёт прокси, который управляет транзакцией. Вот как это работает:

  1. Открытие транзакции: перед вызовом метода Spring открывает новую транзакцию.
  2. Выполнение метода: метод выполняется в контексте транзакции.
  3. Фиксация или откат: если метод завершился успешно, транзакция фиксируется. Если произошло исключение, транзакция откатывается.

Пример

Допустим, вы пишете сервис для создания пользователей:

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public void createUser(String name, String email) {
        User user = new User();
        user.setName(name);
        user.setEmail(email);

        userRepository.save(user);

        if (email.contains("@example.com")) {
            throw new IllegalArgumentException("Email not allowed: " + email);
        }
    }
}

- Если email содержит @example.com, метод выбросит исключение, и данные в базе откатятся.
- Если ошибок нет, пользователь сохранится в базе.

Настройка @Transactional

Аннотация @Transactional поддерживает множество параметров:

1. Propagation

Определяет, как текущая транзакция взаимодействует с существующими транзакциями. Основные значения:

REQUIRED (по умолчанию) — если транзакция уже существует, метод использует её; если нет, создаётся новая.

REQUIRES_NEW — всегда создаёт новую транзакцию, независимо от существующих.

SUPPORTS — если транзакция существует, метод использует её; если нет, выполняется без транзакции.

2. Isolation

Задаёт уровень изоляции транзакции. Например:

READ_UNCOMMITTED — предотвращает чтение данных которые не были подтверждены другой транзакцией (данный уровень изоляции не поддерживается ORACLE и PostgreSQL).

READ_COMMITTED (по умолчанию) — предотвращает чтение неподтверждённых данных (существует фантомное и неповторяющееся чтение).

REPEATABLE_READ — в данном уровне изоляции существует только фантомное чтение.

SERIALIZABLE — обеспечивает максимальную изоляцию, но снижает производительность.

3. Timeout

Указывает максимальное время выполнения транзакции. Если время превышено, транзакция откатывается.

@Transactional(timeout = 5) // транзакция завершится, если выполнение превысит 5 секунд
public void performLongTask() {
   // Долгое выполнение
}

4. RollbackFor

Указывает, при каких исключениях следует откатывать транзакцию.

@Transactional(rollbackFor = CustomException.class)
public void performTask() throws CustomException {
   // Код
}

5. ReadOnly

Используется для оптимизации операций только для чтения. Например, при чтении данных из базы:

@Transactional(readOnly = true)
public List getAllUsers() {
  return userRepository.findAll();
}

Частые ошибки и почему они происходят

1. Вызов транзакционного метода изнутри класса

Spring использует прокси для управления транзакциями, и вызов метода с @Transactional изнутри того же класса обходит этот механизм. В результате транзакция не создаётся.

Пример:

@Service
public class UserService {

    @Transactional
    public void methodA() {
        // транзакция создаётся
    }

    public void methodB() {
        methodA(); // транзакция НЕ активируется
    }
}

Решение:

Вынесите транзакционный метод в отдельный компонент или вызовите его через Spring-контекст.

2. Откат работает только для unchecked исключений

По умолчанию транзакция откатывается только при исключениях, наследующихся от RuntimeException. Если вы выбрасываете checked-исключение (например, IOException), транзакция не откатится.

Пример:

@Transactional
public void performTask() throws IOException {
    // код
    throw new IOException("Ошибка");
}

Почему это происходит: Spring настроен откатывать только unchecked исключения для совместимости с JPA.

Решение:

@Transactional(rollbackFor = IOException.class)
public void performTask() throws IOException {
    // код
}

3. Долгие операции в транзакции

Если транзакция открыта слишком долго (например, во время работы с внешним API), это может вызвать блокировки базы данных.

Пример:

@Transactional
public void longOperation() {
    userRepository.save(new User());
    externalApiCall(); // долгий вызов
}

Почему это происходит: Транзакция блокирует ресурсы базы данных до её завершения.

Решение:

public void longOperation() {
    userRepository.save(new User());
    callExternalApi(); // без транзакции
}

Полезные настройки

Заключение

Аннотация @Transactional — это удобный инструмент для управления транзакциями в Spring Boot. Она помогает избежать ошибок в работе с базой данных и сохраняет код чистым. Но важно знать, как она работает, и избегать частых ошибок.

Если вам понравилась статья, подписывайтесь на Telegram-канал JavaCraft.ru. Там вы найдёте ещё больше простых и полезных материалов!

от admin

Комментарии

Вы должны войти в систему, чтобы оставить комментарий.