profile image

L o a d i n g . . .

728x90

Spring Boot의 @Transactional 어노테이션은 트랜잭션을 사용자가 편리하게 사용할 수 있도록 제공되는 어노테이션입니다. @Transactional 어노테이션을 통해 해당 트랜잭션의 다양한 옵션을 설정할 수 있습니다(propagation, isolation level 등). 이번 포스팅에서는 @Transactional 어노테이션이 어떻게 동작하는지 알아보겠습니다. 

 

Spring Boot는 AOP(Aspect Oriented Programming) 기법을 통해 공통 관심사를 Aspect의 형태로 처리합니다. @Transactional 기능은 데이터베이스와 상호작용하는 다양한 메서드들의 공통 관심사인 트랜잭션을 분리해서 처리하는 점에서 AOP에 포함된다고 볼 수 있습니다. 그럼 AOP를 구현하는데 필요한 기술에는 무엇이 있을까요? 

 

Spring은 AOP를 위해 Proxy를 활용합니다. 디자인 패턴 중 하나인 Proxy Pattern을 적용한 것이며 Proxy Pattern은 다음과 같이 정의됩니다. 

Proxy Pattern은 객체에 대한 접근을 제어하는 용도로 대리인에 해당하는 객체를 제공하는 패턴입니다. 

 

 

Proxy Pattern은 필요에 따라 대리인에 해당하는 객체를 통해 접근 제어뿐 아니라 추가적인 기능도 제공하도록 활용할 수 있습니다. Spring AOP는 Proxy 역할을 하는 객체를 생성합니다(사용자 입장에서는 proxy가 자동으로 생성됩니다). Proxy를 자동으로 생성하기 위해 Spring은 Jdk dynamic proxy 또는 CGLib을 사용합니다. Spring Boot Framework의 경우 CGLib을 기본 Proxy 생성 라이브러리로 사용합니다. Spring Boot는 기존에 Jdk dynamic proxy를 사용했지만 다음과 같은 이유로 CGLib으로 교체했습니다. 

 

- Jdk dynamic proxy 사용의 불편함 (proxy 생성을 위해 interface를 구현해야 함). CGLib의 경우 interface구현 없이도 proxy 생성이 가능 
- Jdk dynamic proxy의 Relection 사용으로 인한 성능 이슈 

 

 

그럼 Spring의 @Transactional 어노테이션이 표시된 메서드가 어떻게 실행되는지 디버깅 과정을 통해 알아보겠습니다. 

@Repository의 select 메서드

 

위 코드는 Jooq를 사용해서 데이터베이스에 select 질의를 수행하는 메서드입니다. @Transactional 어노테이션에 의해 어떻게 Proxy가 동작하는지 알아보겠습니다. 

@Transactional annotated method 실행 시 Debugger window

 

findByUsernameAndAuthProvider을 테스트 환경에서 실행하면 debugger window는 위와 같이 표시됩니다. 단순히 findByUsernameAndAuthProvider가 실행되는 게 아니라 Spring의 CglibAopProxy를 통해 해당 메서드 실행이 인터셉트되고 트랜잭션 처리에 필요한 작업을 먼저 수행합니다. 그리고 최종적으로 jdbc의 COMMIT 명령을 내림으로써 해당 트랜잭션을 종료합니다. 

 

그럼 트랜잭션을 Spring에서 어떻게 처리하는지 조금 더 자세히 살펴보겠습니다. Spring은 CglibAopProxy를 통해 트랜잭션 처리에 필요한 TransactionalInterceptor를 찾습니다. TransactionalInterceptor는 invoke 메서드를 통해 트랜잭션에 필요한 작업을 시작합니다. 

TransactionInterceptor's invoke method

 

Spring documentation에 표기된 invokeWithinTransaction 메서드의 정의는 다음과 같습니다. 해당 메서드는 around-advice로 감싸진 메서드의 트랜잭션 처리를 위해서 트랜잭션에 필요한 각 기능들을 template 패턴을 적용하여 다른 객체에 위임하는 방식으로 처리합니다.

invokeWithinTransaction method

 

invokeWithinTransaction 메서드를 따라가보면 transaction을 시작하고 커밋하는 코드를 확인할 수 있습니다.

 

this.createTransaction(...) 메서드를 따라가보면 DataSourceTransactionManager클래스의 doBegin 메서드를 확인할 수 있습니다. doBegin 메서드는 해당 트랜잭션의 옵션을 설정하고 트랜잭션을 시작합니다. 

DataSourceTransactionManager's doBegin method
prepareTransactionalConnection은 트랜잭션의 시작과 관련된 statement를 생성

 

트랜잭션의 시작을 확인했으니 트랜잭션의 마무리인 commit을 확인해보겠습니다. 트랜잭션의 커밋을 담당하는 commitTransactionAfterReturning 메서드는 다음과 같습니다. 

TransactionAspectSupport's commitTransactionAfterReturning method

 

최종적으로는 DataSourceTransactionManager의 doCommit 메서드가 JdbcConnection의 commit 메서드를 호출하면서 해당 트랜잭션은 마무리가 됩니다. 

DataSourceTransactionManager's doCommit method
JdbcConnection's commit method

 

 

 

이렇게 해서 Spring Boot의 @Transactional 어노테이션이 트랜잭션과 관련된 기능을 어떻게 제공하는지 살펴봤습니다. 요약하면 Spring Boot는 AOP의 형태로 @Transactional 기능을 제공합니다. AOP에 사용되는 기술은 CGLib이며 CGLib이 생성한 Proxy를 통해서 메서드를 인터셉트하고, 인터셉트된 메서드 주위로 트랜잭션을 처리 기능을 제공합니다. Proxy를 통해 @Transactional 어노테이션이 표시된 메서드의 시작 전에 트랜잭션을 시작하고, 해당 메서드의 종료 이후에는 트랜잭션을 커밋합니다. 

728x90
복사했습니다!