| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- JdbcTemplate
- docker compose
- CORS
- Web
- mybatis
- 컨테이너
- 스프링 부트
- 페이징
- DLQ
- 서블릿 컨테이너
- DI
- JPA
- Spring Data JPA
- @Transactional
- docker
- redis
- 지연 로딩
- kafka
- Spring
- dockerhub
- AWS
- 쿠버네티스
- securitycontextholderfilter
- Dead Letter Queue
- @ComponentScan
- JPQL
- MSA
- JWT
- Spring Container
- Routing Key
- Today
- Total
look-forest
스프링 트랜잭션 이해 본문
스프링 트랜잭션 기본
스프링 트랜잭션 추상화
JDBC 기술과 JPA 기술은 트랜잭션을 사용하는 코드 자체가 다르다. 따라서 JDBC 기술을 사용하다가 JPA 기술로 변경하게 되면 트랜잭션을 사용하는 코드도 모두 함께 변경해야 한다.
스프링은 이런 문제를 해결하기 위해 PlatformTransactionManager 라는 인터페이스를 통해 트랜잭션을 추상화한다.

선언적 트랜잭션과 AOP
@Transactional 을 통한 선언적 트랜잭션 관리 방식을 사용하게 되면 기본적으로 프록시 방식의 AOP가 적용된다.
트랜잭션 프록시가 트랜잭션 처리 로직을 모두 가져간다. 그리고 트랜잭션을 시작한 후에 실제 서비스를 대신 호출한다.
트랜잭션 프록시 덕분에 서비스 계층에는 순수한 비즈니즈 로직만 남길 수 있다.

스프링은 트랜잭션 AOP를 처리하기 위한 모든 기능을 제공한다.
스프링 부트를 사용하면 트랜잭션 AOP를 처리하기 위해 필요한 스프링 빈들도 자동으로 등록해준다.
트랜잭션 적용 확인
logging.level.org.springframework.transaction.interceptor=TRACE
이 로그를 추가하면 트랜잭션 프록시가 호출하는 트랜잭션의 시작과 종료를 명확하게 로그로 확인할 수 있다.
TransactionSynchronizationManager.isActualTransactionActive()
현재 쓰레드에 트랜잭션이 적용되어 있는지 확인할 수 있는 기능이다. 결과가 true 면 트랜잭션이 적용되어 있는 것이다.
트랜잭션 적용 위치
스프링의 @Transactional 은 다음 두 가지 규칙이 있다.
- 우선순위 규칙 - 항상 더 구체적이고 자세한 것이 높은 우선순위를 가진다. (클래스 < 메소드)
- 클래스에 적용하면 메서드는 자동 적용
스프링 부트 3.0 부터는 protected , package-visible (default 접근제한자)에도 트랜잭션이 적용된다.
(트랜잭션은 주로 비즈니스 로직의 시작점에 걸기 때문에 대부분 외부에 열어준 곳을 시작점으로 사용하므로)
트랜잭션 AOP 주의 사항
프록시 내부 호출
트랜잭션을 적용하려면 항상 프록시를 통해서 대상 객체(Target)을 호출해야 한다. 이렇게 해야 프록시에서 먼저 트랜잭션을 적용하고, 이후에 대상 객체를 호출하게 된다.

만약 프록시를 거치지 않고 대상 객체를 직접 호출하게 되면 AOP가 적용되지 않고, 트랜잭션도 적용되지 않는다.
AOP를 적용하면 스프링은 대상 객체 대신에 프록시를 스프링 빈으로 등록한다. 따라서 스프링은 의존관계 주입시에 항상 실제 객체 대신에 프록시 객체를 주입한다. 프록시 객체가 주입되기 때문에 대상 객체를 직접 호출하는 문제는 일반적으로 발생하지 않는다. 하지만 대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가 발생한다. 이렇게 되면 @Transactional 이 있어도 트랜잭션이 적용되지 않는다.
# 예시


해결 방안 - 트랜잭션을 적용할 메소드를 별도의 클래스로 분리
this.method()가 아니라 트랜잭션프록시.method()를 호출하도록한다.


