[트랜잭션] Spring @Transactional을 이해하기 위한 트랜잭션 기본 개념 총정리

이영재·2025년 11월 7일

Spring

목록 보기
20/20

1. 트랜잭션

트랜잭션의 사전적 의미는 거래이다.
하지만 컴퓨터 과학 분야에서는 “더 이상 분할이 불가능한 업무처리의 단위”를 의미한다.
즉 한꺼번에 수행되어야 할 일련의 연산 모음을 의미한다.

1.1 트랜잭션 ACID

트랜잭션은 ACID라 하는 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability)을 보장해야한다.

  • 원자성: 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것 처럼 모두 성공하거나 모두 실패해야 한다.
  • 일관성: 모든 트랜잭션은 일관성이 있는 데이터베이스 상태를 유지해야 한다.
    • 예를 들어 데이터베이스에서 정한 무결성 제약조건을 항상 만족해야 한다.
  • 격리성: 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다.
    • 예를 들어 동시에 같은 데이터를 수정하지 못하도록 해야 한다.
    • 격리성은 동시성과 관련된 성능 이슈로 인해 트랜잭션 격리 수준을 선택할 수 있다.
  • 지속성: 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다.
    • 중간에 시스템에 문제가 발생해도 데이터베이스 로그 등을 사용해 성공한 트랜잭션 내용을 복구 해야한다.

💡트랜잭션이란
원자성, 일관성, 지속성을 보장한다. 문제는 격리성인데 트랜잭션 간에 격리성을 완전히 보장하려면 트랜잭션을 거의 순서대로 실행해야 한다. 이렇게 하면 동시 처리 성능이 매우 나빠진다. 이런 문제로 인해 ANSI 표준은 트랜잭션의 격리 수준을 4단계로 나누어 정의했다.

1.1.1 트랜잭션 실습

💡Commit 이란
Commit 이란, 모든 작업들을 정상 처리하겠다고 확정하는 명령어로서, 해당 처리과정을 DB에 영구 저장하겠다는 의미이며, Commit을 수행하면 하나의 트랜잭션 과정이 종료되는 것이다. Commit을 수행하면 이전 데이터가 완전히 반영되어 UPDATE 된다.

💡Roll-back 이란
작업 중 문제가 발생되어 트랜잭션의 처리 과정에서 발생한 변경사항을 취소하는 명령어.
해당 명령을 트랜잭션에게 하달하면, 트랜잭션은 시작되기 이전의 상태로 되돌아간다.
즉, Rollback은 Commit 하여 저장한 예정 상태를 복구하는 것이다.

커밋 하지 않은 데이터 읽기

// 테이블 생성
create table member (
                        member_id varchar(10),
                        money integer not null default 0,
                        primary key (member_id)
);

// T1 
set autocommit false;
insert into member(member_id, money) values ('newId1',10000);
insert into member(member_id, money) values ('newId2',10000);
SELECT * FROM MEMBER 

// T2 
SELECT * FROM MEMBER 

계좌이체 상황 시나리오 (롤백 실습)

  • 정상
    // 데이터 초기화
    DELETE FROM member
    insert into member(member_id, money) values ('memberA',10000);
    insert into member(member_id, money) values ('memberB',10000);
    
    // T1 
    set autocommit false;
    update member set money=10000 - 2000 where member_id = 'memberA';
    update member set money=10000 + 2000 where member_id = 'memberB';
    
    commit;
    • 잘 처리 됨.
  • 문제 상황
    // 데이터 초기화
    DELETE FROM member
    insert into member(member_id, money) values ('memberA',10000);
    insert into member(member_id, money) values ('memberB',10000);
    
    // T1 
    set autocommit false;
    update member set money=10000 - 2000 where member_id = 'memberA';
    update member set money=10000 + 2000 where member_iiiid = 'memberB';
    
    commit;
    • memberA 는 -2000 성공하고
    • memberB 에 + 2000 실패
  • 문제 해결(롤백)
    set autocommit false;
    update member set money=10000 - 2000 where member_id = 'memberA';
    update member set money=10000 + 2000 where member_iiiid = 'memberB';
    
    rollback;
    • 롤백시
      • memberA, memberB 모두 update 반영 안됨

