Spring Boot에서 @Transactional을 올바르게 사용하는 법

신효경·2025년 11월 11일

오늘은 개발을 할 때 중요한 어노테이션 @Transaction에 대해서 글을 작성해보며 공부하려고 한다.


1. @Transactional이란?

  • @Transactional 은 하나의 작업 단위를 트랜잭션(Transaction) 으로 묶어주는 어노테이션이다.
  • 즉, 여러 SQL이 한 번에 실행되다가 중간에 에러가 발생하면 전체를 롤백(Rollback) 시켜주는 기능이다.
  • 예를 들어, 포인트를 차감하고 상품 재고를 줄이는 로직이 있다고 가정하자. 이때 로직중 하나라도 실패하면 전체를 취소해야 한다.

    @Transactional 이 없으면 위 코드에서 예외가 발생하더라도
    포인트 차감이 이미 DB에 반영된 채로 남을 수 있다.

2. 동작 원리

  • Spring은 내부적으로 AOP(관점 지향 프로그래밍)를 이용해
    트랜잭션을 관리한다.
  • 즉, @Transactional 이 붙은 메서드가 실행될 때,
    Spring이 자동으로 Connection 시작 → Commit 또는 Rollback 을 제어한다.

3. 어디에 붙여야 할까?

Service 계층에 붙이는 것이 가장 좋다.
Controller나 Repository에 붙이는 것은 권장되지 않는다.

  • Controller: 요청/응답을 처리하는 곳으로 비즈니스 로직이 아니다.
  • Repository: JPA 내부적으로 이미 트랜잭션을 관리한다.
  • Service: 여러 Repository를 묶어서 로직을 수행하는 곳이다.

예시

4. Checked Exception vs Unchecked Exception

Spring의 기본 설정은 RuntimeException (Unchecked) 발생 시에만 rollback 된다.

예외 타입롤백 여부설명
RuntimeException기본적으로 롤백됨
SQLExceptionChecked 예외 — 명시적으로 지정해야 함

Checked 예외까지 롤백하고 싶다면 다음과 같이 작성한다.

5. 주의해야 할 함정들

  1. private 메서드에는 적용되지 않는다

Spring AOP는 프록시 기반으로 동작하므로,
public 메서드가 아니면 트랜잭션이 동작하지 않는다.

  1. 자기 자신 내부 호출은 트랜잭션이 적용되지 않는다

해결 방법은 다음과 같다.
1) 내부 호출 대신 별도의 Bean(클래스) 으로 분리하거나
2) ApplicationContext 로 자기 자신을 주입받아 호출한다.

  1. readOnly 옵션은 SELECT 전용일 때만 사용해야 한다

이렇게 하면 Hibernate가 flush나 dirty checking을 생략하므로
성능이 향상된다.

6. 예외 처리와 함께 사용하는 법

실무에서는 예외가 발생했을 때 로그를 남기거나
사용자에게 에러 응답을 전달해야 한다.

7. 정리

주제핵심 요약
사용 위치Service 계층
기본 롤백 대상RuntimeException
Checked 예외rollbackFor = Exception.class
내부 호출트랜잭션 미적용 주의
readOnly조회 전용 메서드에 사용

@Transactional 은 “붙이는 순간 다 해결되는 마법의 주석”이 아니다.
어디에 붙이고, 무슨 예외가 발생할 때 롤백되는지
정확히 이해해야 한다.

트랜잭션은 데이터의 신뢰성을 지켜주는 최후의 보루다.
Spring Boot를 다룬다면 반드시 이 원리를 이해하고 사용해야 한다.


🔹 오늘의 나는 무엇을 잘했는지
처음엔 @Transactional이 단순히 트랜잭션을 관리해주는 어노테이션 정도라고만 생각했는데, 공부하면서 내부 동작 원리(AOP 기반 프록시)와 롤백 규칙까지 정확히 이해할 수 있었다.
특히 @Transactional(readOnly = true)가 단순 옵션이 아니라 성능 최적화 역할도 한다는 점을 스스로 정리해낸 것이 뿌듯했다.

🔹 어떤 문제를 겪었고, 어떻게 해결할지
Checked Exception에서는 롤백이 자동으로 되지 않는다는 사실을 모르고 테스트하다가 데이터가 실제로 DB에 반영돼버리는 실수를 했다.
원인을 찾는 데 시간이 꽤 걸렸지만, rollbackFor = Exception.class를 사용해야 한다는 것을 알게 되었다.
앞으로는 테스트 코드에 명시적으로 예외를 던져보고 트랜잭션 동작 여부를 검증하는 습관을 들이려고 한다.

🔹 오늘 배운 것

  • @Transactional은 Service 계층에서 사용하는 것이 가장 이상적이다.
  • 내부 호출(private 메서드, 자기 자신 호출)은 트랜잭션이 적용되지 않는다.
  • 기본적으로 RuntimeException에서만 롤백이 발생한다.
  • readOnly 속성은 Dirty Checking을 생략해 성능을 높인다.
  • AOP 프록시를 통해 begin → commit/rollback 과정이 자동으로 제어된다.

이 과정을 코드로 실습해보며 트랜잭션이란 결국 DB 안전벨트라고 하는 말이 무슨 뜻인지 좀 알게된 것 같다~

0개의 댓글