Spring Transaction

Spring의 선언적 트랜잭션(@Transactional)은 AOP 프록시 패턴을 기반으로 동작해, 핵심 비즈니스 로직과 데이터베이스 트랜잭션 관리 로직을 분리한다.[1] 이 페이지는 그 프록시 동작 원리로 인해 발생하는 내부 호출(Self-Invocation) 한계, 예외 타입에 따른 롤백 규칙(Rollback Rules), 다중 트랜잭션 환경에서의 전파 속성 및 격리 수준의 표준 패턴을 다룬다.

프록시 기반 트랜잭션 아키텍처

sequenceDiagram
    autonumber
    actor Client as Client (e.g., Controller)
    participant Proxy as AOP Proxy
(Transaction Barrier) participant TxManager as PlatformTransactionManager participant Target as Target Service
(Business Logic) participant DB as Connection/DB Note over Client, Proxy: 외부 호출 시에만 Proxy 작동 Client->>Proxy: 비즈니스 메서드 호출 activate Proxy Proxy->>TxManager: 트랜잭션 시작 (begin) activate TxManager TxManager->>DB: SET autocommit = false TxManager-->>Proxy: 경계 설정 완료 deactivate TxManager Proxy->>Target: 실제 타겟 메서드 위임 호출 activate Target alt 정상 종료 (No Exception) Target-->>Proxy: 결과 리턴 deactivate Target Proxy->>TxManager: Commit 요청 activate TxManager TxManager->>DB: COMMIT TxManager-->>Proxy: 완료 deactivate TxManager else 런타임 예외 발생 (Unchecked) Target-->>Proxy: Throw RuntimeException Proxy->>TxManager: Rollback 요청 activate TxManager TxManager->>DB: ROLLBACK TxManager-->>Proxy: 완료 deactivate TxManager end Proxy-->>Client: 최종 응답 또는 예외 전파 deactivate Proxy

아키텍처적 제약: 내부 호출(Self-Invocation)

예외 롤백 규칙(Rollback Rules)

Spring은 자바의 예외 계층에 따라 롤백 여부를 판단하며, 개발자가 의도치 않은 데이터 손실이나 잔류를 겪지 않도록 한다.

실무 적용 패턴

실무에서는 IOException·SQLException 같은 Checked Exception 상황에서도 데이터 무결성을 위해 롤백해야 하는 경우가 대부분이다. 따라서 @Transactional(rollbackFor = Exception.class) 옵션을 명시하는 것이 표준이다.[4]

트랜잭션 전파 속성(Propagation)

기존 트랜잭션이 진행 중일 때 새로운 메서드가 트랜잭션을 요구할 경우의 합류/분리 전략이다.[8]

