본문 바로가기
language/java

ThreadLocal과 TransactionSynchronizationManager

by 죄니안죄니 2026. 6. 24.
반응형

이번 글은 Spring 내부를 이해하는 핵심 글이다.

사실 많은 개발자가

"ThreadLocal에 Connection이 저장된다."

까지만 알고 있다.

하지만 Spring은 직접 ThreadLocal을 여기저기 사용하지 않는다.

그 역할을 하는 중앙 관리자(Central Manager)가 있다.

바로

TransactionSynchronizationManager(TSM)

이다.

이 글을 이해하면

  • @Transactional
  • MyBatis
  • JPA(Hibernate)
  • Spring Data

가 어떻게 같은 Connection을 공유하는지 이해할 수 있다.


ThreadLocal과 TransactionSynchronizationManager

Spring은 ThreadLocal을 직접 사용할까?

이전 글에서는

Spring Transaction이

ThreadLocal

↓

Connection 저장

↓

Repository에서 조회

↓

Commit

↓

ThreadLocal.remove()
 

구조로 동작한다고 배웠다.

이 설명은 개념적으로는 맞다.

하지만 실제 Spring 내부는 조금 더 정교하게 설계되어 있다.

Spring은

 
ThreadLocal<Connection>
 

를 여기저기 직접 사용하는 것이 아니라,

모든 리소스를 하나의 중앙 관리자에게 맡긴다.

그 객체가 바로

TransactionSynchronizationManager(TSM)

이다.


먼저 질문 하나

하나의 요청에서

Controller

↓

Service

↓

MyBatis

↓

JPA

↓

JdbcTemplate
 

가 모두 실행된다면

Connection은 어디서 관리해야 할까?


각각이

 
ThreadLocal<Connection>
 

를 만든다면

관리하기 어렵다.


그래서 Spring은

TransactionSynchronizationManager

↓

ThreadLocal(Map)

↓

모든 리소스 관리
 

구조를 사용한다.


내부 구조

실제로는 단순한 Connection 하나가 아니다.

대략 다음과 같은 구조이다.

Thread-1

↓

ThreadLocal

↓

TransactionSynchronizationManager

↓

resources(Map)

↓

DataSource → Connection

SqlSessionFactory → SqlSession

EntityManagerFactory → EntityManager
 

즉,

Thread마다

하나의 리소스 저장소(Map)를 가진다.


그림으로 보면

Thread-1

↓

ThreadLocal

↓

──────────────────────────

resources

--------------------------

DataSource

↓

Connection

--------------------------

SqlSessionFactory

↓

SqlSession

--------------------------

EntityManagerFactory

↓

EntityManager

──────────────────────────
 

왜 Map일까?

예를 들어

하나의 요청에서

MyBatis

+

JPA

+

JdbcTemplate
 

를 함께 사용할 수도 있다.


Connection만 저장하면

Connection 하나

↓

끝
 

이다.


하지만 실제로는

Connection

SqlSession

EntityManager

Transaction 정보

Synchronization 정보
 

등을 함께 관리해야 한다.


그래서 Map 구조가 필요하다.


@Transactional 시작

Proxy가

 
@Transactional
 

을 발견한다.


실행 흐름

Proxy.invoke()

↓

TransactionManager.begin()

↓

Connection 생성

↓

TSM.bindResource()

↓

Real Service 실행
 

bindResource()

Spring 내부에서는

대략 이런 코드가 실행된다.

 
TransactionSynchronizationManager

        .bindResource(

                dataSource,

                connection

        );
 

결과

resources

--------------------------

DataSource

↓

Connection

--------------------------
 

이 저장된다.


Repository에서는?

Repository는

Connection을 전달받지 않는다.


 
jdbcTemplate.query(...);
 

또는

 
sqlSession.selectList(...);
 

만 호출한다.


그런데 내부에서는

 
DataSourceUtils.getConnection(dataSource);
 

가 실행된다.


그리고

TransactionSynchronizationManager

↓

resources 조회

↓

Connection 반환
 

을 수행한다.


Repository는

Connection을 몰라도

같은 Connection을 사용하게 된다.


전체 흐름

Controller

↓

Transaction Proxy

↓

TransactionManager.begin()

↓

TSM.bindResource()

↓

OrderService

↓

OrderRepository

↓

TSM.getResource()

↓

Connection 반환

↓

DB
 

JPA도 동일하다.

JPA는

Connection 대신

EntityManager를 사용한다.


하지만 구조는 같다.

TransactionSynchronizationManager

↓

EntityManagerFactory

↓

EntityManager
 

Service

Repository

EntityManager 조회

동일 EntityManager 사용


MyBatis도 동일하다.

TransactionSynchronizationManager

↓

SqlSessionFactory

↓

SqlSession
 

결국

MyBatis

JPA

JdbcTemplate
 

모두

같은 방식으로 동작한다.


Synchronization은 무엇일까?

TSM 이름을 보면

Transaction

Synchronization

