runtime-role-smoke-20260714.../docs/adr/ADR-003-transaction-boundary.md

3.2 KiB

ADR-003: 트랜잭션 경계(Transaction Boundary) 정의

Context

Spring에서 트랜잭션 경계 설정 방식에 따라:

  • 데이터 무결성 보장
  • 성능 최적화
  • 격리 수준(Isolation Level) 제어

를 적절히 balancing해야 한다.

Decision

1. 트랜잭션 전파 정책

전파 유형 사용 시점
REQUIRED (기본값) 대부분의 Service 메서드
REQUIRES_NEW 독립적인 작업 단위 (로깅, 알림)
NESTED 저장점(savepoint) 기반 부분 롤백
SUPPORTS 읽기 전용 조회 (트랜잭션 없으면 자동 읽기 전용)

2. 격리 수준(Isolation Level)

@Transactional(isolation = Isolation.READ_COMMITTED)
격리 수준 더티 리드 반복 불가능 읽기 팬텀 읽기
READ_UNCOMMITTED 가능 가능 가능
READ_COMMITTED 불가 가능 가능
REPEATABLE_READ 불가 불가 가능
SERIALIZABLE 불가 불가 불가

결정: READ_COMMITTED를 기본값으로 사용

  • 대부분의 비즈니스 시나리오에 적합
  • 동시성 성능과 일관성의 균형

3. 읽기 전용 트랜잭션

@Transactional(readOnly = true)
public List<Role> getAllRoles() {
    return roleRepository.findAll();
}

적용 규칙:

  • 데이터 조회 전용 Service 메서드에 적용
  • JPA: Hibernate flush mode를 MANUAL로 설정하여 최적화
  • JDBC: 읽기 전용 커넥션 힌트 제공

4. 트랜잭션 경계 위치

[Controller] 
    ↓
[Service Layer] ← ★ 트랜잭션 경계
    ↓
[Repository Layer]
    ↓
[Database]

규칙:

  • 트랜잭션은 Service 계층에서 시작
  • Controller에서 @Transactional 사용 금지
  • Repository에서 @Transactional 사용 금지

5. 롤백 정책

@Transactional(rollbackFor = Exception.class)
public void createRole(RoleRequest request) {
    // unchecked exception (RuntimeException): 자동 롤백
    // checked exception: rollbackFor 명시 필요 시 사용
}

결정:

  • 기본값(RuntimeException 및 하위 클래스 자동 롤백) 유지
  • 비즈니스 예외는 모두 RuntimeException 상속

6. 트랜잭션 타임아웃

@Transactional(timeout = 30) // 30초

적용 규칙:

  • 대량 데이터 처리 배치 작업에만 명시적 타임아웃 설정
  • 일반 API 요청은 기본값(INFINITE) 유지

Alternatives

대안 1: Programmatic Transaction

  • TransactionTemplate 사용
  • 단점: 코드 복잡성 증가, AOP 이점 상실
  • 채택하지 않음 (선언적 트랜잭션 채택)

대안 2: Controller 레벨 트랜잭션

  • 단점: HTTP 요청 단위로 전체 트랜잭션 시야 과도
  • 채택하지 않음

Consequences

Positive:

  • Service 메서드 단위로 명확한 트랜잭션 경계
  • 격리 수준 및 전파 정책 세밀한 제어 가능
  • 읽기 전용 최적화 활용 가능

Negative:

  • 잘못된 전파 설정 시 예상 외 동작 가능
  • 다중 데이터소스 환경에서 복잡성 증가

Mitigation:

  • 전파 정책 사용 시 주석으로 의도 명시
  • Integration Test에서 트랜잭션 동작 검증