@Transactional이 붙은 메서드를 호출하면 트랜잭션 AOP 프록시가 호출을 가로채서 트랜잭션 매니저(PlatformTransactionManager) 로 트랜잭션을 시작/종료하고, 그 과정에서 TransactionSynchronizationManager(ThreadLocal) 에 커넥션(또는 EntityManager)을 바인딩하여 같은 스레드에서 동일 트랜잭션 리소스가 재사용되도록 만듭니다.
@Transactional 대상 빈을 프록시로 감싸 메서드 실행 전/후에 트랜잭션 경계를 자동으로 처리합니다.TransactionInterceptor(Advice)가 트랜잭션 로직을 수행합니다.DataSourceTransactionManagerJpaTransactionManagergetTransaction(...)commit(...) / rollback(...)@Transactional 대상 빈을 감지해 AOP 프록시를 생성합니다.@Transactional 메서드를 호출합니다.txManager.getTransaction(...)을 호출합니다.DataSource에서 Connection 획득autoCommit=false 등 설정TransactionSynchronizationManager에 바인딩(bind) 합니다.TransactionSynchronizationManager에 바인딩된 리소스를 찾아 사용합니다.txManager.commit(...)을 호출합니다.TransactionSynchronizationManager에서 리소스 언바인딩(unbind)rollback을 결정합니다.RuntimeException, Error → 롤백rollbackFor로 변경)txManager.rollback(...) 수행 후 리소스 언바인딩/정리까지 진행합니다.this.otherMethod()로 호출하면 프록시를 거치지 않아
@Transactional이 적용되지 않을 수 있습니다.readOnly=true는 “쓰기 금지”를 강제한다기보다, 플러시 전략 등 최적화 힌트 성격이 큽니다.Client
|
v
[Tx AOP Proxy]
|
| getTransaction()
v
[PlatformTransactionManager]
|
| bind(Connection/EntityManager) to ThreadLocal
v
Target(Service/Repository)
|
| 정상: commit() / 예외: rollback()
v
unbind + resource cleanup
DataSourceTransactionManagerJpaTransactionManagerorderService.placeOrder() (메서드에 @Transactional 존재)@Transactional의 속성(전파, 격리, readOnly, rollbackFor 등)을 해석합니다.transactionManager.getTransaction(...) 호출autoCommit=false 설정 및 격리 수준 적용(필요 시)TransactionSynchronizationManager에 저장합니다.DataSourceUtils.getConnection(...) 같은 경로로 바인딩된 커넥션을 재사용합니다.transactionManager.commit(...) 호출TransactionSynchronizationManager에서 리소스 언바인딩rollbackFor로 지정)transactionManager.rollback(...) 수행this.otherTransactionalMethod()로 호출하면 프록시를 거치지 않아 AOP가 동작하지 않을 수 있습니다.REQUIRED(기본): 있으면 참여, 없으면 새로 생성REQUIRES_NEW: 무조건 새 트랜잭션(기존 트랜잭션은 잠시 중단)클라이언트 → (프록시) TransactionInterceptor → PlatformTransactionManager.getTransaction() → TransactionSynchronizationManager.bindResource(connection/EntityManager) → 타깃 메서드 실행 → commit 또는 rollback → TransactionSynchronizationManager.unbindResource(...) → 커넥션 반환
@Transactional 메서드 호출TransactionAttribute(전파, 격리수준, readOnly, timeout, 롤백 규칙 등)를 해석getTransaction(...) 호출DataSource/EntityManagerFactory로부터 리소스를 확보하고 트랜잭션을 시작commit 경로로 이동rollback 규칙에 따라 rollback 또는 commit을 결정@Transactional(rollbackFor = ...) 로 조정REQUIRED(기본): 트랜잭션이 있으면 참여, 없으면 새로 시작REQUIRES_NEW: 기존 트랜잭션을 잠시 중단하고 항상 새 트랜잭션 시작this.someTxMethod()처럼 자기 자신 메서드를 호출하면 프록시를 거치지 않아 @Transactional이 적용되지 않을 수 있습니다.readOnly=true는 “읽기 전용 의도”를 힌트로 주며,
JdbcTemplate, JPA Repository)
RuntimeException과 Error에 대해 롤백합니다.@Transactional(rollbackFor = Exception.class)처럼 커스터마이징합니다.REQUIRED(기본): 기존 트랜잭션이 있으면 참여, 없으면 새로 생성REQUIRES_NEW: 기존 트랜잭션을 일시 중단하고 새 트랜잭션 생성SUPPORTS, MANDATORY, NESTED 등은 상황에 따라 사용@Transactional(readOnly = true)는
Self-invocation 문제
this.someTxMethod()로 호출하면 프록시를 거치지 않아 @Transactional이 적용되지 않을 수 있습니다.프록시 적용 범위
public 메서드에 주로 적용되며(전통적 프록시 기반 AOP), 설정/방식에 따라 private/final 등은 제약이 있을 수 있습니다.트랜잭션은 스레드에 묶인다
예외를 잡아먹으면 롤백이 안 될 수 있다
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 등을 고려합니다.기본 규칙: RuntimeException 및 Error는 롤백, Checked Exception은 커밋 (단,
rollbackFor,noRollbackFor로 변경 가능)
요점은, 전파 설정에 따라 TransactionSynchronizationManager에 바인딩되는 리소스의 수명/범위가 달라지고, 그에 따라 “같은 트랜잭션”이 결정됩니다.
Self-invocation(자기 자신 호출) 문제
this.someTxMethod()처럼 호출하면 프록시를 거치지 않아 @Transactional이 적용되지 않을 수 있습니다.프록시 적용 범위
readOnly=true의 의미
트랜잭션 경계는 짧게
Client
|
v
[Tx AOP Proxy]
| 1) getTransaction()
v
[PlatformTransactionManager]
| 2) bind Connection/EM to ThreadLocal (TransactionSynchronizationManager)
v
[Target Method 실행]
|
| 3) 정상: commit() / 예외: rollback()
v
[PlatformTransactionManager]
| 4) unbind + resource cleanup
v
Client로 반환
Self-invocation(자기 자신 호출)
this.someTxMethod()처럼 호출하면 프록시를 거치지 않아 @Transactional이 적용되지 않습니다.프록시 생성 방식에 따른 한계(JDK 동적 프록시 vs CGLIB)
트랜잭션 경계가 너무 넓으면
예외 처리로 롤백이 안 되는 케이스
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 같은 방식이 필요할 수 있습니다.getTransaction()Self-invocation(자기 자신 내부 호출)은 프록시를 거치지 않아 트랜잭션이 적용되지 않을 수 있습니다.
this.someTransactionalMethod() 호출프록시 기반 AOP는 기본적으로 public 메서드에 적용되는 구성이 일반적입니다.
**트랜잭션 리소스는 “스레드에 바인딩”**됩니다.
@Async, 별도 스레드 풀, 리액티브 체인 등으로 스레드가 바뀌면 같은 트랜잭션이 자동으로 이어지지 않습니다.예외를 잡아먹으면(try/catch) 롤백이 안 될 수 있음
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 같은 전략이 필요할 때가 있습니다.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 같은 방식으로 롤백 마킹이 필요할 수 있습니다.이 3가지가 합쳐져서, 개발자는 @Transactional만 선언해도 서비스/레포지토리 호출 전반에 걸쳐 일관된 트랜잭션을 사용할 수 있습니다.