spring

[Spring] @Transaction

goblin- 2024. 11. 28. 11:55

@Transactional은 Spring 프레임워크에서 제공하는 애노테이션으로, 트랜잭션 관리를 간단하고 일관성 있게 처리할 수 있게 해줍니다. 데이터베이스 작업에서 트랜잭션의 시작, 커밋, 롤백을 자동으로 처리하는 데 사용됩니다.

 

1. @Transactional의 주요 역할

 

1. 트랜잭션 시작:

메서드 호출 시 Spring은 트랜잭션을 시작합니다.

2. 트랜잭션 경계 설정:

메서드 실행이 끝날 때, 정상적으로 완료되면 커밋(Commit), 예외가 발생하면 **롤백(Rollback)**을 수행합니다.

3. 트랜잭션 전파 및 격리 수준 설정:

트랜잭션 간 상호작용 방식(전파)과 격리 수준을 정의할 수 있습니다.

 

2. @Transactional 주요 속성

 

(1) propagation (전파 레벨)

 

트랜잭션 전파는 현재 트랜잭션이 이미 존재하는 경우나 새로운 트랜잭션이 필요할 때 어떻게 동작할지를 정의합니다.

 

REQUIRED (기본값):

이미 실행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여합니다.

없으면 새 트랜잭션을 생성합니다.

REQUIRES_NEW:

항상 새 트랜잭션을 생성하며, 기존 트랜잭션은 일시 정지됩니다.

SUPPORTS:

트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행됩니다.

NOT_SUPPORTED:

트랜잭션이 있으면 일시 정지하고 트랜잭션 없이 실행합니다.

MANDATORY:

반드시 기존 트랜잭션에 참여해야 합니다. 트랜잭션이 없으면 예외를 발생시킵니다.

NEVER:

트랜잭션이 있으면 예외를 발생시키고, 없으면 트랜잭션 없이 실행됩니다.

NESTED:

기존 트랜잭션에 중첩된 트랜잭션을 생성합니다.

 

(2) isolation (격리 수준)

 

SQL 트랜잭션의 격리 수준을 설정합니다. 데이터베이스 간의 일관성과 동시성 문제를 해결하기 위해 사용됩니다.

 

DEFAULT (기본값):

데이터베이스 드라이버의 기본 격리 수준을 사용합니다.

READ_UNCOMMITTED:

다른 트랜잭션에서 커밋되지 않은 변경 사항도 읽을 수 있습니다. 성능은 좋지만 일관성은 낮습니다.

READ_COMMITTED:

커밋된 데이터만 읽습니다. 대부분의 데이터베이스에서 기본값입니다.

REPEATABLE_READ:

트랜잭션 동안 동일한 데이터를 읽으면 항상 같은 결과를 반환합니다.

SERIALIZABLE:

가장 엄격한 격리 수준으로, 동시성은 떨어지지만 완벽한 일관성을 보장합니다.

 

(3) readOnly (읽기 전용)

 

true: 트랜잭션이 데이터 읽기 작업만 수행하도록 설정합니다. 성능 최적화 및 의도 표시 용도로 사용됩니다.

false (기본값): 읽기/쓰기 작업 모두 허용됩니다.

 

(4) rollbackFor 및 noRollbackFor

 

특정 예외가 발생했을 때 트랜잭션을 롤백하거나 롤백하지 않도록 설정합니다.

rollbackFor: 롤백을 트리거할 예외 지정.

@Transactional(rollbackFor = CustomException.class)

 

noRollbackFor: 롤백을 수행하지 않을 예외 지정.

@Transactional(noRollbackFor = IllegalArgumentException.class)

 

3. @Transactional의 적용 위치

 

클래스:

클래스 레벨에서 설정하면 모든 메서드에 트랜잭션이 적용됩니다.

@Transactional
public class OrderService {
    public void createOrder() { ... }
    public void cancelOrder() { ... }
}

 

메서드:

특정 메서드에만 트랜잭션을 적용할 수 있습니다.

