@Transactional 이란?
@Transactional 은 선언적 트랜잭션 방식으로, Spring 에서 Transaction 처리를 위해 제공하는 어노테이션이다. Spring은 Transaction처리를 위해 Spring AOP를 사용한다. 이때, @Transaction 어노테이션이 붙은 메서드 호출 결과에 따라 Transaciton Commit, Rollback 동작이 일어난다. ( Rollback은 Unchecked Exception)
@Transactional 어노테이션이 붙은 메서드가 호출되면 다음과 같은 동작이 일어난다.
- Transaction Manager 선별
- Datasource로부터 Connection 객체를 얻어오고 이를 ConnectionHolder에 넣고 트랜잭션 동기화 매니저를 통해 ThreadLocal 영역에 저장
- 트랜잭션 동기화 매니저에 트랜잭션 동기화
- 쿼리 실행시 트랜잭션 동기화 매니저를 통해 Connection을 얻어와 쿼리 실행
@Transactional은 기본적으로 전파된다. 전파 속성을 지정할 수 있는데 Default 값은 REQUIRED 이다. (부모 트랜잭션 상속)
- REQUIRED_NEW : 부모 트랜잭션 존재 여부와 상관없이 무조건 새로운 트랜잭션을 생성한다.
- NEVER : 트랜잭션을 사용하지 않는다.
readOnly 옵션이 true 라면 영속성 컨텍스트 생성시 더티 체킹을 위한 스냅샷 유지, 쓰기지연 SQL를 수행하기 위한 동작이 스킵되기 때문에 더 좋은 성능으로 읽기 작업을 수행할 수 있다.
TransactionManager
- TransactionManager는 트랜잭션 처리를 위한 동작들을 추상화한 인터페이스다.
- Spring 은 트랜잭션 시작 시점에 트랜잭션 매니저로부터 Connection을 받아 TransactionSynchronizationManager에 Connection을 보관한다.
- Connection은 Thead Local한 TransactionSynchronizationManager의 ConnectionHolder 에 저장된다.
- 이후 데이터 접근 로직이 수행될 때 Connection을 받아 실제 데이터베이스 서버에 요청을 전송하게 된다. 이를 통해 하나의 요청이 같은 Connection을 사용할 수 있도록 보장해주는 것이다.
Spring에서는 JDBC, JPA, R2DBC 등 다양한 방법으로 데이터베이스에 접근한다. 각각의 기술들이 Transaction을 처리하는 방식은 약간씩 다르다. JDBC를 사용하다가 JPA를 도입할 경우 Transaction과 관련된 로직들을 전부 수정해야하는 것이다. Spring에서는 이러한 문제를 해결하기 위해, Transaction 처리 로직을 PlatformTransactionManager라는 인터페이스로 추상화했다.
// PlatformTransactionManager 인터페이스 명세
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
- Spring은 PlatformTransactionManager라는 인터페이스에 의존하여 JDBC를 쓰던, JPA를 쓰던 동일한 방식으로 트랜잭션을 처리할 수 있는 것이다.
ReadOnly
- Connection 자체를 ReadOnly로 설정함
- FlushMode 도 MANUAL로 설정하는데, MANUAL은 Session.flush() 메서드가 호출될때만 플러시됨. 이는 읽기 전용 트랜잭션에서 매우 효율적으로 동작함
- Entity를 실질적으로 관리하는, EntityManager를 상속받는 인터페이스인 Session의 setDefaultReadOnly를 true로 설정하는데 해당 명세에는 다음과 같은 문구가 있음
- Read-only 엔티티들은 더티 체킹과 영속 상태의 스냅샷을 관리하지 않는다.
- Read-only 엔티티들은 수정될 수 있지만 수정된 내용을 영속하진 않는다.
- Session 인터페이스에는 isDirty() 나 equalsSnapshot() 과 같은 메서드를 통해 더티 체킹하는데 read-only가 true라면 이 동작이 일어나지 않음
- 참고로 Snapshot을 비교할땐 최초 Entity 조회시점에 속성값을 전부 복사하여 하나의 인스턴스를 만들어놓고 추후에 해당 인스턴스와 속성값을 비교하는 방식으로 더티 체킹을 수행함
- @DynamicUpdate를 사용하지 않는다면 기본적으로 더티 체킹을 통한 update 쿼리는 모든 필드를 대상으로 발생함.
- @DynamicUpdate 사용시 변경이 일어난 필드만 update
Propagation
- 트랜잭션이 진행중일때 추가 트랜잭션 진행을 어떻게 할지 결정하는 것을 말함
- 여러 논리 트랜잭션을 묶어 하나의 물리 트랜잭션을 생성함
- 물리 트랜잭션은 실제 데이터베이스에 적용되는 트랜잭션이다.
- 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
- 다음과 같이 신규 트랜잭션에 참여하는 내부 트랜잭션에서 예외가 발생한다면 다음과 같은 동작이 일어남
- 내부 트랜잭션에서 rollback-only 라는 속성값을 체크함
- 신규 트랜잭션에서는 해당 값을 보고, 이를 처리하는 별도의 설정이 없다면 예외가 발생하게됨
- Transaction AOP에서는 TransactionManager에게 rollback을 요청함
- Transaction이 rollback됨
- 로그 저장에 실패해도 비즈니스 로직이 rollback 되지 않게하려면 새로운 트랜잭션을 생성하여 분리하면 됨
- @Transactional(propagation = REQUIRES_NEW)
- 당연하게도 트랜잭션의 개수마다 Connection을 사용함
'TIL' 카테고리의 다른 글
[TIL] GC (0) | 2025.01.06 |
---|---|
[TIL] HTTP 버전별 특징 (0) | 2024.12.30 |
[TIL] SQL Injection (0) | 2024.12.16 |
[TIL] AWS 인프라 및 서비스 개요 (1) | 2024.12.09 |
[TIL] DB 트랜잭션 (0) | 2024.12.02 |