비즈니스 로직 단위 = 트랜잭션 단위

경계는 요청 전체가 아니라 서비스 메서드 한 구간에 둡니다.

주문 생성처럼 여러 저장 작업이 하나의 성공·실패 묶음일 때 @Transactional 은 서비스 레이어에 두는 편이 가장 자연스럽습니다.

트랜잭션 밖 요청을 받고 준비하는 구간
경계가 실제로 열리는 곳 서비스 메서드 안 한 비즈니스 작업을 끝까지 묶음
종료 시점 성공이면 커밋, 실패면 롤백
Controller
요청 검증, DTO 변환
웹 계층 책임입니다.
서비스 호출만 넘김
경계 소유권은 서비스에 있습니다.
응답 생성
트랜잭션 종료 뒤 처리합니다.
Service
주문 생성 작업 선택
실행할 업무 단위를 고릅니다.
@Transactional createOrder()
주문 1건을 하나의 원자적 구간으로 선언합니다.
메서드 종료
성공은 커밋, 예외는 전체 롤백입니다.
Repository
아직 저장 작업 없음
이 단계만으로 주문은 완성되지 않습니다.
여러 저장 작업을 같은 묶음으로 실행
재고 차감
주문 저장
주문 상세 저장
부분 성공 상태를 남기지 않음
하나라도 실패하면 앞선 작업도 함께 취소됩니다.
DB
영구 반영 전
최종 상태가 아직 확정되지 않았습니다.
변경 사항을 같은 트랜잭션에 보관
중간 결과를 따로 확정하지 않습니다.
종료 순간에만 최종 결정
정상 종료 → COMMIT 재고, 주문, 주문 상세가 함께 확정
예외 발생 → ROLLBACK 셋 모두 취소되어 정합성 유지
너무 넓게 잡으면

검증과 응답 준비까지 묶여 락 유지 시간 이 길어지고 동시성이 떨어집니다.

너무 좁게 쪼개면

리포지토리마다 따로 열리면 일부 변경만 남아 비즈니스 로직의 원자성 이 깨집니다.