public class OrderService {
    @Transactional
    public void createOrder() { ... }
}

 

4. @Transactional 동작 과정

 

1. AOP 적용:

@Transactional은 AOP(Aspect-Oriented Programming)를 사용해 동작합니다.

트랜잭션이 시작되기 전에 프록시 객체가 동작하여 트랜잭션을 시작합니다.

트랜잭션 관리자는 프록시 객체를 통해 트랜잭션 시작, 커밋, 롤백을 자동으로 처리합니다. 이 과정은 개발자가 명시적으로 Connection이나 commit()을 호출하지 않아도 되도록 도와줍니다.

예를 들어, @Transactional이 선언된 메서드를 호출하면 Spring이 TransactionInterceptor를 통해 해당 메서드 실행 전후로 트랜잭션을 관리합니다.

 

2. Connection 관리:

Spring은 트랜잭션 컨텍스트에서 데이터베이스 커넥션을 생성하거나 기존 커넥션을 재사용합니다.

3. 커밋 또는 롤백:

메서드가 정상적으로 종료되면 commit() 호출.

예외가 발생하면 rollback() 호출.

 

5. 예제 코드

 

기본 사용

@Service
public class OrderService {

    @Transactional
    public void placeOrder(Order order, Payment payment) {
        orderRepository.save(order);
        paymentRepository.process(payment);
        // 모든 작업이 성공하면 커밋, 예외 발생 시 롤백
    }
}

 

읽기 전용 트랜잭션

@Service
public class ReportService {

    @Transactional(readOnly = true)
    public List<Report> getReports() {
        return reportRepository.findAll(); // 읽기 전용 트랜잭션
    }
}

 

전파 속성 사용

@Service
public class NotificationService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendNotification(String message) {
        // 항상 새로운 트랜잭션 생성
        notificationRepository.save(message);
    }
}

 

6. 주의사항

 

1. 프록시 기반 동작:

같은 클래스 내에서 호출된 메서드는 @Transactional이 적용되지 않을 수 있습니다.

@Service
public class OrderService {

    @Transactional
    public void placeOrder() {
        savePayment(); // 같은 클래스 내 메서드 호출
    }

    @Transactional
    public void savePayment() {
        // 결제 처리 로직
    }
}

실행 흐름:

 

1.1. 클라이언트 호출:

외부에서 placeOrder()를 호출 → 프록시 객체가 트랜잭션 시작.

placeOrder() 메서드가 실행 중.

1.2. 내부 메서드 호출:

placeOrder() 메서드 내에서 savePayment() 호출 → 프록시 객체를 거치지 않고 직접 호출됨.

결과적으로 savePayment()에 설정된 @Transactional은 적용되지 않음.

 

 

2. 체크 예외:

Spring은 런타임 예외에 대해서만 기본적으로 롤백합니다.

체크 예외에서 롤백하려면 rollbackFor를 명시적으로 지정해야 합니다.

3. readOnly:

readOnly = true 설정은 데이터베이스에 따라 성능 최적화 효과가 다를 수 있습니다.

4. 메서드 내부 호출:

같은 클래스에서 this.someTransactionalMethod()와 같이 호출하면 트랜잭션이 적용되지 않습니다. 이는 AOP 프록시가 동작하지 않기 때문입니다.

 

7. 최근 현업 사용

 

Spring Data JPA와 함께 사용되는 경우가 많으며, 대부분의 데이터 처리에서 기본적으로 사용됩니다.

트랜잭션 전파, 격리 수준, 롤백 조건을 명시적으로 설정하여 복잡한 비즈니스 로직을 처리할 때도 널리 사용됩니다.

 

@Transactional은 데이터 일관성을 유지하고, 프로그래머가 복잡한 트랜잭션 처리를 간단히 처리할 수 있도록 해주는 중요한 도구입니다.

 

 

 

+ 추가질문

 

1. @Transactional은 AOP를 어떻게 사용하나요?

 

Spring AOP 활용: @Transactional을 사용하면 Spring AOP를 통해 트랜잭션 관리가 구현됩니다.