전파 옵션 동작 메커니즘 실무 유스케이스
REQUIRED(Default) 기존 트랜잭션에 참여(없으면 신규 생성) 대부분의 단일 비즈니스 플로우 묶음
REQUIRES_NEW 항상 독립적인 신규 트랜잭션 생성(기존 보류) 실패해도 무조건 기록해야 하는 **[[Wiki/backend/patterns/audit-log.md
MANDATORY 반드시 기존 트랜잭션 내에서만 실행 부모 트랜잭션의 존재를 강제할 때
NESTED 기존 트랜잭션 내 중첩(Savepoint) 생성 자식 실패가 부모에게 영향 주지 않아야 할 때(단, JPA 미지원)

격리 수준(Isolation Level) 및 이상 현상

동시성 환경에서 다른 트랜잭션이 수정 중인 데이터를 어떻게 읽게 허용할 것인지에 대한 RDBMS 격리 정책이다.

수준(Level) Dirty Read Non-Repeatable Read Phantom Read RDBMS 채택
READ_UNCOMMITTED 발생 발생 발생 거의 사용 안 함[9]
READ_COMMITTED 방지 발생 발생 Oracle, PostgreSQL 기본값[9:1]
REPEATABLE_READ 방지 방지 발생 MySQL(InnoDB) 기본값[9:2]
SERIALIZABLE 방지 방지 방지 완벽 격리(성능 이슈로 금융 등 극히 제한적 사용)[9:3]

관련

출처


  1. Transaction.md — "@Transactional이 선언된 빈(Bean)이 컨테이너에 등록될 때, 스프링은 실제 객체를 감싸는 프록시 객체(Proxy)를 자동으로 만들어 등록합니다.", "클라이언트가 메서드를 호출하면 프록시 객체의 메서드가 대신 먼저 호출되며, 내부의 PlatformTransactionManager를 통해 DB 트랜잭션을 시작(begin)합니다." ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. Transaction.md — "클래스 내부에서 @Transactional이 없는 메서드가 동일 클래스 내의 다른 @Transactional 메서드를 직접 호출(this 호출)하는 경우, 프록시를 거치지 않고 실제 객체의 레퍼런스를 직접 참조하므로 트랜잭션이 적용되지 않습니다.", "가장 권장되는 방법은 트랜잭션이 필요한 비즈니스 단위를 별도의 클래스(서비스 빈)로 분리하여 외부 호출 구조로 변경하는 것입니다." ↩︎ ↩︎ ↩︎

  3. Transaction.md — "RuntimeException과 그 하위 클래스들, 그리고 시스템 레벨 오류인 Error가 발생한 경우 스프링은 비즈니스 실패로 판단하여 자동으로 트랜잭션을 롤백합니다.", "RuntimeException을 상속받지 않은 일반 Exception 계열(Checked Exception)이 발생하면... 트랜잭션을 커밋해 버립니다." ↩︎ ↩︎

  4. Transaction.md — "실무에서는 비즈니스 로직 중 발생하는 어떠한 예외에 대해서도 데이터 무결성을 위해 롤백을 수행해야 하는 경우가 대부분입니다. 따라서 @Transactional 선언 시 Checked Exception에 대해서도 롤백이 수행되도록 명시적 설정을 해 주는 것이 표준입니다.", 예외 계층도 "Checked Exception (그 외)", "- IOException, SQLException 등" (L129-130) ↩︎

  5. Transaction.md — "메서드가 정상 종료되면 트랜잭션을 커밋(Commit)합니다.", "메서드 실행 중 예외(Exception)가 발생하면 예외 종류에 따라 롤백(Rollback) 또는 커밋을 수행합니다.", sequence diagram "Target-->>Proxy: Exception 던짐", "Proxy->>TxManager: 롤백 요청" ↩︎

  6. 인증 실패 응답·좋아요 중복 방지 노트 — "Spring: 리포지토리에서 예외가 나는 순간 바깥 트랜잭션을 "롤백 전용"으로 표시한다. 조회를 안 해도 커밋할 때 UnexpectedRollbackException이 난다.", "Postgres: 트랜잭션 안에서 에러가 한 번 나면 그 트랜잭션 전체를 버린다. 이후 쿼리는 current transaction is aborted로 실패한다.", Postgres vs MySQL 표("그 문장만 롤백", "그래도 500 (Spring 롤백 전용 표시 때문)"), "save()와 delete()에는 Spring Data가 기본으로 @Transactional을 붙여 둔다." ↩︎ ↩︎ ↩︎ ↩︎

  7. 인증 실패 응답·좋아요 중복 방지 노트 — "큰 트랜잭션이 꼭 필요한 경우: 쓰기가 2개 이상이고 같이 성공하거나 같이 실패해야 할 때(예: 송금의 출금과 입금). 좋아요 토글은 쓰기가 하나(추가 또는 삭제)라 묶을 짝이 없다." ↩︎

  8. Transaction.md — 전파 속성 표: REQUIRED("기본값. 진행 중인 트랜잭션이 있으면 참여하고, 없으면 새로 생성합니다."), REQUIRES_NEW("항상 새로운 트랜잭션을 시작합니다. 기존 트랜잭션은 보류(Suspend)됩니다."), MANDATORY, NESTED("DB Savepoint 기술 사용... JPA에서는 미지원") 등. ↩︎

  9. Transaction.md — 격리 수준 표: "READ_UNCOMMITTED... 데이터 부정확성이 매우 높아 실무 미사용.", "READ_COMMITTED... Oracle, PostgreSQL 등 다수 RDBMS의 기본값.", "REPEATABLE_READ... MySQL(InnoDB)의 기본값.", "SERIALIZABLE... 성능 저하가 심해 특수한 금융 거래 등 외에는 거의 미사용." ↩︎ ↩︎ ↩︎ ↩︎