JAVA/STUDY
[WebFlux] WebFlux 사용 시 Transaction rollback 구현하기 , flatMap , JDBC vs Reactor
Jungry_
2025. 5. 20. 21:05
반응형
Spring mvc 사용할땐 @Transactional 을 사용하면 모든게 다 해결 되었지만 .. WebFlux 를 사용하면 그렇지 않다 슬프다 !
그럼 어떻게 해야할까 바로바로 TransactionalOperator 를 사용해야한다 !
일단 build.gradle 에 해당 내용을 추가해준다 특이하게 aop 를 추가해야함
implementation 'org.springframework.boot:spring-boot-starter-aop:3.4.5'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
그리고 ReactiveTransactionManager 를 설정해줘야함 !!!
package com.example.toygry.webfluxstudy.Board.config
import io.r2dbc.spi.ConnectionFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.r2dbc.connection.R2dbcTransactionManager
import org.springframework.transaction.ReactiveTransactionManager
import org.springframework.transaction.annotation.EnableTransactionManagement
@Configuration
@EnableTransactionManagement
class R2dbcTransactionManager {
@Bean
fun transactionManager(connectionFactory: ConnectionFactory): ReactiveTransactionManager {
return R2dbcTransactionManager(connectionFactory)
}
}
최종적으로 코드에서는
@Service
class BoardService(
private val boardRepository: BoardRepository,
private val txOperator: TransactionalOperator
) {
private val log = LoggerFactory.getLogger(javaClass)
fun createBoard(request: CreateBoardRequest): Mono<BoardResponse> {
val board = Board(title = request.title, content = request.content)
return boardRepository.save(board)
.map { saved ->
BoardResponse(
id = saved.id!!,
title = saved.title,
content = saved.content
)
}
.`as`(txOperator::transactional)
}
중요 부분은 `as` 이 부분이다
as(txOperator::transactional) 를 사용하게 되는데
전체 체인을 트랜잭션 컨텍스트로 감싸기 -> 예외 발생 시 rollback 이 됨
따라서 rollback 을 시키고싶은 부분들에 as 를 사용해서 묶어주면 되는 것이다 ! 어렵지 않음 ~
그럼 왜 둘 사이에 이런 차이가 발생하는지 알아보자 !
JDBC vs R2DBC
JDBC | R2DBC | |
차이 | RDBMS(관계형 데이터베이스)에 접근하기 위한 블로킹용 API | RDBMS(관계형 데이터베이스)에 접근하기 위한 논블로킹용 API |
프로그래밍 모델 | 동기 (blocking) | 비동기 (non-blocking) |
스레드 처리 | 요청마다 별도 스레드 사용 (1 req = 1 thread) | 이벤트 루프 기반, 스레드 적게 씀 |
리소스 사용 | 스레드 수에 비례하여 커넥션 사용 | 커넥션 재사용 + 효율적 |
예: .findById() | User user = repo.findById(id) → 결과 나올 때까지 대기 | Mono<User> → 결과 나올 때까지 블로킹 없음 |
예외 처리 | try-catch | .onErrorResume { ... } |
트랜잭션 처리 | @Transactional 사용 (AOP 기반) | TransactionalOperator로 감쌈 |
쓰기 지연 | 없음 (즉시 커밋/롤백) | 체인 실행 완료 시점에 트랜잭션 처리 |
핵심 개념 차이
- JDBC: 요청 → 스레드 확보 → DB 커넥션 점유 → 응답 받을 때까지 대기
- R2DBC: 요청 → 리액터 체인 등록 → 응답 올 때까지 스레드는 놀고 있음
→ 이 차이 때문에 WebFlux 같은 논블로킹 시스템에서는 JDBC를 쓰면 성능이 심각하게 저하됨
왜 @Transactional이 WebFlux에서 동작하지 않는가?
AOP | @Transactional은 실제로는 프록시를 만들어 스레드 단위로 트랜잭션을 걸어줌 |
WebFlux | 체인은 비동기 이벤트로 이루어져 있어 스레드가 고정되지 않음 |
결과 | 트랜잭션 시작/종료가 제대로 감지되지 않아서 → rollback/commit 안 됨 |
좀 더 간단하게 설명하면
@Transactional 은 스레드 단위로 트랜잭션을 관리함
JDBC 의 경우에는 요청의 시작부터 끝까지 스레드를 확보해 두기때문에 스레드 단위로 관리 가능
WebFlux 의 경우에는 스레드를 확보해두지 않고 요청의 단계별로 스레드가 바뀔 수 있어서 트랜잭션 시작/ 종료가 제대로 감지되지 않음
반응형