ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 @Transaction 이해
    Spring/Spring Boot 2021. 10. 29. 12:10

    스프링의 트랜잭션


    우리는 선언적 트랜잭션 관리를 주로 사용하는데 @Transactional` 애노테이션 하나만 선언해서 매우 편리하게 트랜잭션을 적용하는 것을 선언적 트랜잭션 관리라 하며 스프링 부트로 개발한다면 주로 이 어노테이션 방법으로 개발하게 된다.

     

    @Transaction 애노테이션을 선언한다면 스프링의 트랜잭션 AOP는 이 애노테이션을 인식해서 트랜잭션을 처리하는 프록시를 적용해준다. 구체적으로  @Transactional`애노테이션이 특정 클래스나 메서드에 하나라도 있으면 트랜잭션 AOP는 cglib 기반의 프록시 클래스를 만들어서 스프링 컨테이너에 등록한다.

     

    따라서 실제 객체 대신에 프록시인 {클래스}$$CGLIB 을 스프링 빈으로 등록한다. 그리고 프록시는 내부에 실제 객체를 참조하게 된다. 여기서 핵심은 실제 객체 대신에 프록시가 스프링 컨테이너에 등록되었다는 점이다.

     

     

     

    트랜잭션 AOP 주의 사항


    AOP를 적용하면 스프링은 대상 객체 대신에 프록시를 스프링 빈으로 등록한다고 했다. 따라서 스프링은 의존관계 주입시에 항상 실제 객체 대신에 프록시 객체를 주입하는데 만약 대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가 발생하게 된다. 이렇게 되면 `@Transactional` 이 있어도 트랜잭션이 적용되지 않는다

     

    class CallService {
    
            public void external() {
                log.info("call external");
                printTxInfo();
                internal();
    }
            @Transactional
            public void internal() {
                log.info("call internal");
                printTxInfo();
            }
    }

     

    CallService에서 external() 메서드 호출후 internal() 메서드를 호출한다. external() 메서드에 의해 호출된 internal() 메서드는 @Transactional 이 선언되어 있는 메서드이지만 프록시 객체를 통해 접근되지 않았기에 트랜잭션은 적용되지 않는다. 

    • internal() == this.internal() 
    • this 는 프록시 객체가 아닌 직접 객체 CallService

     

     

    그렇다면 이 문제를 어떻게 해결할 수 있을까?

    가장 단순한 방법은 내부 호출을 피하기 위해 internal()  메서드를 별도의 클래스로 분리하는 것이다.

     

     

     

     

    예외와 트랜잭션


    예외 발생시 스프링 트랜잭션 AOP는 예외의 종류에 따라 트랜잭션을 커밋하거나 롤백한다. 언체크 예외인 RuntimeException Error 와 그 하위 예외가 발생하면 트랜잭션을 롤백한다체크 예외인 Exception 과 그 하위 예외가 발생하면 트랜잭션을 커밋한다.

     

    체크 예외는 커밋하기 때문에 비즈니스 요구사항으로 활용할 수 있다.

    • 예 ) 주문시 결제 잔고가 부족하면 주문 데이터를 저장하고, 결제 상태를 대기로 처리한다. 이는 고객의 정보를 저장해놓아야 하기에 롤백하지 않는 체크 예외로 풀수도 있음.

     

     

     

     

     

     

    참고


    김영한 - 스프링 DB 2편 데이터 접근 활용 기술

Designed by Tistory.