트랜잭션(Transaction)과 Spring의 @Transactional 이해하기 그리고 Proxy?

이상민·2024년 10월 1일
0

트랜잭션이란?

데이터베이스에서 트랜잭션은 하나의 논리적 작업 단위를 말한다. 즉, 여러 작업이 하나의 트랜잭션으로 묶여서 성공하면 모두 반영되고, 실패하면 모두 롤백되는 원자성을 가진다. 트랜잭션은 데이터 일관성을 보장하기 위해 다음과 같은 ACID 특성을 가진다:

  1. Atomicity (원자성): 모든 작업이 성공하거나 전부 실패하는 것.

  2. Consistency (일관성): 트랜잭션이 끝난 후에도 데이터베이스는 항상 일관성 있는 상태여야 한다.

  3. Isolation (고립성): 동시에 여러 트랜잭션이 실행되더라도 각각의 트랜잭션은 독립적으로 처리된다.

  4. Durability (지속성): 트랜잭션이 성공적으로 끝나면 그 결과는 영구적으로 저장된다.

Spring에서의 트랜잭션 관리

Spring 프레임워크는 @Transactional 어노테이션을 통해 트랜잭션을 선언적으로 관리할 수 있게 해준다. 이 어노테이션은 클래스나 메서드에 적용할 수 있으며, Proxy 패턴을 사용해 트랜잭션의 범위를 제어한다.

주요 옵션

  • propagation: 트랜잭션이 다른 트랜잭션과 어떻게 상호작용할지 설정.

  • isolation: 트랜잭션 간의 고립 수준을 설정.

  • timeout: 트랜잭션의 제한 시간을 설정.

  • readOnly: 읽기 전용 트랜잭션으로 설정해 성능을 최적화.

  • rollbackFor: 특정 예외 발생 시 롤백하도록 설정.

  • noRollbackFor: 특정 예외에 대해 롤백하지 않도록 설정.

Propagation(전파 수준)

트랜잭션 전파는 현재 트랜잭션이 다른 트랜잭션과 어떤 관계를 가지는지를 결정한다. Spring은 다양한 전파 수준을 제공한다.

  1. REQUIRED: 이미 진행 중인 트랜잭션이 있으면 그 트랜잭션을 사용하고, 없으면 새 트랜잭션을 시작한다. 기본값이다.

  2. REQUIRED_NEW: 항상 새 트랜잭션을 시작하고, 기존 트랜잭션은 일시 중단한다.

  3. SUPPORTS: 트랜잭션이 존재하면 그 안에서 실행하고, 없으면 트랜잭션 없이 실행한다.

  4. MANDATORY: 현재 트랜잭션이 반드시 있어야 하며, 없으면 예외를 발생시킨다.

  5. NEVER: 트랜잭션 없이 실행되어야 하며, 트랜잭션이 있으면 예외를 발생시킨다.

  6. NESTED: 진행 중인 트랜잭션 안에서 중첩 트랜잭션을 실행하며, Savepoint를 이용할 수 있다.

예시: REQUIRED vs REQUIRED_NEW

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    methodB(); // 같은 트랜잭션 내에서 실행
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // 항상 새로운 트랜잭션에서 실행
}

위 코드에서 methodA는 기존 트랜잭션을 사용하지만, methodB는 새로운 트랜잭션을 생성한다.

Isolation(고립 수준)

고립 수준은 여러 트랜잭션이 동시에 실행될 때 서로 간섭하지 않도록 설정하는 것이다. SQL에서 발생할 수 있는 트랜잭션 문제를 방지하기 위해 고립 수준을 조정한다.

  1. READ_UNCOMMITTED: 다른 트랜잭션이 커밋되지 않은 데이터를 읽을 수 있다.

  2. READ_COMMITTED: 커밋된 데이터만 읽을 수 있다.

  3. REPEATABLE_READ: 트랜잭션 내에서 같은 데이터를 여러 번 읽어도 항상 같은 결과를 보장한다.

  4. SERIALIZABLE: 트랜잭션을 순차적으로 실행해 가장 높은 고립 수준을 제공하지만, 성능이 떨어질 수 있다.

Proxy란 무엇인가?

Proxy는 한국어로 '대리인'을 의미하며, 소프트웨어에서는 다른 객체의 대리자 역할을 하는 객체를 뜻한다. 즉, 어떤 객체에 접근하기 전 그 객체 앞에 Proxy 객체를 두어 추가적인 작업을 처리하거나, 실제 객체에 대한 접근을 제어하는 역할을 한다.

Spring에서의 Proxy
Spring은 트랜잭션 관리, AOP, 보안, 캐싱 등의 다양한 기능을 Proxy 패턴을 사용하여 구현한다. 특히 트랜잭션 관리를 할 때, @Transactional이 선언된 메서드를 호출하면 Spring이 자동으로 Proxy 객체를 생성해 실제 비즈니스 로직을 감싸서 실행한다.

