[Spring] @Transactional 사용 시 주의할 점!

조성우·2025년 8월 16일

Spring Boot

목록 보기
14/14

@Transactional은 스프링에서 선언적 트랜잭션 관리를 가능하게 해주는 어노테이션이다.

이 어노테이션을 클래스나 메서드에 붙이면, 해당 작업이 하나의 트랜잭션으로 묶여 모든 작업이 성공적으로 완료될 경우 커밋(commit) 되고, 도중에 예외가 발생하면 모든 변경 사항이 자동으로 롤백(rollback) 된다.

횡단 관심사(Cross-Cutting Concerns)를 모듈화하는 AOP에 기반한 처리이면서, 개발자가 직접 트랜잭션을 시작하거나 커밋, 롤백하는 코드를 작성할 필요도 없으니 매우 유용한 기능이다.


간단한 내부 작동 원리

@Transactional은 내부적으로 프록시(Proxy) 기술을 활용해 동작한다. 스프링 컨테이너는 @Transactional이 붙은 클래스나 메서드를 발견하면, 실제 객체를 감싸는 프록시 객체를 생성한다.

아래는 @Transactional 메서드 호출 시 작동 순서이다.

  1. 메서드 호출 가로채기 - 외부에서 @Transactional이 붙은 메서드를 호출하면, 실제 객체가 아닌 이 프록시 객체가 먼저 호출을 가로챈다.

  2. 트랜잭션 인터셉터 - 프록시 내부의 트랜잭션 인터셉터가 어노테이션을 감지하고, 지정된 트랜잭션 매니저(JpaTransactionManager 등)에게 트랜잭션 시작을 요청한다.

  3. 트랜잭션 시작 - 트랜잭션 매니저는 데이터 소스로부터 데이터베이스 커넥션을 얻어와 setAutocommit(false)를 설정하고 트랜잭션을 시작한다.

  4. 동기화 매니저 등록 - 이 커넥션은 트랜잭션 동기화 매니저에 등록되어, 해당 트랜잭션 안에서 수행되는 모든 데이터베이스 작업이 동일한 커넥션을 사용하도록 보장한다.

  5. 모든 작업이 완료되면 인터셉터는 커밋을, 예외가 발생하면 롤백을 수행하도록 제어한다.


일반적으로 주의할 점!

  • 프록시 내부 호출 문제 - Service 클래스 내부의 다른 메서드에서 @Transactional이 붙은 메서드를 호출하면, 프록시를 통하지 않고 실제 객체의 메서드를 직접 호출하게 된다. 이 경우 트랜잭션이 적용되지 않는다. 이를 해결하기 위해서는 트랜잭션이 필요한 메서드를 별도의 클래스로 분리해야 한다.

  • private 메서드 적용 불가 - @Transactionalpublic 메서드에만 적용됩니다. 프록시 기술인 CGLIB은 타켓 클래스를 상속받아 프록시를 생성하는데, private에는 자식 클래스에서 접근 불가능하기 때문이다.


테스트 코드에서 주의할 점!

@Transactional은 테스트 코드에서도 매우 유용하게 사용된다.

테스트 메서드에 이 어노테이션을 붙이면, 테스트가 끝난 후 데이터베이스의 모든 변경 사항이 자동으로 롤백된다. 덕분에 테스트 간 데이터 충돌을 방지하고, 매번 데이터를 초기화하는 번거로운 작업을 생략할 수 있다.

다만, 이는 실제 서비스 코드와 테스트 환경의 동작 불일치를 야기할 수 있다는 점을 유의해야 한다.

위 사진은 아래에 있는 테스트 코드만 정상적으로 작동하는 케이스다. JPA의 변경 감지는 트랜잭션 안에서만 동작하기 때문에 @Transactional이 없는 실제 서비스 코드에서는 예상치 못한 문제가 발생할 수 있다.

항상 실제 환경을 염두에 두고 테스트를 작성하는 것이 중요하다.


장점

  1. 명시적으로 DB를 초기화하는 작업 없이 손쉽게 롤백이 가능하다.
  2. DB를 초기화하는 작업에 비해 테스트 성능이 더 빠르다.

단점

  1. 실제 서버 환경과 다르게 동작할 수 있다.
  2. 의도치 않은 트랜잭션이 테스트 코드에 적용될 수 있다.

참고 - [10분 테코톡] 카피의 @Transactional

0개의 댓글