본문 바로가기
Spring

@Transactional 원리

by sangyunpark99 2025. 2. 25.
@Transactional은 어떤 원리로 실행되는 걸까?

 

 

이번 글에서는 커스텀 어노테이션, AOP, 그리고 TransactionManger를 활용하여 @Transactional과 유사한 기능을 가지는 커스텀 어노테이션을 직접 구현해 보고, 이를 통해 @Transactional의 원리에대해 정리했습니다.

 
 
먼저, @Transactional과 같은 역할을하는 커스텀 어노테이션 @Sangyunpark을 만들어 보겠습니다.
 

 

커스텀 어노테이션을 아래와 같이 만들어 줍니다.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sangyunpark {
}

 

 

@Sangyunpark이라는 어노테이션은 메서드 or 클래스에 적용이 가능하고, 런타임까지 유지되도록 설정했습니다.

 

런타임까지 유지된다는게 무슨 뜻인가요?

 

해당 커스텀 어노테이션의 생명 주기를 의미합니다.

이는 어노테이션이 언제까지 유지되는지를 결정하는 것으로, 컴파일 시점 이후인 런타임 시점에도 어노테이션이 유지것을 의미합니다.

 

 

AOP를 활용해서 @Sangyunpark가 붙은 메서드 실행 시 트랜잭션이 시작되도록 만들어 보겠습니다.

 

@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class SangyunparkAspect {

    private final PlatformTransactionManager transactionManager;

    @Around("@annotation(com.example.spring_practice.annotation.Sangyunpark)")
    public Object handle(ProceedingJoinPoint joinPoint) {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            Object result = joinPoint.proceed();
            transactionManager.commit(status);
            return result;
        } catch (Throwable e) {
            transactionManager.rollback(status);
            throw new RuntimeException(e);
        }
    }
}

 

Aspect클래스가 어떤 역할을 하는지 간단히 알아보겠습니다.

@Around("@annotation(com.example.spring_practice.annotation.Sangyunpark)")

 

이 코드는 제가 만든 커스텀 어노테이션인 @Sangyunpark가 붙은 메서드에 대해서 AOP가 적용됩니다.

PlatformTransactionalManager를 이용해서 트랜잭션을 커밋 또는 롤백을 해줍니다.

 

joinPoint는 AOP에서 원래 실행하려던 메서드를 실행하는 역할을 합니다.

아래 코드를 기준으로 원래 실행하려던 메서드는 createUser 메서드가 됩니다.

@Sangyunpark
public void createUser() {
    User user = new User("박상윤");
    userRepository.save(user);
}

 

이렇게 커스텀 어노테이션인 @Sangyunpark을 createUser()라는 메서드에 적용합니다.

 

이제, @Transactional과 비슷한 역할을 하는지 한번 테스트 해볼까요?

 

테스트 시나리오는 다음과 같습니다.

1. User 엔티티를 생성한 후, 저장합니다.
2. 저장한 User 엔티티를 조회합니다.
3. 생성한 User 엔티티의 이름과 엔티티와 저장후 조회한 User 엔티티의 이름이 같은지 비교합니다.

 

@SpringBootTest
public class CustomAnotation {

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    private static final String USERNAME = "박상윤";

    @Test
    @DisplayName("커스텀 어노테이션으로 @Transactional 구현하기")
    void createUser() throws Exception {
        //given
        userService.createUser();

        //when
        User user = userRepository.findUserByName(USERNAME).orElseThrow();

        // then
        assertThat(user.getName()).isEqualTo(USERNAME);
    }
}

 

테스트 결과는 다음과 같습니다.

 

커스텀 어노테이션 @Sangyunpark으로 트랜잭션 처리가 됬습니다.

 

@Transactional은 어떤 흐름으로 실행될까요?

 

 

@Transactional 흐름

@Transactional 어노테이션을 사용해서 트랜잭션을 적용하는 것을 선언적 트랜잭션이라고 합니다.

 

선언적 트랜잭션을 사용할 경우엔 프록시 방식의 AOP가 아래 그림과 같이 적용되게 됩니다.

이미지 출처 : 김영한 강의 "SpringDB 2편 - 데이터 활용 접근"

 

왜 트랜잭션을 처리하기 위해 프록시를 적용할까요?

 

트랜잭션 프록시를 적용하는 경우, 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 객체를 명확하게 분리할 수 있게 됩니다.

객체지향 설계 원칙인 SRP(Single Responsibility Principle, 단일 책임 원칙)을 더 잘 지키게 해줍니다.

 

프록시를 적용하지 않은 경우엔 어떻게 될까요?

 

트랜잭션 프록시를 적용하기 전에는 서비스 클래스에서 직접 트랜잭션을 시작하고, 커밋하거나 롤백하는 로직을 포함해야 합니다.
이렇게 되면 서비스 클래스가 비즈니스 로직뿐만 아니라 트랜잭션 관리까지 담당하게 되어, 단일 책임 원칙(SRP)을 위반할 가능성이 높아집니다.

 

이미지 출처 : 김영한 강의 "SpringDB 2편 - 데이터 활용 접근"

 

조금 더 자세히 알아볼까요?

 

이미지 출처 : 김영한 강의 "SpringDB 2편 - 데이터 활용 접근"

  1. 트랜잭션 프록시를 호출합니다.
  2. 스프링 컨테이너가 트랜잭션을 관리하는 트랜잭션 매니저를 주입(DI) 합니다.
  3. 획득한 트랜잭션 매니저에서 getTransaction()메서드를 호출해서 트랜잭션을 시작합니다.
  4. DataSource로 DB Connection을 생성하고, 자동 커밋 옵션은 false로 설정해줍니다.
  5. Connection을 보관하기 위해서 트랜잭션 동기화 매니저에게 전달합니다.
  6. 트랜잭션 동기화 매니저는 ThreadLocal을 이용해 트랜잭션 컨텍스트에서 Connection을 관리하며, 동일한 트랜잭션 범위에서 같은 Connection을 재사용할 수 있도록 보장합니다.
  7. 비즈니스 로직인 실제 서비스를 호출합니다.
  8. DB에 접근하는 로직이 실행되는 경우 트랜잭션 매니저가 보관해두었던 커넥션을 사용해서 처리합니다.

 

정리

  • @Transactional은 어노테이션으로 트랜잭션의 기능을 사용하는 선언적 트랜잭션 방식입니다.
  • 트랜잭션 프록시는 @Transactional이 적용된 메서드 실행 전후로 트랜잭션을 관리하며, 트랜잭션 매니저를 통해 커넥션을 생성하고 트랜잭션 동기화 매니저에 저장하여 동일한 트랜잭션 내에서 재사용할 수 있도록 보장합니다.
  •  트랜잭션 프록시는 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 객체를 명확하게 분리합니다.

'Spring' 카테고리의 다른 글

트랜잭션 AOP 주의사항  (0) 2025.03.01
Thread Local  (0) 2025.02.28
트랜잭션 전파 다양한 옵션  (0) 2025.02.17
싱글톤 컨테이너  (0) 2025.02.17
트랜잭션 전파 REQUIRES_NEW 활용  (0) 2025.02.15