Manager
 

이다.


ResourceManager가 아니라

SynchronizationManager인 이유가 있다.


Transaction 종료 시

해야 할 일이 많다.

Commit

↓

Event 발생

↓

Connection Close

↓

ThreadLocal 제거
 

이 작업들을

순서대로 실행하기 위해

Synchronization 객체들을 등록한다.


registerSynchronization()

Spring 내부에서는

 
TransactionSynchronizationManager

        .registerSynchronization(...);
 

를 수행한다.


Commit 시

beforeCommit()

↓

afterCommit()

↓

afterCompletion()
 

이 자동으로 호출된다.


@TransactionalEventListener도 여기서 동작한다.

 
@TransactionalEventListener

public void sendMail(...) {

}
 

이 이벤트는

메서드가 끝나는 즉시 실행되지 않는다.


Commit 성공

↓

afterCommit()

↓

Event 실행
 

이다.


이 역시

TSM의 Synchronization 기능이다.


ThreadLocal과 비교

우리가 직접 사용한다면

 
ThreadLocal<Connection>
 

이다.


Spring은

ThreadLocal

↓

TransactionSynchronizationManager

↓

Map

↓

Connection

SqlSession

EntityManager

Synchronization
 

를 사용한다.


훨씬 확장성이 좋다.


Thread 종료

Request 종료

Commit

Connection Close

TSM.unbindResource()

ThreadLocal.remove()


모든 리소스가 정리된다.


remove가 중요한 이유

Tomcat은

ThreadPool을 사용한다.


Request1

↓

Thread-1

↓

Connection 저장
 

정리하지 않으면

Request2

↓

Thread-1 재사용

↓

이전 Connection 사용
 

그래서 Spring은

항상

 
try {

    ...

}

finally {

    TransactionSynchronizationManager

            .clear();

}
 

패턴으로 동작한다.


이전 글들과 연결

지금까지의 흐름을 모두 연결하면

HTTP Request

↓

Security Filter

↓

ThreadLocal(Authentication)

↓

Controller

↓

Transaction Proxy

↓

TransactionManager

↓

TransactionSynchronizationManager

↓

ThreadLocal(Map)

↓

Connection

SqlSession

EntityManager

↓

Repository

↓

DB

↓

Commit

↓

Synchronization 실행

↓

ThreadLocal.clear()

↓

Response
 

Spring 내부에서 TSM을 사용하는 대표 기능

Transaction

↓

Connection 공유

----------------

JPA

↓

EntityManager 공유

----------------

MyBatis

↓

SqlSession 공유

----------------

@TransactionalEventListener

↓

afterCommit 실행

----------------

Synchronization Callback
 

실무에서는 직접 사용할까?

거의 사용하지 않는다.


하지만

 
TransactionSynchronizationManager

        .isActualTransactionActive();
 

또는

 
TransactionSynchronizationManager

        .getResource(...)
 

처럼

프레임워크 개발이나 라이브러리 개발에서는 자주 등장한다.


면접 포인트

TransactionSynchronizationManager란?

Spring이 트랜잭션과 관련된 리소스(Connection, EntityManager, SqlSession 등)를 ThreadLocal 기반으로 중앙 관리하는 클래스이다.


왜 ThreadLocal 하나가 아니라 Map을 사용하는가?

하나의 요청에서 Connection뿐 아니라 EntityManager, SqlSession, Synchronization 등 여러 리소스를 함께 관리해야 하기 때문이다.


Repository는 어떻게 같은 Connection을 사용하는가?

DataSourceUtils.getConnection()이 TransactionSynchronizationManager에서 현재 Thread에 바인딩된 Connection을 조회하기 때문이다.


@TransactionalEventListener는 언제 실행되는가?

TransactionSynchronizationManager에 등록된 Synchronization이 Commit 이후(afterCommit)에 실행하면서 호출된다.


다음 글 추천

이제 자연스럽게 ExecutorService와 @Async로 넘어가는 것을 추천한다.

그 이유는 바로 지금까지 배운 ThreadLocal의 한계를 보여줄 수 있기 때문이다.

Controller(Thread-1)

↓

@Transactional

↓

ThreadLocal(Connection)

↓

@Async

↓

Thread-2

↓

★★★★★ ThreadLocal이 사라진다 ★★★★★
 

즉,

왜 @Async 안에서 SecurityContext, Transaction, ThreadLocal이 전파되지 않는지,

그리고 Spring이 이를 어떻게 해결하는지까지 연결하면 독자가 Spring 내부를 훨씬 깊게 이해하게 된다.

반응형

'language > java' 카테고리의 다른 글

ExecutorService와 @Async  (0) 2026.06.24
ThreadLocal과 Spring Security  (0) 2026.06.24
Java Proxy 기반 트랜잭션 처리  (0) 2026.06.17
JDK Proxy vs CGLIB in Spring  (0) 2026.06.17
Dynamic Proxy와 Spring AOP  (0) 2026.06.17

댓글