LostCatBox

Transaction 전파단계와 TransactionManager에 대해서

Word count: 1.3kReading time: 8 min
2025/11/26 Share

참조

  • spring 에서 전파단계를 학습하여, Transaction을 더 잘 다뤄보자
  • JDBC, JPA 의 차이를 알아보자

사전 지식

  • JPA

    ORM 표준 스펙(인터페이스 규격)

    → EntityManager, EntityManagerFactory, EntityTransaction 같은 인터페이스 정의만 함.

  • Hibernate

    JPA 구현체 + 자기만의 ORM 프레임워크

    → JPA가 정의한 EntityManager 인터페이스를 구현하고, 내부에서 SQL 만들고 실행함.

  • EntityManager

    → JPA에서 정의한 API 인터페이스

    → persist, find, merge, remove, createQuery, flush 등을 제공

    → Spring 환경에서는 실제 구현체가 Hibernate의 Session을 감싼 구현 클래스.

  • JDBC API

    → Java 표준 DB 접근 인터페이스

    → Connection, PreparedStatement, ResultSet 등

    → Hibernate가 결국 여기까지 내려와서 SQL 실행.

  • JDBC Driver

    → 각 DB 벤더가 제공하는 JDBC API의 실제 구현체

    → MySQL, PostgreSQL, Oracle 등.

    → JDBC 호출을 실제 DB 프로토콜로 변환해서 DB 서버랑 통신.

  • TransactionManager (Spring)

    → 스프링이 제공하는 트랜잭션 제어 추상화 인터페이스

    → 구현체 예시:

    • JDBC용: DataSourceTransactionManager

    • JPA용: JpaTransactionManager

      트랜잭션 시작 / 커밋 / 롤백을 책임지고,

      JPA/Hibernate + JDBC + Connection을 한 덩어리로 묶어서 관리.
      getTransaction() 메서드를 보면 entityManager 생성 또는 기존 것 사용하는 코드 존재

기본지식 (JDBC, JPA)

스크린샷 2025-11-27 20.53.05

JDBC (Java Database Connectivity)

  • Java 프로그래밍 언어와 광범위한 데이터베이스 사이의 연결을 제공하는 자바 API (인터페이스+클래스로 구성)
  • 해당 JDBC API를 DB별로 알맞게 구현한 JDBC 드라이버를 활용하여 DB에 접근한다.
  • 직접 사용 시, 해당 DB에 맞는 SQL 문은 직접 작성해야한다.

JPA

  • JPA는 Java Persist API의 약자로 Java ORM 기술 표준 (인터페이스)
  • 해당 JPA를 알맞게 구현한 hibernate 등의 구현체로 JDBC API를 활용한다.

ORM(Object Relation Mapping)이란?

ORM은 지속성을 유지하기 위해 도입된 개념으로, POJO 자바 클래스를 관계형 데이터베이스에 맵핑하는 기술을 의미한다. 쉽게 말하자면, 순수 자바 클래스와 데이터베이스의 데이터가 서로 대응되게 만든다는 뜻이다.

Transaction이란?

트랜잭션은 반드시 시작-종료를 갖는다

트랜잭션은 하나의 connection을 가져와 사용하다가 닫는 사이에서 일어난다.

  • 시작 방법은 1가지(connection)

  • 종료방법은 2가지(commit, rollback)

JDBC 사용 시

1
2
3
4
5
6
7
8
9
@Test
@DisplayName("DataSource를 이용해 수동으로 트랜잭션을 관리할 수 있다.")
public void executeQuery() throws SQLException {
Connection connection = dataSource.getConnection();
connection.setAutoCommit(false);
// 쿼리 실행
connection.commit();
connection.close();
}

Spring 사용 시

스프링을 이용하면 내부적으로 커넥션을 갖고 있는 추상화된 트랜잭션 매니저를 이용하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
@DisplayName("TransactionManager를 이용해 수동으로 트랜잭션을 관리할 수 있다.")
public void executeQueryWithTransactionManager() throws SQLException {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 쿼리 실행
// 트랜잭션 종료
transactionManager.commit(status);
} catch (Exception e) {
// 오류 발생 시 롤백
// 트랜잭션 종료
transactionManager.rollback(status);

}
}

TransactionManger란?

스프링은 트랜잭션 처리를 TransactionManager 객체를 통해 처리합니다.

트랜잭션을 시작하고, 커밋하거나 롤백하는 트랜잭션 제어 책임 객체

PlatformTransactionManager 인터페이스이며, 구현체를 런타임에 주입합니다.

1
2
3
4
5
6
7
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;

void commit(TransactionStatus var1) throws TransactionException;

void rollback(TransactionStatus var1) throws TransactionException;
}

스크린샷 2025-11-30 16.02.41

Transaction 전파단계에 대해

트랜잭션 전파란?