1.2 트랜잭션 상태

1.2.1 활성 (Active)

트랜잭션이 정상적으로 실행중인 상태

  • 트랜잭션이 시작되면, 해당 트랜잭션의 상태는 활동 상태가 된다.
  • 해당 상태는 설계자가 설계한 대로 연산들이 정상적으로 실행중인 상태를 의미한다.

1.2.2 부분 완료 (Partially Committed)

트랜잭션의 마지막까지 실행되었지만, Commit 연산이 실행되기 직전의 상태

  • 설계된 트랜잭션대로 명령을 성공적으로 수행하면 그 다음 상태는 부분적 완료 상태가 된다.
  • 작업이 성공하였다고 무조건 반영하는 것이 아니라, 설계자의 최종 승인이 있을 때까지 실제 데이터베이스에 작업 내용을 반영하지 않고 기다리고 있는 상태이다.

1.2.3 완료 (Committed)

트랜잭션 내의 모든 작업이 성공적으로 수행되어 커밋 명령을 통해 데이터베이스에 영구 반영되고 트랜잭션이 종료된 상태

1.2.4 실패 (Failed)

트랜잭션 실행에 오류가 발생하여 중단된 상태

  • 트랜잭션을 수행하는 중간에 모종의 원인으로 인하여 오류가 발생하여 중단된 상태를 실패 상태라고 한다

1.2.5 철회 (Aborted)

트랜잭션이 비정상적으로 종료되어 Rollback 연산을 수행한 상태

  • 트랜잭션이 비정상적으로 종료되었으니 설계되어있는 트랜잭션 내부의 작업을 다시 수행 이전의 상태로 되돌리는 Rollback 연산을 수행하면 그 상태를 철회 라고 한다.

1.3 트랜잭션의 격리 수준

  • READ UNCOMMITED (커밋되지 않은 읽기)
  • READ COMMITED (커밋된 읽기)
  • REPEATABLE READ (반복 가능한 읽기)
  • SERIALIZEBLE (직렬화 가능)

1.3.1 READ UNCOMMITED (커밋되지 않은 읽기)

다른 트랜잭션이 아직 커밋하지 않은 데이터(Dirty Data) 도 읽을 수 있다.

https://tlatmsrud.tistory.com/118

→ 10번 트랜잭션이 Update 한 후 Commit 하지 않았을 때

  • 이 경우
    • 13번 트랜잭션이 데이터를 읽은 후에 10번 트랜잭션이 문제가 발생하여 롤백된다면 데이터의 부정합을 발생 시킬 수 있다.
      • Dirty Read : 커밋되지 않은 트랜잭션에 접근하여 부정합을 유발할 수 있는 데이터를 읽는 것

1.3.2 READ COMMITED (커밋된 읽기)

다른 트랜잭션에서 커밋된 데이터로만 접근할 수 있게 하는 격리 수준이다.

→ 10번 트랜잭션이 Update 한 후 Commit 하지 않았을 때

  • 이 경우

    • 13번 트랜잭션이 데이터를 조회할 경우 Update 전의 데이터를 조회한다.

      • 이때는 Dirty Read 현상은 발생하지 않으며, Undo 영역을 조회한다.
        • Undo 영역 지속성

          Undo 영역이란 변경 전 데이터가 저장된 영역이고, Commit 하기 전 데이터를 읽어올 수 있는 이유는 Undo 영역에 있는 데이터를 읽어오기 때문이다.

    • 그러나  Non Repeatable Read(반복 가능하지 않은 읽기) 현상 발생한다

      Non Repeatable Read
      하나의 트랜잭션에서 동일한 select 쿼리를 실행시켰을 때 다른 결과가 나오는 것을 말한다.

      • 다음처럼 13번 트랜잭션이 동일한 SELECT 쿼리를 두 번 실행했을 때 결과가 다르게 나타난다.
        • 10번 트랜잭션이 데이터 update 이후 commit 하기 전, 후에 select 쿼리를 실행했기 때문이다.