트랜잭션 AOP 주의 사항 - 초기화 시점
스프링 초기화 시점에는 트랜잭션 AOP가 적용되지 않을 수 있다.
초기화 코드(예: @PostConstruct )와 @Transactional 을 함께 사용하면 트랜잭션이 적용되지 않는다.
왜냐하면 초기화 코드가 먼저 호출되고, 그 다음에 트랜잭션 AOP가 적용되기 때문이다. 따라서 초기화 시점에는 해당 메서드에서 트랜잭션을 획득할 수 없다.
가장 확실한 대안은 ApplicationReadyEvent 이벤트를 사용하는 것이다.
트랜잭션 AOP를 포함한 스프링이 컨테이너가 완전히 생성되고 난 다음에 이벤트가 붙은 메서드를 호출해 준다.
@EventListener(value = ApplicationReadyEvent.class)
@Transactional
public void init2() {
log.info("Hello init ApplicationReadyEvent");
}
트랜잭션 옵션 소개
value, transactionManager : 사용하는 트랜잭션 매니저가 둘 이상이라면 트랜잭션 매니저의 이름을 지정해서 구분
rollbackFor : 이 옵션을 사용하면 기본 정책에 추가로 어떤 예외가 발생할 때 롤백할 지 지정할 수 있다
@Transactional(rollbackFor = Exception.class) -> 체크 예외인 Exception 이 발생해도 롤백하게 된다
참고로 예외 발생시 스프링 트랜잭션의 기본 정책은 다음과 같다.
- 언체크 예외인 RuntimeException , Error 와 그 하위 예외가 발생하면 롤백한다. (어쩔수 없는 문제는 걍 롤백)
- 체크 예외인 Exception 과 그 하위 예외들은 커밋한다. (비즈니스에 의미가 있는 예외일 수 있으므로)
readOnly : 등록, 수정, 삭제가 안되고 읽기 기능만 작동
트랜잭션은 기본적으로 읽기 쓰기가 모두 가능한 트랜잭션이 생성된다. readOnly=true 옵션을 사용하면 읽기 전용 트랜잭션이 생성된다. 읽기에서 다양한 성능 최적화가 발생할 수 있다.
readOnly 옵션은 크게 3곳에서 적용된다.
- 프레임워크 - JPA는 읽기 전용 트랜잭션의 경우 커밋 시점에 플러시를 호출하지 않고, 변경 감지를 위한 스냅샷 객체도 생성하지 않는다
- JDBC 드라이버 - 읽기, 쓰기(마스터, 슬레이브) 데이터베이스를 구분해서 요청한다. 읽기 전용 트랜잭션의 경우 읽기(슬레이브) 데이터베이스의 커넥션을 획득해서 사용한다.
- 데이터베이스 - 읽기만 하면 되므로, 내부에서 성능 최적화
예외와 트랜잭션 커밋, 롤백
예외 발생시 스프링 트랜잭션 AOP는 예외의 종류에 따라 트랜잭션을 커밋(체크)하거나 롤백(언체크)한다.
스프링은 기본적으로 체크 예외는 비즈니스 의미가 있을 때 사용하고, 런타임 예외는 복구 불가능한 예외로 가정한다.
참고로 꼭 이런 정책을 따를 필요는 없다. 그때는 앞서 배운 rollbackFor 라는 옵션을 사용해서 체크 예외도 롤백하면 된다

참고 자료 & 이미지 출처
스프링 DB 2편 - 데이터 접근 기술 (김영한 님)
'Spring > Spring 데이터 접근 - 활용 기술' 카테고리의 다른 글
| 트랜잭션 전파 활용 (0) | 2024.09.07 |
|---|---|
| 스프링 트랜잭션 전파 (0) | 2024.09.07 |
| 데이터 접근 기술 - 활용 방안 (2) | 2024.09.02 |
| QueryDSL : type-safe Query (0) | 2024.08.31 |
| 스프링 데이터 JPA (0) | 2024.08.18 |