Proxy가 동작하는 방식

  1. 메서드 호출 시 Proxy 개입: 클라이언트 코드가 @Transactional이 붙은 메서드를 호출할 때, 실제 비즈니스 로직을 바로 실행하는 것이 아니라, 먼저 Proxy가 해당 메서드를 가로챈다.

  2. 트랜잭션 처리: Proxy는 메서드 실행 전에 트랜잭션을 시작하고, 메서드 실행 후 정상적으로 완료되면 트랜잭션을 커밋하거나, 예외가 발생하면 트랜잭션을 롤백하는 작업을 자동으로 처리한다.

  3. 실제 메서드 실행: Proxy가 트랜잭션 시작을 처리한 후에야 실제 비즈니스 로직을 실행한다. 이 모든 과정이 클라이언트 코드에서 보이지 않기 때문에, 우리는 마치 메서드를 그냥 호출하는 것처럼 간단하게 트랜잭션 관리를 적용할 수 있다.

예시: Proxy의 트랜잭션 관리

@Service
public class MyService {

    @Transactional
    public void doSomething() {
        // 트랜잭션이 Proxy에 의해 관리됨
        // 여기서는 비즈니스 로직만 신경 쓰면 됨
    }
}

위의 MyService 클래스에서 doSomething() 메서드는 @Transactional에 의해 트랜잭션이 관리된다. 트랜잭션 시작, 커밋, 롤백 등은 Proxy가 알아서 처리하므로 개발자는 비즈니스 로직에만 집중하면 된다.

Proxy가 왜 중요한가?

Spring의 Proxy는 AOP(Aspect-Oriented Programming)의 기반이 되며, 트랜잭션 관리 외에도 다양한 기능을 손쉽게 적용할 수 있게 도와준다. 특히 트랜잭션 관리에서는 트랜잭션을 선언적으로 처리할 수 있어 코드가 단순해지고, 관리가 쉬워진다.

AOP와 Proxy의 관계

AOP는 핵심 로직에 별도의 횡단 관심사(트랜잭션, 보안, 로깅 등)를 추가하기 위한 개념이다. Proxy는 AOP를 구현하기 위한 대표적인 방식 중 하나다. Spring에서는 AOP를 사용할 때 실제로는 Proxy 객체가 생성되어서 비즈니스 로직에 횡단 관심사를 적용한다.

예를 들어, 트랜잭션 관리가 필요한 메서드에 @Transactional을 붙이면, 그 메서드를 호출할 때마다 Proxy가 해당 메서드를 가로채 트랜잭션을 처리하는 것이다.

Proxy와 @Transactional의 주의 사항

  1. 자기 자신 호출 문제: 같은 클래스 내에서 트랜잭션이 적용된 메서드를 호출하면 Proxy가 적용되지 않는다. 이는 Proxy가 외부에서 해당 메서드를 호출할 때만 트랜잭션을 관리하기 때문이다. 그래서 트랜잭션이 필요한 메서드가 같은 클래스 내 다른 메서드에서 호출될 경우 트랜잭션이 적용되지 않을 수 있다.

해결 방법: 서비스 계층에서는 항상 외부에서 트랜잭션이 필요한 메서드를 호출하도록 설계하는 것이 좋다.

  1. 리포지토리 계층에서 Proxy가 적용되지 않음: 일반적으로 트랜잭션은 서비스 계층에서 관리되고, 리포지토리 계층에서는 트랜잭션이 적용되지 않는다. 서비스 계층에서 트랜잭션을 시작하고 리포지토리 계층의 데이터베이스 작업을 포함하는 방식으로 설계해야 한다.

Proxy 이해를 돕는 예시

실제 동작 흐름

  1. 클라이언트가 MyService 클래스의 doSomething() 메서드를 호출.

  2. Spring이 자동으로 생성한 Proxy 객체가 해당 메서드를 가로챔.

  3. Proxy가 트랜잭션을 시작.

  4. 비즈니스 로직을 실행.

  5. 메서드가 성공적으로 끝나면 트랜잭션을 커밋. 만약 예외가 발생하면 트랜잭션을 롤백.

결론

Spring에서 @Transactional은 Proxy 패턴을 사용하여 트랜잭션을 선언적으로 관리한다. 이 Proxy는 실제 비즈니스 로직을 실행하기 전후로 트랜잭션을 처리하며, 개발자가 직접 트랜잭션을 관리할 필요 없이 비즈니스 로직에만 집중할 수 있게 해준다. Proxy의 동작 원리를 이해하면 Spring 트랜잭션 관리뿐 아니라 AOP의 다른 기능도 더 쉽게 이해할 수 있다.

정리

Spring에서 트랜잭션은 매우 중요한 개념으로, 데이터 일관성과 무결성을 보장하기 위해 꼭 사용해야 한다. @Transactional 어노테이션은 트랜잭션을 선언적으로 관리하는 강력한 도구다. Proxy와 AOP를 통해 트랜잭션 관리를 자동화할 수 있지만, 그 원리를 이해하는 것이 중요하다.

이 글에서는 트랜잭션의 기본 개념부터 전파 수준, 고립성, 그리고 Proxy를 이용한 트랜잭션 관리에 대해 알아보았다. Spring에서 트랜잭션을 제대로 적용하려면 Proxy와 AOP의 동작 원리를 이해하고, 적절한 설정을 통해 성능과 일관성을 모두 잡는 것이 중요하다.

profile
안녕하세요

0개의 댓글