Границы транзакций
Граница транзакции — где она начинается и где заканчивается. На практике это значит: на каком именно методе ставить @Transactional — слишком “узко” и слишком “широко” одинаково плохи.
@Service
class OrderService {
@Transactional // граница здесь — на уровне бизнес-операции
void placeOrder(Order order) {
validate(order); // часть транзакции
save(order); // часть транзакции
notifyWarehouse(order); // часть транзакции
}
}
Правильная граница — обычно сервисный метод, представляющий целую бизнес-операцию (“оформить заказ”), а не отдельный шаг внутри неё.
Копнуть глубже
Propagation (распространение транзакции) — что делать, если @Transactional-метод вызывает другой @Transactional-метод:
@Transactional(propagation = Propagation.REQUIRED) // по умолчанию: использовать текущую транзакцию, если есть
@Transactional(propagation = Propagation.REQUIRES_NEW) // всегда новая, независимая транзакция
REQUIRES_NEW полезен, когда нужно, чтобы часть операции (например, логирование) сохранилась даже если основная транзакция откатится — например, залогировать попытку платежа независимо от того, прошла она или нет.
Слишком широкая граница — проблема. Если обернуть @Transactional весь метод контроллера, включая медленный вызов внешнего API внутри, соединение с базой держится открытым всё это время — впустую занимает ресурс пула соединений (см. тему «Пулы (Hikari)») и блокирует строки в базе дольше необходимого.
Слишком узкая граница — другая проблема. Если разбить одну логическую операцию на несколько отдельных мелких транзакций, теряется атомарность — часть может пройти успешно, а часть — нет, и данные окажутся в противоречивом состоянии.
• что такое propagation и чем плохи слишком широкие/узкие границы (если дошёл до 2-го слоя).