1.3.3 REPEATABLE READ (반복 가능한 읽기)

Non Repeatable Read 문제를 해결하는 격리 수준으로 커밋된 데이터만 읽을 수 있되 자신보다 낮은 트랜잭션 번호를 갖는 트랜잭션에서 커밋한 데이터만 읽을 수 있는 격리수준이다.

💡이게 가능한 이유는 Undo 로그 때문이다.
트랜잭션 ID를 통해 Undo 영역의 데이터를 스냅샷처럼 관리하여 동일한 데이터를 보장하는 것을 MVCC(Multi Version Concurrency Control) 라고 한다.

💡Repeatable Read를 지원하지 않는 오라클?
오라클은 REPEATABLE READ 격리 수준을 명시적으로 지원하지 않는다. 그럼 Non Repeatable Read 문제를 해결할 수 없을까? 
→  해결할 수 있는 방법이 있다. 오라클은 MVCC(Undo 기반 일관된 읽기)Exclusive Lock을 함께 사용해 사실상 REPEATABLE READ 이상의 일관성을 구현한다.

Exclusive Lock

  • 특정 레코드나 테이블에 대해 다른 트랜잭션에서 수정(쓰기) 작업을 할 수 없도록 하는 Lock 이다.
  • Select ~ For Update 구문을 통해서 사용한다.
  • Exclusive Lock이 걸린 레코드는 다른 트랜잭션에서 UPDATE나 DELETE 시도 시 대기 상태가 된다.
  • 다만, 오라클은 MVCC를 사용하기 때문에 다른 트랜잭션이 해당 레코드를 조회(SELECT)하는 것은 가능하다.
    • 이때는 Undo 영역의 이전 버전(snapshot)을 읽는다.

→ 즉 오라클의 READ COMMITTED = 타 DB의 REPEATABLE READ에 가까운 동작을 한다.
그래서 오라클이 굳이 REPEATABLE READ 를 지원하지 않는다고 하는 것이다.

⚠️여기서 발생할 수 있는 문제

REPEATABLE READ도 완전히는 완벽하지 않다.

다른 트랜잭션의 수정 작업은 막았지만 Insert 작업이 일어나면 어떻게 될까?

→ 같은 행을 여러 번 읽더라도 항상 동일한 값을 반환하지만, 새로운 행 자체가 생기거나 사라지는 경우는 막지 못한다.

예를 들어, 특정 조건(age > 20)으로 조회했을 때, 다른 트랜잭션이 그 사이에 새로운 데이터를 삽입하면 두 번째 조회 시 “새로운 행(Phantom)”이 나타날 수 있다. 이 현상을 Phantom Read(팬텀 리드) 라고 한다.


1.3.4 SERIALIZEBLE (직렬화 가능)

트랜잭션을 순차적으로(직렬화) 수행하도록 강제하는 가장 높은 격리 수준이다.

트랜잭션 간의 간섭이 완전히 차단되어, Dirty Read, Non-Repeatable Read, Phantom Read 등 모든 데이터 부정합 문제가 발생하지 않는다.

하지만 동시에 여러 트랜잭션이 수행되지 못하므로 동시성(Concurrency) 이 크게 떨어지고 처리 속도가 느려지는 단점이 있다.

SERIALIZABLE 수준에서는 DBMS가 읽기와 쓰기 모두를 잠금(Lock) 으로 제어한다.

  • 조회 : 데이터를 읽는 동안 다른 트랜잭션이 해당 데이터를 수정하지 못하도록 함. Shared Lock
  • 삽입/ 삭제 / 수정 : 데이터를 수정하는 동안 다른 트랜잭션이 동일 데이터에 접근(읽기/쓰기)하지 못하도록 함. Exclusive Lock

💡Shared Lock
데이터를 읽을 때 사용되는 잠금.
여러 트랜잭션이 동시에 같은 데이터를 읽을 수 있지만 그 데이터에 대한 수정(쓰기)은 불가능하다.트랜잭션이 종료되면(Commit / Rollback) 잠금이 해제된다.

격리 수준 실습

테이블 생성 & 초기 데이터

