[JOOQ] SpringBoot 환경에서 jOOQ transaction
Spring Boot에서 jOOQ를 사용할 때 @Transactional 을 활용해 트랜잭션을 관리해왔습니다.
이번 포스팅에서는 jOOQ의 트랜잭션 처리 방식에 대해 정리해보겠습니다.
1️⃣ Spring과 jOOQ를 같이 쓰는 환경에서 트랜잭션 처리 방법
🏖️ Spring에서의 트랜잭션 처리
Spring에서는 일반적으로 선언적 트랜잭션 관리 방식을 사용합니다. 가장 대표적인 방법은 @Transactional 어노테이션을 사용하는 것이며, 이는 AOP 기반의 프록시 방식으로 동작합니다.
이 방식은 코드 외부에서 트랜잭션을 선언적으로 처리할 수 있어 간편하지만, 메서드 내부 호출에는 적용되지 않는 등의 제약이 있습니다.
그런 경우에는 프로그래밍 방식으로 트랜잭션을 제어할 수 있는 TransactionTemplate을 사용할 수 있습니다.
즉, Spring에서는 다음 두 가지 방식으로 트랜잭션을 제어합니다.
선언적 트랜잭션 관리 : @Transactional 어노테이션 사용 (AOP 기반)
명시적 트랜잭션 관리 : TransactionTemplate 을 사용한 직접 제어 방식
🏖️ jOOQ에서의 트랜잭션 처리
jOOQ도 Spring 스타일로 트랜잭션을 처리할 수 있습니다.
즉, @Transactional 어노테이션이나 TransactionTemplate을 사용하는 것도 가능합니다.
하지만 jOOQ는 Spring 프레임워크에 의존하지 않도록 설계되었기 때문에, 자체적으로 트랜잭션을 제어할 수 있는 API도 제공합니다.
jOOQ의 트랜잭션 API는 DSLContext.transaction(...) 메서드를 통해 제공되며, 람다 형식으로 트랜잭션 블록을 정의할 수 있습니다.
dsl.transaction(configuration -> {
DSLContext ctx = DSL.using(configuration);
ctx.insertInto(...).execute();
ctx.update(...).execute();
});
이 방식은 jOOQ 내부의 DefaultTransactionProvider에 의해 트랜잭션이 시작되고 커밋/롤백됩니다.
Spring과 jOOQ의 환경에서 트랜잭션 처리 방법은 아래처럼 정리할 수 있습니다.
1. jooq transaction API를 무시하고 Spring 스타일의 트랜잭션 사용 (추천)
2. Spring 스타일을 무시하고 jooq transaction API 사용 (비추천)
3. Spring 스타일과 jooq transacrion API를 혼용 (금지)
2️⃣ 트랜잭션 전파(Propagation)
🏖️ 트랜잭션 전파(Propagation)
트랜잭션 propagation은 이미 실행 중인 트랜잭션이 있을 때, 새로운 트랜잭션 동작을 어떻게 할 것인지 제어하는 메커니즘입니다.
REQUIRED | 이미 실행 중인 트랜잭션에 참여하며, 없는 경우 새로운 트랜잭션을 시작 |
REQUIRES_NEW | 항상 새로운 트랜잭션을 시작하며, 현재 실행 중인 트랜잭션이 있더라도 새로운 트랜잭션을 시작 |
SUPPORTS | 현재 실행 중인 트랜잭션에 참여하며, 트랜잭션이 없는 경우에는 트랜잭션 없이 실행 |
MANDATORY | 현재 실행 중인 트랜잭션이 없는 경우 예외를 발생시키며, 이미 존재하는 트랜잭션 내에서 실행 |
NEVER | 현재 실행 중인 트랜잭션이 있는 경우 예외를 발생시키며, 트랜잭션 없이 실행 |
NOT_SUPPORTED | 현재 실행 중인 트랜잭션을 일시 중단시키고, 트랜잭션 없이 실행 트랜잭션이 없는 경우에도 트랜잭션 없이 실행 |
NESTED | 새로운 중첩 트랜잭션을 시작하며, 외부 트랜잭션이 커밋되거나 롤백될 때까지 독립적으로 커밋되거나 롤백 |
- Spring Default : REQUIRED
- jOOQ Default : NESTED
🏖️ NESTED vs REQUIRED
- REQUIRED : 내부 예외가 rollback되면 전체 트랜잭션이 롤백 표시가 되기 때문에 외부에서도 rollback되거나 UnexpectedRollbackException 발생
- NESTED : Savepoint 생성 후 롤백해도 외부 트랜잭션은 계속 진행 가능
Spring과 달리 jOOQ는 NESTED (savepoint 기반) 전파 전략을 기본값으로 사용합니다.
창시자인 루카스 에더는 "중첩 메서드에서 발생한 실패는 독립적으로 rollback되어야 한다"고 말하며,
트랜잭션 전파 기본을 NESTED로 설정했다고 말합니다.
해당 내용은 jOOQ 블로그에서 더 자세히 확인할 수 있습니다.
3️⃣ jOOQ transaction api 트랜잭션
🏖️ SPI (Service Provider Interface)
SPI : 특정 서비스의 구현을 외부에 위임하고, 이를 유연하게 변경할 수 있게 하는 인터페이스
jOOQ는 트랜잭션 동작을 트랜잭션 프로바이더라는 인터페이스의 구현체들로 처리를 합니다.
여기서 SPI는 특정 로직, 서비스의 구현을 외부 인터페이스에 위임하는 것이라고 합니다.
🏖️ Transasction Provider 구현체들
SpringTransactionProvider
SpringTransactionProvider는 jOOQ에서 제공하는 트랜잭션 관리 구현체로,
Spring Boot에서 jOOQ를 자동 설정할 때 기본적으로 적용됩니다.
이 구현체는 내부적으로 Spring의 PlatformTransactionManager를 사용하여 트랜잭션을 관리합니다.
예를 들어, DataSourceTransactionManager나 JpaTransactionManager는 모두 PlatformTransactionManager의 구현체로,
각각 JDBC 기반 데이터 소스와 JPA를 사용하는 경우에 활용됩니다.
DefaultTransactionProvider
DefaultTransactionProvider는 Spring Boot나 Spring을 사용하지 않는 환경에서 jOOQ가 기본으로 사용하는 트랜잭션 관리 구현체입니다.
이 구현체는 JDBC의 수동 트랜잭션(commit/rollback)을 직접 제어하며, 외부 트랜잭션 매니저와의 통합 없이 동작합니다.
ThreadLocalTransactionProvider
ThreadLocalTransactionProvider는 ThreadLocal 범위를 가지는 트랜잭션 프로바이더로,
시작된 트랜잭션은 생성된 쓰레드 내에서만 유효하며 다른 쓰레드로 전파될 수 없습니다.
이러한 제한으로 인해 일반적인 애플리케이션에서는 잘 사용되지 않으며, 특수한 상황에서만 활용됩니다.
NoTransactionProvider
NoTransactionProvider는 트랜잭션 처리를 아예 수행하지 않는 트랜잭션 프로바이더입니다.
트랜잭션이 필요 없는 특수한 경우를 제외하면 일반적인 애플리케이션에서는 사용할 일이 없습니다.
오른쪽 세 종류가 Spring을 사용하지 않을 때 jOOQ에서 기본적으로 제공해주는 TransactionProvider 입니다.
🧾 결론 정리
Spring과 jOOQ를 함께 사용하는 환경에서는 jOOQ의 트랜잭션 API보다 Spring 스타일의 트랜잭션 방식(@Transactional)을 사용하는 것이 가장 안정적이고 권장됩니다.
jOOQ는 Spring 트랜잭션 컨텍스트 내에서도 정상적으로 동작하며, 굳이 jOOQ 고유의 트랜잭션 API를 사용할 필요는 없습니다.
정리하면, 우리가 지금까지 해왔던 것처럼 Spring의 선언적 트랜잭션 방식(@Transactional)을 사용하는 것을 추천합니다.
🧾 참고 자료
실전 jOOQ! Type Safe SQL with Java 강의 - 20. 트랜잭션 - Spring Style transaction vs jOOQ transaction API
jOOQ 블로그 - https://blog.jooq.org/nested-transactions-in-jooq/