Spring이 제공하는 선언적 트랜잭션(@Transactional)의 장점 중 하나는 여러 트랜잭션을 묶어서 커다란 하나의 트랜잭션 경계를 만들수 있다는 점이다.

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

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

  • 물리 트랜잭션 : 실제 DB에 적용되는 트랜잭션으로 커넥션을 통해 커밋/롤백하는 단위

    트랜잭션은 데이터베이스에서 제공하는 기술이므로 커넥션 객체를 통해 처리한다.
    그래서 1개의 트랜잭션을 사용한다는 것은 하나의 커넥션 객체를 사용한다는 것이고,
    실제 데이터베이스의 트랜잭션을 사용한다는 점에서 물리 트랜잭션이라고도 한다.
    물리 트랜잭션은 실제 커넥션에 롤백/커밋을 호출하는 것으로 해당 트랜잭션이 끝난다.

  • 논리 트랜잭션 : 스프링이 트랜잭션 매니저를 통해 트랜잭션을 처리하는 단위

    DB 트랜잭션과 구별되는 스프링 입장에서 트랜잭션 매니저를 통해 트랜잭션을 뜻한다.

    이는 물리 트랜잭션의 갯수와 다를수있다.

    트랜잭션 전파 속성에 따라서 논리 트랜잭션 2개-> 물리 트랜잭션 1개인 상황을 나타낸다.
    스크린샷 2025-11-30 16.37.37

    • 스프링에서 논리 트랜잭션의 원칙

      1. 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋됨

      2. 같은 물리 트랜잭션을 공유하는 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백됨

@Transactional 전파 단계 설정 시 Spring에서 발생하는 내부 로직

  • @Transactional 가 존재하는 MyService.method() 호출 전 발생하는 내부 로직
1
2
3
4
5
6

MyService.method() 호출 → 프록시의 invoke()

TransactionInterceptor → invokeWithinTransaction()

PlatformTransactionManager → getTransaction() / commit() / rollback()
  • PlatformTransactionManager.getTransaction()전파 단계 관련 처리 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 생략
// 해당 부분에서 현재 쓰레드에서 이미 시작된 트랜잭션이 있는지 확인하고, 트랜잭션 객체(여기서는 JpaTransactionObject)를 생성해 반환합니다.
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
// 기존에 Transaction이 존재한다면, Propagation에 맞게 어떻게 트랜잭션을 다룰지 결정합니다.(핸들링)
return handleExistingTransaction(def, transaction, debugEnabled);
}
// 기존 Transaction없을 시, 등등 케이스에 대해서 비즈니스 로직 존재(생략)
}
  • 참고) EntityManager 에 대해서

    트랜잭션이 시작될 때 entityManagerFactory에서 엔티티 매니저를 생성하고 엔티티의 영속성을 관리한다.

    그러나 transaction 종료시 commit 이후
    모든 영속성 관리는 해제되기 때문에 이를 인자로 다른 트랜잭션 내부에서 사용해도 persist 상태가 아니기 때문에 따로 merge를 해줘야 한다.

    JpaTransactionManager.doBegin() 코드

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {

    JpaTransactionObject txObject = (JpaTransactionObject) transaction;

    if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
    throw new IllegalTransactionStateException(
    "Pre-bound JDBC Connection found! JpaTransactionManager does not support " +
    "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
    "It is recommended to use a single JpaTransactionManager for all transactions " +
    "on a single DataSource, no matter whether JPA or JDBC access.");
    }

    try {
    if (!txObject.hasEntityManagerHolder() ||
    txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
    EntityManager newEm = createEntityManagerForTransaction();
    if (logger.isDebugEnabled()) {
    logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
    }
    txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
    }

    EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
    //...이하 생략
    }

테스트 상황

둘다 @Transactional을 의존하며,

PaymentService.processPayment() -> PointService.usePoint()를 호출한다.

1
2
3
graph LR

PaymentService.processPayment --> PointService.usePoint

테스트 방법

usePoint() 메서드의 @Transactional(propagation = XXX) 전파 단계를 바꿔가며,

상황 및 에러를 확인해보자

전파 단계별 특징과 예시

REQUIRED

  • 의미: 트랜잭션이 필요함(없으면 새로 만듬)

    • 기존 트랜잭션 없음: 새로운 트랜잭션을 생성함
    • 기존 트랜잭션이 있음: 기존 트랜잭션에 참여함
      • 외부 트랜잭션 1개에는 내부 트랜잭션 2개 포함됨
  • 예시

    • 각 메서드의 @Transactional 설정

      • processPayment()

        • @Transactional
        • Try-catch 문으로 usePoint()의 예외 처리됨
      • usePoint()

        • @Transactional
        • throw IllegalArgumentException("Treansactional 테스트용 예외")
    • 해당 전파단계에서의 발생된 에러

      1
      org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only