CREATE TABLE account (
  id BIGINT PRIMARY KEY,
  name VARCHAR(50),
  balance INT
);

INSERT INTO account VALUES (1, '맥스', 10000);

실습 시나리오별 구성

READ UNCOMMITTED (더티 리드)

커밋되지 않은 데이터를 다른 트랜잭션이 읽을 수 있는 수준

Session 1

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
UPDATE account SET balance = 5000 WHERE id = 1;
-- 커밋하지 말고 대기

Session 2

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT balance FROM account WHERE id = 1;

결과

  • Session 2에서 5000이 조회됨 (아직 커밋 안 됨) → ✅ Dirty Read 발생
  • 이후 Session 1이 ROLLBACK; 하면 실제 DB 값은 10000으로 복귀

READ COMMITTED (커밋된 읽기)

커밋된 데이터만 읽을 수 있는 수준 (Dirty Read 방지)

Session 1

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
UPDATE account SET balance = 7000 WHERE id = 1;
-- 커밋하지 않고 대기

Session 2

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT balance FROM account WHERE id = 1;

결과

  • Session 2는 여전히 10000 (커밋 안 된 변경 내용 안 보임)
  • Session 1이 COMMIT; 한 후 다시 SELECT 하면 → 7000으로 바뀜

REPEATABLE READ (반복 가능한 읽기)

같은 트랜잭션 내에서는 항상 동일한 데이터를 읽을 수 있음

Session 1

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT balance FROM account WHERE id = 1; -- 결과: 7000
-- 커밋하지 말고 대기

Session 2

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
UPDATE account SET balance = 9000 WHERE id = 1;
COMMIT;

Session 1 (다시 실행)

SELECT balance FROM account WHERE id = 1;

결과

  • Session 1은 여전히 7000을 봄
  • 즉, 같은 트랜잭션 내에서는 데이터가 “고정 스냅샷”으로 유지됨

SERIALIZABLE (직렬화 가능)

모든 트랜잭션을 순차적으로 실행한 것처럼 동작함

Session 1

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT * FROM account;
-- 커밋하지 않고 대기

Session 2

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO account VALUES (2, '새계좌', 2000);

결과

  • Session 2는 잠금이 걸려 대기 → Session 1이 COMMIT; 해야 실행됨
  • 완전한 일관성이 유지되지만 성능 저하 심함

1.4 트랜잭션 전파 속성(Transaction Propagation)

💡간단한 연산에 경우에 오류가 나면 해당 트랜잭션을 롤백하면 된다. 하지만 이 트랜잭션이 여러개 연결된다면? 어디서 부터 어디까지 끊고 롤백시켜야 할까? 또한 성공한 경우에 어디까지 커밋 해야 할까?

이미 트랜잭션이 진행중일 때 추가 트랜잭션 진행을 어떻게 할지 결정하는 것이 전파 속성(Propagation)이다.

1.4.1 트랜잭션의 시작과 종료

  • 트랜잭션은 시작 지점과 끝나는 지점이 존재한다. 시작하는 방법은 1 가지이지만,
  • 끝나는 방법은 2가지이다.
    • 트랜잭션이 끝나는 방법에는 모든 작업을 확정짓는 커밋과
    • 모든 작업을 무효화하는 롤백이 있다.

1.4.2 논리 트랜잭션과 물리 트랜잭션

물리 트랜잭션

트랜잭션의 원래 개념은 DBMS 에서 출발했다.
실제로 데이터의 commit 과 rollback을 수행하는 주체는 DB 이다.
이 트랜잭션은 DB Connection 단위로 관리된다.

→ 이런 식으로 DB 커넥션 단에서 직접 제어되는 것이 바로 물리 트랜잭션이다.

논리 트랜잭션

현대 애플리케이션은 단일 DB만 다루지 않고 JDBC, JPA, Redis 등 다양한 자원을 활용하는데
이 때 비즈니스 로직 단위에서 트랜잭션을 일관되게 관리해야할 필요에 생긴 것이 논리 트랜잭션이다.

→ 스프링이 내부적으로 물리 트랜잭션을 대신 시작하고, 여러 기술 계층을 하나로 묶어서 관리한다.

