[Spring WebFlux] 18. Spring Data R2DBC

y001·2025년 5월 7일

Reactive Programming

목록 보기
26/30
post-thumbnail

✅ 1. R2DBC란?

R2DBC는 "Reactive Relational Database Connectivity"의 약자로, 리액티브 프로그래밍 방식으로 관계형 데이터베이스에 접근할 수 있도록 설계된 비동기 논블로킹 API 표준 사양이다.

  • 기존 JDBC는 스레드당 하나의 커넥션을 사용하고, 쿼리를 실행하는 동안 스레드가 블로킹됨
  • 이러한 특성은 리액티브 시스템과 어울리지 않으며, 많은 동시 요청을 처리하는 데 병목이 발생함
  • 반면 R2DBC는 Reactive Streams 사양을 따르며, I/O 작업이 완료될 때까지 스레드를 블로킹하지 않고, 리액티브 시퀀스를 반환(Mono/Flux)

등장 배경

초기에는 MongoDB, Cassandra 같은 NoSQL DB만 리액티브 드라이버를 제공했기 때문에, 리액티브 애플리케이션과 관계형 DB를 함께 사용하기 어려웠다. 이를 해결하기 위해 R2DBC 사양이 등장했다.


✅ 2. Spring Data R2DBC란?

Spring Data R2DBC는 Spring Data 프로젝트의 일부로, R2DBC를 기반으로 한 리액티브 리포지토리(Repository) 인터페이스를 통해 CRUD 연산을 간편하게 구현할 수 있도록 도와주는 프레임워크이다.

  • ReactiveCrudRepository, Query by method, @Query 등 Spring Data 공통 기능을 지원
  • JPA처럼 Entity 클래스 기반의 데이터 접근이 가능하지만, 완전히 논블로킹 방식
  • @EnableR2dbcRepositories, @EnableR2dbcAuditing 등의 설정이 필요

✅ 3. 설정 및 구성

build.gradle

  • spring-boot-starter-data-r2dbc: R2DBC 전용 Spring Data 기능
  • io.r2dbc:r2dbc-h2: H2 DB의 리액티브 드라이버

application.yaml

  • schema-locations: 초기 테이블 스키마를 지정하는 위치
  • 주의: R2DBC는 JPA의 ddl-auto 기능을 지원하지 않음 → 직접 스크립트로 생성 필요

✅ 4. 엔티티 클래스 정의

@Table("book")
public class Book {
  @Id
  private Long bookId;
  private String titleKorean;
  private String titleEnglish;
  private String author;
  private String isbn;
  @CreatedDate
  private LocalDateTime createdAt;
  @LastModifiedDate
  private LocalDateTime modifiedAt;
}
  • @Table: R2DBC에서는 명시적으로 테이블을 지정해야 함
  • @Id: 기본 키 식별자
  • @CreatedDate, @LastModifiedDate: Auditing 설정을 통해 자동 처리됨 (@EnableR2dbcAuditing 필요)

✅ 5. Repository를 이용한 데이터 접근

Repository

public interface BookRepository extends ReactiveCrudRepository<Book, Long> {
  Mono<Book> findByIsbn(String isbn);
}
  • Mono<T>: 0 또는 1개의 데이터를 비동기로 반환
  • Flux<T>: 여러 개의 데이터를 비동기로 스트리밍

Service 예시

public Mono<Book> save(Book book) {
  return bookRepository.findByIsbn(book.getIsbn())
    .flatMap(existing -> Mono.empty()) // 이미 존재하면 저장 안 함
    .switchIfEmpty(bookRepository.save(book)); // 존재하지 않으면 저장
}
  • switchIfEmpty: 빈 결과일 경우 대체 연산 수행
  • then(...): 이전 처리 결과는 무시하고 다음 Mono를 이어붙임

✅ 6. R2dbcEntityTemplate을 이용한 데이터 접근

R2dbcEntityTemplate은 SQL 스타일의 명시적 쿼리 작성보다 좀 더 프로그래밍적 접근을 원하는 개발자에게 적합하다.

template.select(Book.class)
  .matching(query(where("author").is("홍길동")))
  .all();
  • select, insert, update, delete 등의 메서드 체이닝 방식 지원
  • Criteria.where().and().is() 등으로 조건 조합

주요 메서드

Terminating Methods (종료 연산자)

메서드설명
insert객체 삽입
update객체 수정
delete삭제
select, all, one조회
count총 개수 반환
exists존재 여부 확인

Criteria 메서드

메서드설명
is(), isNull()값 비교
greaterThan(), lessThan()비교 연산자
in(), notIn()포함 여부
like()패턴 매칭
and(), or()논리 조합

✅ 7. 페이지네이션 처리

Repository 방식

bookRepository.findAllBy(PageRequest.of(page, size, Sort.by("bookId")));
  • Pageable을 인자로 받아 자동 처리
  • 단, 쿼리 성능 개선이 필요하면 DB 인덱스 또는 limit-offset 튜닝 필요

R2dbcEntityTemplate 방식

template.select(Book.class)
  .count()
  .flatMap(total -> {
    long totalPages = (long) Math.ceil((double) total / size);
    long pageToUse = Math.min(page, totalPages);
    long skip = (pageToUse - 1) * size;

    return template.select(Book.class)
      .all()
      .skip(skip)
      .take(size)
      .collectList();
  });
  • skip: 얼마나 건너뛸지
  • take: 몇 개를 가져올지
  • count: 전체 개수 파악
  • collectList: 최종 List 형태로 반환

0개의 댓글