Как лучше использовать аннотацию Transactional в Spring
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование @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 коде.