https://mangkyu.tistory.com/269

다음과 같은 경우에는 2개의 트랜잭션 범위가 존재하기 때문에 개별 논리 트랜잭션이 존재하지만 실제로는 물리 트랜잭션이 사용된다.

  • 물리 트랜잭션(Physical Transaction): 실제 데이터베이스에 적용되는 트랜잭션으로, 커넥션을 통해 커밋/롤백하는 단위
  • 논리 트랜잭션(Logical Transaction): 스프링이 트랜잭션 매니저를 통해 트랜잭션을 처리하는 단위

💡왜 트랜잭션을 이렇게 나눠서 관리할까?
기존의 트랜잭션이 진행중일 때 또 다른 트랜잭션이 사용되면 복잡한 상황이 발생한다. 하지만 스프링은 논리 트랜잭션이라는 개념을 도입함으로써 상황에 대한 설명을 쉽게 만들고, 다음과 같은 원칙을 세울수 있다.

  • 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋됨.
  • 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백됨.

논리 트랜잭션을 기반으로 단순한 원칙을 세움으로써 2개 이상의 트랜잭션을 다루는 경우에 대한 이해가 상당히 쉬워진다.

1.5 다양한 스프링의 트랜잭션 전파 속성

“트랜잭션 전파(Propagation)”란,

이미 진행 중인 트랜잭션이 있을 때 새로 시작된 메서드가 그 트랜잭션에 참여할지(join) 아니면 새로운 트랜잭션을 새로 만들지를 결정하는 규칙이다.

1.5.1 REQUIRED

  • 의미 : 트랜잭션이 필요한 경우 트랜잭션이 없으면 새로 만들고 있으면 기존 트랜잭션에 참여함.
  • 기존 트랜잭션이 없다면 새로운 트랜잭션을 시작하고
    • 기존 트랜잭션이 있다면 기존 트랜잭션에 추가 시킨다.
  • 1개의 물리 트랜잭션 안에 2개의 논리 트랜잭션까지 확장된다.

  • 물리 트랜잭션 = 1개
  • 논리 트랜잭션 = 2개 (outer, inner)

내부 트랜잭션이 참여할 때 벌어지는 일

  • 이때 스프링은 이렇게 동작한다.
    1. outer() 실행 시 → 물리 트랜잭션 시작
    2. inner() 실행 시 → “이미 트랜잭션이 있네?” 하고
      • 새로 만들지 않고 외부 트랜잭션에 참여(join)
    • 이제 outer()inner()하나의 물리 트랜잭션을 공유하지만, @Transactional 단위로 각각 논리 트랜잭션이 존재한다.
    • 모든 @Transactional은 AOP 프록시로 감싸져 있어서,
      • 자기 경계가 끝나면 트랜잭션 매니저에게 커밋을 요청한다.

        그래서 아래처럼 된다.

    1. inner() 종료 → 트랜잭션 매니저에 “커밋 요청”
    2. outer() 종료 → 다시 “커밋 요청”
      - 트랜잭션 매니저: “모든 참여자 정상 → 실제 DB 커밋 수행”
    3. 모든 논리 트랜잭션 종료
      - outer, inner 모두 성공 상태로 종료

💡inner() 에서 예외가 터져서 롤백이 발생한다면?
트랜잭션 매니저는 내부 트랜잭션에서 문제가 발생했음을 체크한다.
- “rollback-only” 플래그를 설정
그 상태에서 외부 트랜잭션이 커밋을 시도하면, 매니저는 이미 내부에서 롤백됐음을 감지하고 전체 롤백을 수행한다.

💡outer() 에서 예외가 터져 롤백이 발생한다면?
트랜잭션 매니저는 외부 트랜잭션의 실패를 감지하고 현재 진행 중인 물리 트랜잭션을 롤백한다.

  • 내부 트랜잭션이 있었다면 같은 물리 트랜잭션을 공유함하므로 함께 롤백된다. (REQUIRED)
  • 별도의 트랜잭션으로 이미 커밋되었다면 이미 커밋되어 유지된다. (REQUIRES_NEW)

