클래스나 메서드에 붙여 트랜잭션을 선언적으로 관리할 구현할 수 있는 어노테이션이다.
동작 과정
- 어노테이션 메서드 호출 감지:
TransactionInterceptor가 메서드 호출을 가로채 트랜잭션 준비 - 트랜잭션 매니저 결정: 메서드와 클래스의 트랜잭션 속성에 따라
TransactionManager를 선택- JPA에서는
JPATransactionManager
- JPA에서는
- 커넥션 생성:
DataSource를 통해Connection생성 - 트랜잭션 시작 (JDBC의
autoCommit을false) - 트랜잭션 동기화 매니저에게 Connection 저장: 트랜잭션동안 동일한 커넥션을 하나의 스레드에서 일관되게 사용
TransactionSynchronizationManager- DB 접근 작업이 있을때마다 커넥션을 꺼내다 씀
전파 속성
기본적으로 여러 트랜잭션을 묶어서 커다란 하나의 트랜잭션 경계를 만든다. (REQUIRED)
이때 기존에 트랜잭션이 진행중일 때 추가적인 트랜잭션을 어떻게 수행할지를 결정하는 것이 전파 속성이다.
| Propagation | 기존 트랜잭션 O | 기존 트랜잭션 X |
|---|---|---|
REQUIRED | 참여 | 새로 생성 |
REQUIRES_NEW | 기존 트랜잭션 중단 + 새로 생성 | 새로 생성 |
SUPPORTS | 참여 | 트랜잭션 없이 실행 |
NOT_SUPPORTED | 기존 트랜잭션 중단 | 트랜잭션 없이 실행 |
MANDATORY | 참여 | 예외 |
NEVER | 예외 | 트랜잭션 없이 실행 |
NESTED | 중첩 트랜잭션 (Savepoint 사용) | 새로 생성 |
REQUIRES_NEW와 NESTED를 비교한건 다음과 같다.
| REQUIRES_NEW | NESTED | |
|---|---|---|
| 물리 트랜잭션 | 별도 | 동일 |
| Connection | 별도 필요 가능 | 동일 |
| 구현 | 새 Transaction | Savepoint |
| 내부 Commit | 실제 Commit 가능 | 독립 Commit 아님 |
| 외부 Rollback 영향 | 내부 Commit 유지 | 전체 Rollback됨 |
읽기 전용 트랜잭션
읽기 작업을 위한 트랜잭션도 제공하며 속성에 readOnly = true를 추가 (기본값은 false)
우선 읽기 작업에도 트랜잭션을 설정하는 이유는 다음과 같다.
- 일관성: 여러
SELECT작업이 묶여 있을 경우 일관성을 유지할 수 있다. - DB 라우팅 기준: 읽기/쓰기 DB 분리 구조에서 라우팅 기준으로 사용 가능하다.
- JPA 최적화: JPA 사용 시, 읽기 전용 힌트나
flush모드로 인한 최적화 여지가 존재한다.
readOnly=true를 설정한다고 해서 INSERT, UPDATE, DELETE가 불가능한건 아니다.
공식문서에서는 다음과 같이 언급한다.
Quote
이는 실제 트랜잭션 하위 시스템에 대한 힌트일 뿐이며, 쓰기 접근 시도 실패를 반드시 유발하는 것은 아닙니다. 읽기 전용 힌트를 해석할 수 없는 트랜잭션 관리자는 읽기 전용 트랜잭션 요청을 받았을 때 예외를 발생시키지 않고 힌트를 묵묵히 무시합니다.
원본
This just serves as a hint for the actual transaction subsystem; it will not necessarily cause failure of write access attempts. A transaction manager which cannot interpret the read-only hint will not throw an exception when asked for a read-only transaction but rather silently ignore the hint.
이점
주로 사용하는 환경인 Spring + JPA + Hibernate을 기준으로 설명
- Spring Transaction에 readOnly 정보가 기록, Spring은 이 정보를 Hibernate나 JDBC 같은 하위 계층에 전달
- Hibernate에서
FlushMode.MANUAL설정, JPA에서는 더티 체킹을 수행하지 않는다. (CPU/메모리 비용 감소) - 락 획득 최소화, 모든 락이 아닌 읽기 락만 획득함
- 트랜잭션 로그 생성 생략
추가로
SimpleJpaRepository와 같은 경우 기본적으로 save메서드에 트랜잭션 어노테이션이 붙어있다.
@Override
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, ENTITY_MUST_NOT_BE_NULL);
if (entityInformation.isNew(entity)) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}