프록시 기반 동작: Spring은 프록시를 생성하여 메서드 호출을 가로채고, 트랜잭션 시작과 종료를 관리합니다.

AOP의 핵심 개념 사용:

Advice: 트랜잭션 관리 로직은 AOP의 Advice로 구현되어 메서드 실행 전후에 적용됩니다.

Pointcut: @Transactional이 적용된 메서드를 대상으로 하는 Pointcut이 정의됩니다.

Advisor: Advice와 Pointcut을 결합하여 어떤 Advice를 어떤 Pointcut에 적용할지 결정합니다.

 

2. 프록시 사용이 AOP와 유사해서 AOP가 사용되는 건가요?

 

프록시를 통한 메서드 가로채기: 프록시를 사용하여 메서드 호출을 가로채고 부가 기능을 적용하는 방식은 AOP의 핵심 동작 방식입니다.

AOP의 구현 방식 중 하나: Spring AOP는 프록시 기반으로 동작하며, 프록시를 통한 메서드 가로채기 자체가 AOP의 구현 방식입니다.

따라서, @Transactional을 사용할 때 프록시를 통해 메서드 호출을 가로채고 트랜잭션 관리 로직을 적용하는 것은 AOP를 사용하는 것입니다.

 

3. @Transactional 내부에 AOP 로직이 들어 있어서 AOP가 사용되는 건가요?

 

@Transactional 자체가 어노테이션일 뿐: @Transactional 어노테이션은 메타데이터로서 해당 메서드나 클래스에 트랜잭션 관리가 필요하다는 정보를 제공합니다.

Spring이 내부적으로 AOP 설정을 생성: Spring은 @Transactional을 감지하고, 내부적으로 AOP 설정(Advisor, Advice, Pointcut 등)을 생성합니다.

개발자가 AOP 설정을 명시적으로 작성하지 않아도: @Transactional을 사용하면 Spring이 자동으로 AOP 설정을 구성하므로, 개발자가 직접 AspectJ 어노테이션이나 AOP 설정을 작성할 필요가 없습니다.

AspectJ를 직접 사용하지 않음: @Transactional을 사용할 때 AspectJ 프레임워크나 AspectJ 어노테이션을 내부적으로 사용하는 것은 아닙니다. 대신 Spring이 자체적으로 제공하는 AOP 기능을 활용합니다.

 

4. TransactionSynchronizationManager는 언제 사용되나요?

 

4.1. 트랜잭션의 시작과 종료 시점에 사용됩니다.

 

트랜잭션 시작: @Transactional 어노테이션이 적용된 메서드가 호출되면, Spring은 AOP를 통해 프록시를 생성하고, 해당 메서드 호출을 가로채어 트랜잭션을 시작합니다.

이때 **TransactionSynchronizationManager**는 현재 스레드에 트랜잭션 관련 정보를 저장하고 관리합니다. 트랜잭션 리소스(예: Connection 객체)를 바인딩하여 트랜잭션 내에서 동일한 리소스를 사용할 수 있게 합니다.

 

4.2. 트랜잭션 동기화 및 상태 관리를 위해 사용됩니다.

 

트랜잭션 내에서 실행되는 코드가 트랜잭션 상태나 리소스에 접근해야 할 때, **TransactionSynchronizationManager**를 통해 현재 트랜잭션의 정보를 가져올 수 있습니다.

예를 들어, 같은 스레드 내에서 여러 DAO나 서비스가 동일한 트랜잭션 리소스를 공유해야 할 때, **TransactionSynchronizationManager**가 이를 가능하게 합니다.

 

4.3. 트랜잭션 동기화 콜백을 등록하고 실행할 때 사용됩니다.

 

트랜잭션이 커밋되거나 롤백될 때 추가로 실행되어야 하는 작업(예: 캐시 갱신, 이벤트 발행 등)이 있다면, **TransactionSynchronizationManager**에 동기화 콜백을 등록하여 트랜잭션 종료 시점에 실행할 수 있습니다.