Back-end/Spring

[Spring] @Transactional 내부 동작 원리

랑 이 2026. 3. 10. 11:39
반응형

Spring Boot로 백엔드 개발을 하면서 자주 사용되는 어노테이션 중 하나가 @Transactional입니다

이번에 정보처리기사,SQLD를 취득하기 위해서 공부를 하다 보니 트랜잭션(Transaction)에 관심이 많이 가지게 되었는데

 

Spring에서 트랜잭션을 다루는 어노테이션 @Transactional의 내부 동작 원리에 대해 알아보려고 한다

 

보통 @Transactional 을 해당 메서드를 하나의 트랜잭션으로 실행한다라고 간단하게 생각하며 사용한다

하지만 실제로는 Spring 내부에서 여러 컴포넌트가 결합하여 트랜잭션을 관리한다

 

먼저 트랜잭션에 대해서 짚고 넘어가 보려고 한다 트랜잭션은 무엇이고 어떤 특징을 가지며 왜 사용되는지 간단하게 알아본다 

트랜잭션(Transaction)

트랜잭션은 데이터베이스에서 하나의 작업 단위를 의미하며 여러 개의 데이터 변경 작업을 하나의 논리적인 단위로 묶어 처리하는 개념

은행 계좌 이체를 생각해 보면 한 계좌에서 돈을 출금하고 다른 계좌에 입금하는 두 작업은 반드시 모두 성공하거나 모두 실패해야 한다
만약에 출금만 성공하고 입금이 실패하게 된다면 데이터의 정합성이 깨지게 된다

이러한 문제를 방지하기 위해서 데이터베이스에서는 트랜잭션을 사용하며
트랜잭션은 작업의 일관성과 안정성을 보장하기 위해 몇 가지 중요한 특징을 가진다

트랜잭션의 특징 (ACID)

데이터베이스에서 트랜잭션은 4가지 중요한 특징을 가진다

(Atomicity, Consistency, Isolation, Durability) 

 

1. 원자성 (Atomicity)

원자성은 트랜잭션에 포함된 작업들이 모두 성공하거나 모두 실패해야 한다는 특성

 

트랜잭션의 내부의 작업은 일부만 수행될 수 없다 작업 중 하나라도 실패하면 전체 트랜잭션은 롤백(Rollback) 되어 이전 상태로 돌아간다

 

2. 일관성 (Consistency)

일관성은 트랜잭션이 실행되기 전과 후에 데이터베이스가 항상 유요한 상태를 유지해야 한다는 특성

데이터베이스에 정의된 제약 조건(Constraint) 이 항상 만족되어야 한다

 

3. 격리/고립성 (Isolation)

격리성은 동시에 여러 트랜잭션이 실행될 때 각 트랜잭션이 서로의 작업에 영향받지 않도록 하는 특성

주로 낙관적/비관적 락을 활용하여 트랜잭션의 간섭을 제어한다

 

4. 지속성(Durability)

지속성은 트랜잭션이 성공적으로 Commit 되면 그 결과가 영구적으로 저장되는 특성이다

트랜잭션이 완료된 후에는 시스템 장애가 발생해도 데이터가 사라지지 않고 영구적이다

1. @Transactional은 어디서 동작할까

@Transactional은 메서드 자체 내부에서 직접 동작하지 않고 Spring이 생성한 프록시(Proxy) 객체에서 동작하는 방식이다

 

우리가 @Transactional을 붙인 메서드를 호출할 때 실제로는 대상 객체의 메서드가 실행되는 것이 아닌 

먼저 프록시 객체가 호출을 가로채고 그 과정에서 트랜잭션을 실행,커밋,롤백을 제어한다

@Transactional이 동작하는 핵심 구조

  1. 서비스 메서드에 @Transactional을 정의
  2. Spring 컨테이너가 해당 빈을 등록할 때 @Transactional 어노테이션이 존재하는지 확인
  3. 트랜잭션 대상이라면 원본 객체 대신 프록시 객체를 생성한다
  4. 클라이언트는 원본 객체가 아닌 프록시 객체를 통해 메서드를 호출
  5. 프록시는 메서드 호출 전 트랜잭션을 시작하여 (정상 종료 -> commit | 예외 발생 -> rollback)

2. 실제 동작 구조

다음과 같이 주문 비즈니스 로직을 처리하는 OrderService 클래스를 사용한다고 가정한다