1.5.2 REQUIRES_NEW

  • 의미 : 외부 트랜잭션과 내부 트랜잭션을 완전히 분리하는 전파 속성이다.
  • 각각 트랜잭션 별로 커밋과 롤백이 수행된다.

  • 서로 다른 물리 트랜잭션을 별도로 가지기 때문에 각각의 db 커넥션을 사용하는데, 즉 1개의 http요청에 2개의 커넥션이 사용된다.
    • 따라서 병목이 발생하는 구간에 대기하는 트랜잭션이 있다면 db 커넥션을 고갈 시킬 수 있다.

1.5.3 SUPPORTS

  • 의미: 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행한다.
  • 특징:
    • 상황에 따라 유연하게 동작 (트랜잭션 의존도가 낮은 코드에 적합)
    • 트랜잭션이 없어도 예외가 발생하지 않음
    • “읽기 전용” 쿼리에 자주 사용

💡SUPPORTS는 트랜잭션이 있으면 자엽스럽게 참여하고, 없으면 Auto Commit 모드로 즉시 실행하는 구조다.
→ 데이터 변경 작업(INSERT/UPDATE/DELETE) 에는 부적합

ex) 핵심이 아닌 로직을 트랜잭션 경계에서 유연하게 처리 할 수 있다.


1.5.4 NOT_SUPPORTED

  • 의미: 트랜잭션이 있으면 일시 중단(suspend) 하고, 트랜잭션 없이 실행한다.
  • 특징:
    • 트랜잭션 경계 밖에서 실행됨
    • 커밋/롤백의 영향을 받지 않음
    • 외부 트랜잭션과 분리된 “독립적인 비트랜잭션 작업”

💡이미 진행 중인 트랜잭션이 있더라도 그 트랜잭션을 잠시 중단하고 Auto Commit 모드로 별도로 실행한다.
즉 트랜잭션이 커밋되거나 롤백되어도 이 메서드의 작업 결과를 영향을 받지 않는다.
그래서 비즈니스 트랜잭션과 분리되어야 하는 부수 로직에 자주 사용된다.

ex) 실패 하더라도 본 트랜잭션의 롤백에 휘룰리면 안되는 로직에서 사용된다.


1.5.5 MANDATORY

  • 의미: 반드시 트랜잭션 안에서 실행되어야 하며, 없으면 예외 발생.
  • 특징:
    • “트랜잭션 내부 전용” 메서드에 사용
    • 외부에서 트랜잭션을 시작하지 않으면 IllegalTransactionStateException 발생

💡새로운 트랜잭션을 만들지 않고, 항상 이미 존재하는 트랜잭션에 참여해야만 동작한다.

즉, 트랜잭션 경계 내부에서만 실행되도록 강제하는 정책적 제약이다.(트랜잭션 없이 실행되면 예외를 던짐)
이 설정은 해당 로직이 반드시 커밋/롤백 단위로 함께 묶여야 한다는 의도를 명확히 표현할 때 사용된다.


1.5.6 NEVER

  • 의미: 트랜잭션이 존재하면 안 되며, 트랜잭션이 있으면 예외 발생.
  • 특징:
    • “트랜잭션 비허용” 로직 (로그 남기기, 모니터링 등)에 사용
    • 기존 트랜잭션이 있으면 즉시 실패

💡트랜잭션 경계 밖에서만 실행되도록 강제하는 옵션이다. 트랜잭션이 걸린 상태에서 실행되면 오히려 비즈니스 영향을 주거나 불필요한 락, 롤백 전파가 발생할 수 있기 때문이다.

ex)비즈니스 트랜잭션과 묶이면 안 되는(non-transactional)대표적인 케이스에서 사용된다.
따라서 트랜잭션이 걸려 있으면 예외를 발생시켜, 절대 트랜잭션 안에서 실행되지 않게 만든다.


