선언적 트랜잭션과 AOP
@Transactional이라는 애노테이션을 사용하여 매우 편리하게 트랜잭션을 적용하는 것을 선언적 트랜잭션 관리라고 한다. 이 방식은 기본적으로 프록시 방식의 AOP가 적용된다.
AOP의 핵심은, 실제 객체 대신 트랜잭션을 처리해주는 프록시 객체가 스프링 빈에 등록된다는 것이다. 또한 주입을 받을 때도 실제 객체 대신에 프록시 객체가 주입된다. 즉, 선언적 트랜잭션을 사용하면 항상 프록시를 통해서 대상 객체를 호출한다는 것이다.
프록시 내부 호출
만약, 프록시를 거치지 않고 대상 객체를 직접 호출하게 되면 어떻게 될까? 2가지 상황에 대해 알아보자.
상황 1
트랜잭션이 적용된 Internal() 메서드가 내부 호출로 External() 메서드를 호출하는 상황이다. Internal() 메서드에는 트랜잭션이 적용되어 있으므로 프록시 객체를 호출하게 되고, 때문에 External() 메서드에는 트랜잭션이 없더라도 Internal()이 트랜잭션이 적용된 상태에서 실행되고 있으므로 같은 스레드에서 External()은 실행되는 내부호출을 통해 트랜잭션에 참여하는 것이다.
상황 2 → 문제 상황
그럼 이 경우는 어떨까? 트랙잭션이 적용되지 않는 External() 메서드를 호출하고 External() 메서드는 내부에서 트랜잭션이 적용된 Internal() 메서드를 호출하는 상황이다.
이 경우는 AOP가 적용되지도 않고 트랜잭션도 적용되지 않는다. 즉, Internal() 메서드에 트랜잭션이 적용되어 있어도, 트랜잭션이 적용되지 않는 External() 메서드가 Internal() 메서드를 내부 호출한 것이기 때문에 트랜잭션이 적용되지 않는 것이다. 정리하면 대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가 발생한다.
해결 방법
위의 내부 호출 문제를 어떻게 해결할 수 있을까? 가장 단순한 방법은 별도의 클래스로 분리하는 것이다.
이 흐름의 핵심은 Internal() 메서드와 External() 메서드를 다른 클래스로 분리함으로써 내부 호출을 외부 호출로 바꾼 것이다. 트랜잭션이 적용되어 있지 않은 External()을 호출하더라도, 트랜잭션이 선언된 Internal() 메서드를 호출하기 위해 Internal() 메서드가 속한 프록시 객체를 만들게 되고, 이를 통해 호출하면서 Inernal() 메서드에 트랜잭션을 적용할 수 있는 것이다.
readOnly = true
readOnly = true 는 읽기 전용 트랜잭션 옵션이다. 기본적으로 트랜잭션은 읽기 쓰기가 모두 가능한 트랜잭션이 생성된다. 즉, readOnly = false가 기본 옵션이기 때문에, 읽기 전용 트랜잭션을 사용하기 위해서는 명시적으로 작성해주어야 한다. readOnly 옵션을 사용하면 읽기에서 다양한 성능 최적화가 발생할 수 있다.
성능 최적화
JPA에서 읽기 전용 트랜잭션의 경우, 커밋 시점에 플러시(flush)를 호출하지 않는다. 읽기 전용이니 변경에 사용되는 flush를 호출할 필요가 없기 때문이다. 또한 변경이 필요 없으니 변경 감지를 위한 스냅샷 객체도 생성하지 않는다.
스프링 트랜잭션 기본 정책
예외와 트랜잭션 커밋
예외 발생시 스프링 트랜잭션의 기본정책은 다음과 같다.
- 언체크 예외인 RuntimeException, Error와 그 하위 예외가 발생하면 롤백(RollBack)한다.
- 체크 예외인 Exception과 그 하위 예외들은 커밋(Commit)한다.
스프링은 왜 체크 예외는 커밋하고 언체크(런타임) 예외는 롤백할까? 스프링은 기본적으로 체크 예외는 비즈니스 의미가 있을 때 사용하고, 런타임 예외는 복구 불가능한 예외로 가정하기 때문이다. 이때, rollbackFor = 예외 클래스.class 옵션을 사용하면 체크 예외도 강제로 롤백 시킬 수 있다.
'Spring' 카테고리의 다른 글
스프링 DB 2편 - 섹션 10~11 (0) | 2025.03.19 |
---|---|
스프링 DB 1편 - 섹션 3~4 (0) | 2025.03.18 |
스프링 DB 1편 - 섹션 1~2 (0) | 2025.03.18 |
스프링 핵심 원리 고급편 섹션2 (0) | 2025.03.17 |
빈으로 등록된 필터가 WebSecurity의 ignoring에 의해 무시되지 않는 이유 (0) | 2025.03.17 |