@Service
public class OrderService {

    @Transactional
    public void createOrder() {
        // 주문 저장
        // 재고 차감
        // 결제 처리
    }
}

createOrder() 메서드에 직접 트랜잭션이 작동하는 것처럼 보이지만 실제로는 다른 구조로 동작하게 된다

원본 객체

public class OrderService {
    public void createOrder() {
        // 실제 비즈니스 로직
    }
}

프록시 객체

public class OrderServiceProxy {

    private final OrderService target;
    private final TransactionManager transactionManager;

    public void createOrder() {
        try {
            transactionManager.begin();
            target.createOrder();
            transactionManager.commit();
        } catch (Exception e) {
            transactionManager.rollback();
            throw e;
        }
    }
}

실제로는 OrderServiceProxy 프록시 객체가 생성되어 해당 객체를 호출하여 트랜잭션을 관리하는 방식이다

 

- 비즈니스 로직은 원본 객체가 담당

- 트랜잭션 시작/커밋/롤백은 프록시가 담당하는 구조

3. Spring은 어떤 프록시를 사용할까?

보통 두 가지 방식을 사용한다고 알려져 있다

1. JDK Dynamic Proxy

인터페이스를 기반으로 프록시를 생성하는 방식이다

interface

public interface OrderService {
    void createOrder();
}

Service Class

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    @Override
    public void createOrder() {
    }
}

Spring에서 인터페이스 기반 설계를 많이 사용하는데 이는 구현체에 대한 의존성을 줄이고 확장성을 높여준다

해당 방식을 사용했을 때 JDK Dynamic Proxy를 통해 Proxy 객체를 생성하여 동작하는 구조이다

2. CGLIB Proxy

인터페이스를 구현하지 않는 클래스라면 Spring은 CGLIB를 사용하여 프록시를 생성한다

클래스를 상속하여 프록시를 생성하는 방식으로 다음과 비슷하게 동작한다

public class OrderService$$EnhancerBySpringCGLIB extends OrderService {
    @Override
    public void createOrder() {
        // 트랜잭션 시작
        super.createOrder();
        // 커밋 또는 롤백
    }
}

4. 내부 처리 주체

@Transactional은 단순한 마커가 아니라, Spring의 트랜잭션 인프라와 연결되어 동작한다 

대표적으로 다음 요소들이 관여한다

 

1. TransactionInterceptor

실제로 메서드 호출을 가로채어 트랜잭션 처리를 수행하는 인터셉터

  • 어떤 메서드에 @Transactional 어노테이션이 정의되어 있는지 확인
  • 트랜잭션 속성 (readOnly = true)을 읽고 트랜잭션 매니저를 통해 실행/커밋/롤백을 수행

2. PlatformTransactionManager

Spring이 제공하는 트랜잭션 추상화 인터페이스

다음과 같은 구현체가 존재한다

  • DataSourceTransactionManager
  • JpaTransactionManager

트랜잭션 매니저가 실제 DB 커넥션 또는 JPA 영속성 컨텍스트 단위로 트랜잭션을 관리한다

 

3. TransactionAttributeSource

@Transactional의 속성을 읽는 역할을 한다

@Transactional(
    readOnly = true,                        // 해당 트랜잭션을 읽기 전용으로 설정
    rollbackFor = Exception.class,          // Exception 발생 시 트랜잭션을 롤백하도록 설정
    propagation = Propagation.REQUIRES_NEW  // 항상 새로운 트랜잭션을 생성
)

5. 그래서 정확히 동작 위치는?

@Transactional은 실제 비즈니스 메서드 내부에서 동작하지 않으며 Spring 컨테이너가 생성한 프록시 객체에서 동작한다

프록시에 연결된 TransactionInterceptor가 동작하게 되며 @Transactional은 어노테이션을 해석하여 실행하는 Spring의 AOP  트랜잭션 인프라로 설명할 수 있다

6. @Transactional 실행 제약 사항

프록시는 기본적으로 외부에서 호출 가능한 메서드를 가로채는 방식으로 동작하게 된다

그러므로 보통 다음과 같은 제약 사항이 존재한다

 

  • private 메서드: 프록시 적용 어려움
  • final 메서드: CGLIB 오버라이딩 불가
  • final 클래스: CGLIB 상속 불가

 

일반적으로 @Transactional을 사용하는 서비스 메서드의 접근 제한자는 public으로 관리해야 안전하다

반응형