1.5.7 NESTED

  • 의미:
    외부 트랜잭션 안에서 저장점(Savepoint) 을 만들어 부분 롤백이 가능한 중첩 트랜잭션을 수행한다.
  • 특징:
    • 외부 트랜잭션이 롤백되면 내부도 롤백됨
    • 내부 트랜잭션만 롤백해도 외부는 커밋 가능
    • JDBC Savepoint를 지원해야 동작 (H2, PostgreSQL 지원 / 일부 DB 미지원)

💡하나의 물리 트랜잭션 안에서 부분 복구가 필요한 경우 사용된다. 기본적으로 Spring 은 논리 트랜잭션만 관리하지만 NESTED는 그 내부에서 저장점을 만들어 특정 구간만 롤백할 수 있게 해준다.

NESTED 는 REQUIRED 처럼 같은 커넥션을 공유하지만, 내부 트랜잭션 실패 시 전체를 날리지 않고 부분적으로 북구할 수 있다는 점이 다르다.


2. 트랜잭션 사용법 정리

스프링에서 트랜잭션을 관리하는 방법은 크게 세 가지가 있다.

가장 많이 쓰는 건 @Transactional인데, 사실 그 아래엔 PlatformTransactionManager라는 애가 실제로 트랜잭션을 관리하고 있다.


2.1 @Transactional (선언적 방식)

그냥 메서드 위에 @Transactional 붙이면 스프링이 알아서 트랜잭션을 열고 닫는다.

@Transactional(transactionManager = "transactionManager1")
public void saveData() {
    jdbcTemplate.update("INSERT INTO table_a(value) VALUES ('A')");
}

내가 따로 commit()이나 rollback()을 호출하지 않아도 된다.

스프링이 내부적으로 PlatformTransactionManager를 찾아서 메서드 시작할 때 트랜잭션을 열고,

정상 끝나면 commit, 예외 나면 rollback 해준다.

“@Transactional은 PlatformTransactionManager를 자동으로 불러주는 도우미 역할”

그래서 트랜잭션 매니저를 여러 개 쓸 땐 transactionManager = "이름" 이렇게 명시해줘야

스프링이 어떤 매니저를 쓸지 헷갈리지 않는다.

→ 스프링 컨테이너에 등록된 Bean 이름을 명시한다


2.2 TransactionTemplate (명시적 방식)

조금 더 직접 제어하고 싶으면 TransactionTemplate을 쓴다.

이건 코드로 트랜잭션 범위를 감쌀 수 있다.

transactionTemplate.execute(status -> {
    jdbcTemplate.update("INSERT INTO table_a(value) VALUES ('A')");
    return null;
});

이 안에서 예외가 나면 자동으로 롤백된다.

성공하면 commit.

사실 내부적으로도 똑같이 PlatformTransactionManager를 사용한다.

그냥 코드로 명확하게 보이게 쓴 버전이라고 보면 된다.


2.3 PlatformTransactionManager 직접 사용 (완전 수동)

마지막은 진짜 수동으로,

스프링이 대신 해주던 걸 내가 직접 하는 방법이다.

TransactionStatus tx = txManager.getTransaction(new DefaultTransactionDefinition());
try {
    jdbcTemplate.update("INSERT INTO a_tbl(v) VALUES ('A')");
    txManager.commit(tx);
} catch (Exception e) {
    txManager.rollback(tx);
}

이게 바로 @Transactional이 내부에서 실제로 하는 일이다.

스프링은 이 코드를 AOP 프록시로 감싸서 자동으로 실행해주는 것뿐이다.

그래서 트랜잭션 동작 원리를 배우고 싶을 땐 이걸 직접 써보는 게 제일 좋다.


PlatformTransactionManager란?

스프링 트랜잭션의 “진짜 관리자”. 트랜잭션을 시작하고, 커밋하고, 롤백하는 주체다.

우리가 어떤 기술을 쓰느냐에 따라 구현체가 다르다.

  • JDBC → DataSourceTransactionManager
  • JPA → JpaTransactionManager
  • Hibernate → HibernateTransactionManager
  • 여러 DB(분산 트랜잭션) → JtaTransactionManager

PlatformTransactionManager는 트랜잭션의 공통 인터페이스고,

실제 동작은 각각의 기술에 맞는 매니저가 맡는다.

0개의 댓글