REQUIRES_NEW

  • 의미: 항상 새로운 트랜잭션이 필요함
    • 기존 트랜잭션 없음: 새로운 트랜잭션을 생성함
    • 기존 트랜잭션이 있음: 기존 트랜잭션을 보류시키고 새로운 트랜잭션을 생성함
      • 외부 트랜잭션 2개에 각각 내부 트랜잭션 1개씩 포함됨.
  • 예시
    • 각 메서드의 @Transactional 설정
      • processPayment()
        • @Transactional
        • Try-catch 문으로 usePoint() 예외 처리완료.
      • usePoint()
        • @Transactional(propagation = Propagation.REQUIRES_NEW)
        • throw IllegalArgumentException("Treansactional 테스트용 예외")
    • 이 전파단계에서의 에러
      • 없음. -> rollback-only 마크가 상위 트랜잭션에게 전달되지않는다.

SUPPORTS

  • 의미: 트랜잭션이 있으면 지원함(트랜잭션이 없어도 됨)

    • 기존 트랜잭션 없음: 트랜잭션 없이 진행함
    • 기존 트랜잭션이 있음: 기존 트랜잭션에 참여함
      • 외부 트랜잭션 1개에 2개의 내부 트랜잭션 포함됨
  • 예시

    • 각 메서드의 @Transactional 설정

      • processPayment()
        • @Transactional
        • Try-catch 문으로 usePoint() 예외 처리완료.
      • usePoint()
        • @Transactional(propagation = Propagation.SUPPORTS)
        • throw IllegalArgumentException("Treansactional 테스트용 예외")
    • 이 전파단계에서의 에러 -> 기존 트랜잭션 존재했으므로 rollback-only marked

      1
      org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only

NOT_SUPPORTED

  • 의미: 트랜잭션을 지원하지 않음(트랜잭션 없이 진행함)

    • 기존 트랜잭션 없음: 트랜잭션 없이 진행함
    • 기존 트랜잭션이 있음: 기존 트랜잭션을 보류시키고 트랜잭션 없이 진행함
      • 외부 트랜잭션 1개에 1개의 내부 트랜잭션이 존재
  • 예시

    • 각 메서드의 @Transactional 설정

      • processPayment()

        • @Transactional
      • usePoint()

        • @Transactional(propagation = Propagation.NOT_SUPPORTED)
        • SQL 쿼리문 실행
    • 이 레벨에서의 에러 : Transaction 이 없어서 쿼리 실행불가

    1
    jakarta.persistence.TransactionRequiredException: Query requires transaction be in progress, but no transaction is known to be in progress

MANDATORY

  • 의미: 트랜잭션이 의무임(트랜잭션이 반드시 필요함)

    • 기존 트랜잭션 없음: IllegalTransactionStateException 예외 발생
    • 기존 트랜잭션이 있음: 기존 트랜잭션에 참여함
      • 외부 트랜잭션 1개에 2개의 내부 트랜잭션 포함됨
  • 예시

    • 각 메서드의 @Transactional 설정

      • processPayment()
        • X
      • usePoint()
        • @Transactional(propagation = Propagation.MANDATORY)
        • SQL 쿼리문 실행
    • 이 전파단계에서의 에러 :

      1
      org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

NEVER

  • 의미: 트랜잭션을 사용하지 않음(기존 트랜잭션도 허용하지 않음)

    • 기존 트랜잭션 없음: 트랜잭션 없이 진행
    • 기존 트랜잭션이 있음: IllegalTransactionStateException 예외 발생
      • 외부 트랜잭션 1개와 내부 트랜잭션 1개 존재 시 Exception 발생
  • 예시

    • 각 메서드의 @Transactional 설정
      • processPayment()
        • @Transactional
      • usePoint()
        • @Transactional(propagation = Propagation.NEVER)
        • SQL 쿼리문 실행
    • 이 전파단계에서의 에러 :
    1
    org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
CATALOG
  1. 1. 참조
  2. 2.
  3. 3. 사전 지식
  4. 4. 기본지식 (JDBC, JPA)
    1. 4.1. JDBC (Java Database Connectivity)
    2. 4.2. JPA
  5. 5. Transaction이란?
    1. 5.1. JDBC 사용 시
    2. 5.2. Spring 사용 시
  6. 6. TransactionManger란?
  7. 7. Transaction 전파단계에 대해
    1. 7.1. 트랜잭션 전파란?
    2. 7.2. 물리 트랜잭션과 논리 트랜잭션
    3. 7.3. @Transactional 전파 단계 설정 시 Spring에서 발생하는 내부 로직
    4. 7.4. 테스트 상황
    5. 7.5. 테스트 방법
    6. 7.6. 전파 단계별 특징과 예시
      1. 7.6.1. REQUIRED
      2. 7.6.2. REQUIRES_NEW
      3. 7.6.3. SUPPORTS
      4. 7.6.4. NOT_SUPPORTED
      5. 7.6.5. MANDATORY
      6. 7.6.6. NEVER