1. TransactionTemplate
1.1 공식 정의

트랜잭션 경계 제어를 코드로 명시하기 위한 수단
- 프로그래밍 방식 트랜잭션 관리 도구
- 트랜잭션 시작 / 커밋 / 롤백을 템플릿이 대신 처리
- 개발자는 “트랜잭션 안에서 실행할 로직”만 작성
2. 왜 TransactionTemplate가 필요할까
2.1 @Transactional의 한계
Spring에서 가장 흔히 사용하는 트랜잭션 방식은 @Transactional이다.
@Transactional
public void save() {
// business logic
}하지만 이 방식에는 명확한 한계가 있다.
- 메서드 단위로만 트랜잭션 경계 설정 가능
- 반복문 내부에서 트랜잭션 분리 ❌
- 조건에 따라 트랜잭션 적용 여부를 바꾸기 어려움
- 멀티스레드 환경에서는 적용 불가
즉, @Transactional은 선언적 트랜잭션에는 편하지만, 세밀한 제어에는 부적합하다.
2.2 프로그래밍 방식 트랜잭션의 필요성
다음과 같은 상황에서는 선언적 트랜잭션이 부족해진다.
- 반복 작업마다 트랜잭션을 분리해야 할 때
- 멀티스레드 환경에서 명시적으로 트랜잭션을 열어야 할 때
- 트랜잭션 경계를 코드 흐름에 따라 결정해야 할 때
- 배치 / 테스트 데이터 초기화 코드
이때 TransactionTemplate을 사용한다.
2.3 @Transactional을 사용하면 어떻게 될까?
앞선 설명을 보고 나면 자연스럽게 다음 질문이 생긴다.
“그냥
@Transactional을 쓰면대량 데이터도 하나의 트랜잭션으로 처리할 수 있는 것 아닌가?”
결론부터 말하면 가능은 하지만, 이 예제에서는 적절하지 않다.
2.3.1 하나의 트랜잭션으로 묶였을 때의 문제
만약 아래와 같이 @Transactional을 사용한다면,
@Transactional
public void insertAll() {
for (int i = 0; i < 12_000_000; i++) {
entityManager.persist(article);
}
}1,200만 건의 insert가 하나의 트랜잭션으로 묶이게 된다.
이 경우 트랜잭션은 다음과 같은 문제를 갖는다.
- 트랜잭션이 지나치게 커진다
- 롤백 시 undo log 비용이 감당 불가능한 수준이 된다
- 락을 장시간 점유해 다른 트랜잭션을 막는다
- 장애 발생 시 모든 작업이 한 번에 롤백된다
2.3.2 멀티스레드 환경에서는 더 위험해진다
지금까지는 단일 스레드에서 하나의 트랜잭션이 너무 커지는 문제를 살펴봤다.
하지만 실제 대량 데이터 처리나 배치 작업에서는
처리 속도를 위해 멀티스레드 병렬 실행을 함께 사용하는 경우가 많다.
이때 @Transactional의 한계는 더욱 명확해진다.
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(() -> insert());위 코드에서 중요한 점은 다음이다.
insert()는 메인 스레드가 아닌ExecutorService가 관리하는 워커 스레드에서 실행된다
즉, 이 시점부터 트랜잭션은 **“어느 스레드에서 시작되었는가”**가 핵심 문제가 된다.
하지만 @Transactional은 다음과 같은 특성을 가진다.
- 트랜잭션은 ThreadLocal 기반으로 관리된다
- 메서드를 호출한 현재 스레드에만 적용된다
- 다른 스레드로 자동 전파되지 않는다
이 구조를 그대로 적용하면 다음과 같은 문제가 발생한다.
- 메인 스레드에서 시작한
@Transactional은 워커 스레드에서 실행되는insert()에 적용되지 않는다 - 결과적으로 워커 스레드의
persist()호출은 트랜잭션 없이 실행될 수 있다 - 트랜잭션 경계가 코드 상으로도, 실행 시점으로도 불분명해진다
즉, 멀티스레드 환경에서 @Transactional을 사용하면
- 트랜잭션이 적용되지 않거나
- 예상과 다른 시점에 커밋·롤백되거나
- 트랜잭션 경계가 개발자의 의도와 어긋난다
따라서
@Transactional은병렬 배치 작업을 전제로 설계된 도구가 아니다.
2.3.3 결과적으로 트랜잭션의 의도가 훼손된다
이런 상황에서 @Transactional은 다음 중 하나가 된다.
- 너무 커서 롤백할 수 없는 트랜잭션
- 장애 시 모든 작업을 무효화하는 트랜잭션
- 멀티스레드 환경에서 적용되지 않는 트랜잭션
3. TransactionTemplate의 역할
TransactionTemplate은 다음을 책임진다.
- 트랜잭션 시작 (BEGIN)
- 콜백 코드 실행
- 정상 종료 시 → COMMIT
- 예외 발생 시 → ROLLBACK
개발자는 트랜잭션 제어 코드를 직접 작성하지 않는다.
4. TransactionTemplate의 사용 방식
4.1 기본 사용 예시
transactionTemplate.executeWithoutResult(status -> {
// 트랜잭션 내부 로직
});이 코드의 의미는 다음과 같다.
- 람다 시작 시점 → 트랜잭션 시작
- 람다 정상 종료 → 커밋
- 예외 발생 → 롤백
람다 블록 자체가 하나의 트랜잭션 경계
4.2 execute vs executeWithoutResult
| 메서드 | 설명 |
|---|---|
execute() | 결과를 반환하는 트랜잭션 |
executeWithoutResult() | 결과 없이 실행 |
5. 예시
아래 코드는 대량 데이터를 병렬로 삽입하는 테스트 코드의 일부다.
@Autowired
TransactionTemplate transactionTemplate;
void insert() {
transactionTemplate.executeWithoutResult(status -> {
// 이 블록 진입 시점에 트랜잭션 시작
for (int idx = 0; idx < BULK_INSERT_SIZE; idx++) {
Article article = Article.create(...);
entityManager.persist(article);
}
// 이 블록 정상 종료 시점에 flush + commit
});
}이 코드에서 TransactionTemplate의 역할은 단순히 “트랜잭션을 여는 것”에 그치지 않는다.
- Spring이 관리하는
PlatformTransactionManager를 기반으로 트랜잭션의 시작·커밋·롤백을 코드 블록 단위로 제어한다 - 선언적 트랜잭션(
@Transactional)로는 불가능한 세밀한 트랜잭션 경계 제어를 가능하게 한다 - 멀티스레드 환경에서도 각 작업을 독립적인 트랜잭션으로 분리한다
즉, 이 코드는 배치 작업과 병렬 실행을 전제로 한 트랜잭션 설계다.
5.1 트랜잭션 단위 제어
void insert() {
transactionTemplate.executeWithoutResult(status -> {
for (int idx = 0; idx < BULK_INSERT_SIZE; idx++) {
entityManager.persist(article);
}
});
}이 코드의 트랜잭션 단위는 다음과 같이 정의된다.
insert()호출 1회 = 트랜잭션 1개- 트랜잭션마다 독립적인 영속성 컨텍스트 생성
- 코드 블록이 정상 종료되는 시점에 flush → commit 순서로 트랜잭션이 완료된다
중요한 점은 트랜잭션의 경계가 메서드 선언이 아니라 코드 블록에 명시되어 있다는 것이다.
즉,
TransactionTemplate은트랜잭션 단위를 메서드가 아닌 코드 흐름 기준으로 제어하게 해준다.
이 특성 덕분에 반복문, 조건문, 배치 로직과 자연스럽게 결합할 수 있다.
5.2 멀티스레드 환경에서의 의미
executorService.submit(() -> insert());이 코드와 TransactionTemplate이 결합되면 멀티스레드 환경에서도 트랜잭션 경계가 명확해진다.
이 구조에서의 동작은 다음과 같다.
- 각 스레드는
insert()를 독립적으로 호출 - 호출될 때마다
TransactionTemplate이 새 트랜잭션을 생성 - 생성된 트랜잭션은 해당 스레드에 바인딩
- 스레드 간 트랜잭션 공유는 발생하지 않는다
그 결과:
- 한 스레드의 실패가 다른 스레드의 트랜잭션에 영향을 주지 않고
- 각 작업은 독립적으로 커밋 또는 롤백된다
따라서 이 구조는 병렬 배치 작업에서 요구되는 트랜잭션 안정성을 만족한다.
5.3 정리
이 예제에서 TransactionTemplate의 핵심 역할은 다음과 같다.
- 트랜잭션 경계를 코드 블록 단위로 명확히 표현
- 반복·배치·병렬 작업에 적합한 트랜잭션 구조 제공
- 멀티스레드 환경에서도 안전한 트랜잭션 분리 보장
이 때문에 해당 코드에서는 @Transactional이 아니라 TransactionTemplate이 선택되었다.
6. TransactionTemplate를 사용하는 경우
6.1 적절한 경우
- 배치 / 대량 insert 작업
- 반복문 내부에서 트랜잭션을 분리해야 할 때
- 멀티스레드 환경
- 테스트 데이터 초기화
- 트랜잭션 경계를 명시적으로 제어해야 할 때
6.2 피해야 하는 경우
- 일반적인 서비스 로직
- 단순 CRUD
- 트랜잭션 경계가 메서드 단위로 충분한 경우
이 경우에는 @Transactional이 더 간결하고 읽기 쉽다.
7. @Transactional과 TransactionTemplate 비교
| 구분 | @Transactional | TransactionTemplate |
|---|---|---|
| 방식 | 선언적 | 프로그래밍 |
| 트랜잭션 경계 | 메서드 단위 | 코드 블록 단위 |
| 반복문 제어 | 어려움 | 가능 |
| 멀티스레드 | 부적합 | 적합 |
| 가독성 | 높음 | 상대적으로 낮음 |
8. 요약
TransactionTemplate은 선언적 트랜잭션이 감당하지 못하는 영역에서,트랜잭션 경계를 코드로 직접 제어하기 위해 제공되는 Spring의 도구다.
참고 자료 (출처)
- Spring Framework Reference – Transaction Management https://docs.spring.io/spring-framework/reference/data-access/transaction.html
- Spring API Docs – TransactionTemplate https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/support/TransactionTemplate.html