14.3 R2DBC를 이용한 리액티브 데이터베이스 CRUD
WebFlux 기반의 완전한 논블로킹(Non-blocking) 파이프라인을 구성하려면 데이터베이스 계층도 논블로킹이어야 합니다. **R2DBC(Reactive Relational Database Connectivity)**는 기존 JDBC의 블로킹 특성을 제거한 리액티브 데이터베이스 드라이버 표준입니다.
1. 의존성 및 설정
implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
runtimeOnly 'io.asyncer:r2dbc-mysql:1.2.0' // MySQL R2DBC 드라이버 예시
spring:
r2dbc:
url: r2dbc:mysql://localhost:3306/mydb
username: root
password: secret
경고
R2DBC는 JPA(Hibernate)와 함께 쓸 수 없습니다. 리액티브 스택(WebFlux + R2DBC)과 서블릿 스택(MVC + JPA)은 서로 다른 아키텍처입니다. 하나의 서비스에서 혼용하지 마세요.
2. ReactiveCrudRepository 상속 및 사용
// 1. 엔티티 정의 (@Table로 테이블 매핑, @Id로 기본키)
@Table("users")
@Data
@NoArgsConstructor
public class User {
@Id
private Long id;
private String name;
private String email;
}
// 2. 리액티브 리포지토리 (반환형이 Mono/Flux인 것에 주목)
@Repository
public interface UserR2dbcRepository extends ReactiveCrudRepository<User, Long> {
Flux<User> findByName(String name);
Mono<User> findByEmail(String email);
}
// 3. 서비스 레이어 - 논블로킹 DB 연산
@Service
@RequiredArgsConstructor
public class UserReactiveService {
private final UserR2dbcRepository userRepository;
public Flux<User> getAllUsers() {
return userRepository.findAll();
}
public Mono<User> createUser(User user) {
return userRepository.save(user);
}
public Mono<User> getUserById(Long id) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다.")));
}
public Mono<Void> deleteUser(Long id) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
.flatMap(userRepository::delete);
}
}
// 4. WebFlux 컨트롤러 (논블로킹 응답 파이프라인 완성)
@RestController
@RequestMapping("/api/v2/users")
@RequiredArgsConstructor
public class UserReactiveController {
private final UserReactiveService userService;
@GetMapping
public Flux<User> getAll() { return userService.getAllUsers(); }
@GetMapping("/{id}")
public Mono<User> getById(@PathVariable Long id) { return userService.getUserById(id); }
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<User> create(@RequestBody User user) { return userService.createUser(user); }
}
위 코드에서 Controller → Service → Repository 모두 Mono/Flux를 리턴하기 때문에, 요청부터 DB 응답까지 단 하나의 스레드도 블로킹되지 않는 완전한 논블로킹 체인이 완성됩니다.