| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- 단위테스트
- 스프링부트
- Java
- string
- 배열
- list
- 자바
- Array
- java테스트
- js
- HashMap
- 자바문법
- vscode
- junit5
- input
- html
- CSS
- ArrayList
- javascript
- math
- 인텔리제이
- 정규식
- IntelliJ
- Visual Studio Code
- SpringBoot
- Eclipse
- 테스트자동화
- 문자열
- junit
- 자바스크립트
- Today
- Total
어제 오늘 내일
[R2DBC] "트랜잭션은 어떻게 걸죠?" Reactive Transactional과 R2dbcEntityTemplate 본문
[R2DBC] "트랜잭션은 어떻게 걸죠?" Reactive Transactional과 R2dbcEntityTemplate
hi.anna 2026. 4. 3. 08:23
WebFlux에서 R2DBC를 쓸 때 가장 불안한 점은 트랜잭션입니다.
"A 계좌에서 돈을 빼고(update), B 계좌에 돈을 넣어야(update) 하는데, 중간에 에러 나면 롤백이 될까?"
결론부터 말씀드리면, @Transactional 어노테이션 하나면 완벽하게 동작합니다.
하지만 그 원리는 MVC와 완전히 다릅니다.
1. 트랜잭션 매니저 설정 ()
스프링 부트가 ConnectionFactory를 보고 알아서 빈을 등록해 주지만, 명시적으로 알고 있어야 합니다.
JDBC의 PlatformTransactionManager가 아니라, R2dbcTransactionManager가 필요합니다.
@Configuration
@EnableTransactionManagement // 트랜잭션 관리 활성화
public class R2dbcConfig {
@Bean
public ReactiveTransactionManager transactionManager(ConnectionFactory connectionFactory) {
return new R2dbcTransactionManager(connectionFactory);
}
}
2. 선언적 트랜잭션 ()
사용법은 MVC와 100% 똑같습니다.
@Service
@RequiredArgsConstructor
public class MoneyTransferService {
private final AccountRepository accountRepository;
@Transactional // ★ 예외 발생 시 알아서 Rollback 됨!
public Mono<Void> transfer(Long fromId, Long toId, Long amount) {
return accountRepository.findById(fromId)
.flatMap(from -> {
from.withdraw(amount);
return accountRepository.save(from);
})
.flatMap(savedFrom -> accountRepository.findById(toId))
.flatMap(to -> {
to.deposit(amount);
return accountRepository.save(to);
})
.then(); // Mono<Void> 리턴
}
}
동작 원리 (Reactor Context):
트랜잭션 정보가 쓰레드가 아니라, 데이터 흐름(Pipeline)의 컨텍스트(Context)를 타고 흘러갑니다. 그래서 중간에 쓰레드가 바뀌어도 트랜잭션은 유지됩니다.
3. JPA가 그리울 땐: (DatabaseClient)
ReactiveCrudRepository는 너무 단순합니다. JOIN도 어렵고 동적 쿼리도 안 되죠.
이때 JPA의 EntityManager나 MyBatis처럼 쓸 수 있는 도구가 바로 R2dbcEntityTemplate입니다.
(스프링 부트 최신 버전에서는 DatabaseClient를 더 권장합니다.)
① 복잡한 조건 검색 (Fluent API)
@Repository
@RequiredArgsConstructor
public class UserCustomRepository {
private final R2dbcEntityTemplate template;
public Flux<User> findUsersByAgeAndName(int age, String name) {
// SQL 몰라도 메서드 체이닝으로 쿼리 생성!
return template.select(User.class)
.from("users")
.matching(Query.query(Criteria.where("age").is(age)
.and("name").is(name)))
.all();
}
}
② 직접 SQL 작성 (Native Query)
복잡한 조인이나 통계 쿼리는 그냥 SQL로 짜는 게 편합니다.
@Repository
@RequiredArgsConstructor
public class NativeQueryRepository {
private final DatabaseClient databaseClient; // 더 로우 레벨 API
public Flux<UserStatDto> getUserStats() {
return databaseClient.sql("SELECT count(*) as count, role FROM users GROUP BY role")
.map((row, metadata) -> new UserStatDto(
row.get("role", String.class),
row.get("count", Long.class)
))
.all();
}
}
4. 프로그래밍 방식 트랜잭션 ()
@Transactional을 못 붙이는 상황이거나, 로직 중간에 세밀하게 트랜잭션을 제어하고 싶다면 TransactionalOperator를 씁니다.
@Service
@RequiredArgsConstructor
public class ManualTransactionService {
private final TransactionalOperator rxtx; // 스프링이 주입해줌
private final UserRepository userRepository;
public Mono<User> saveWithRollback(User user) {
return userRepository.save(user)
.flatMap(saved -> {
if (saved.getName().equals("BAD_USER")) {
return Mono.error(new RuntimeException("롤백시켜!"));
}
return Mono.just(saved);
})
// ★ 이 체인 전체를 트랜잭션으로 감싸라!
.as(rxtx::transactional);
}
}
.as(rxtx::transactional) 한 줄이면 끝입니다. 정말 우아하지 않나요?
마치며
오늘의 결론입니다.
- WebFlux에서도
@Transactional은 완벽하게 동작한다. (쓰레드 로컬 대신 Reactor Context 사용) - 복잡한 쿼리는
R2dbcEntityTemplate이나DatabaseClient를 쓰면 된다. - 세밀한 제어는
TransactionalOperator를 활용하자.
이로써 WebFlux로 API부터 DB 트랜잭션까지, 완전한 Non-Blocking 시스템을 구축할 수 있게 되었습니다.
그런데, 비동기 코드는 테스트하기가 정말 까다롭습니다.
"데이터가 올 때까지 기다렸다가 검증해야 하나? Thread.sleep을 써야 하나?"
다음 포스팅에서는 "비동기 코드는 어떻게 테스트해요?" block() 없이 리액티브 스트림을 완벽하게 검증하는 StepVerifier에 대해 알아보겠습니다.
'IT > SpringBoot' 카테고리의 다른 글
| [Troubleshooting] WebFlux 도입 전 꼭 알아야 할 주의사항 (BlockHound 사용법) (0) | 2026.04.04 |
|---|---|
| [Test] "비동기 코드는 어떻게 테스트해요?" StepVerifier로 스트림 검증하기 (0) | 2026.04.04 |
| [R2DBC] "WebFlux에서 JPA 쓰면 망합니다!" R2DBC와 Reactive DB 연결 (0) | 2026.04.03 |
| [WebClient] "RestTemplate은 이제 Deprecated!" 비동기 HTTP 요청의 정석, WebClient 사용법 (0) | 2026.04.02 |
| [WebFlux] "컨트롤러가 없다고?" 함수형 엔드포인트(Router & Handler) 작성법 (0) | 2026.04.02 |
