본문 바로가기
Spring

Spring Data R2DBC 사용

by Heesu.lee 2021. 3. 28.

Reactive Relational DataBase Connectivity (R2DBC) 는 기존 Blocking 기반의 RDB 를 Reactive 하게 사용할 수 있도록 API 를 제공한다.

 

기존 Spring WebFlux 에서 RDB 를 사용하는 경우 온전한 Reactive 한 구현을 하기 어려웠었다. (Blocking 기반 API 제공)

그렇지만, WebFlux 와 R2DBC 를 사용하는 경우 Reactive 한 느낌만 내는 것이 아니라,

애플리케이션이 온전히 Reactive 하게 동작할 수 있도록 지원한다.

R2DBC 는 완전한 Reative RDB 사용을 위해 non-blocking I/O layer 위에 Database 유선 프로토콜 계층까지 완전히 새롭게 구현한 드라이버를 제공.

R2DBC 설정

Application 정보

  • Spring Boot 2.4.3
  • Java 8
  • MySQL 5.7

 

build.gradle

dependencies {
    
    ...
    
	// r2dbc
	implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
	runtimeOnly("dev.miku:r2dbc-mysql:0.8.2.RELEASE")
    
    ...
}

 

application.yml

spring:
  r2dbc:
    url: r2dbc:mysql://localhost:3307/test_contents?useUnicode=true&characterEncoding=utf8
    username: root
    password: 
    
logging:
  level:
    org:
      springframework:
        r2dbc: DEBUG

 

Entity 작성

Member

@Table
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Member {

    @Id
    @Column("member_id")
    private Long id;

    @Column("member_name")
    private String name;

}

 

Order

@Table("orders")
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Order {

    @Id
    @Column("order_id")
    private Long id;

    @Column("member_id")
    private Long member;

    @Column("order_name")
    private String name;

    @Column("order_status")
    private OrderStatus status;
}

 

Entity 작성 시 현재까지 확인된 바로는 JPA 와 생각보다 많이 다르다.

JPA 는 ORM 이지만, R2DBC 의 경우 ORM 이 아니기 때문에 해당 부분을 유의하면서 작성 및 사용해야 한다.

  • 연관관계 미지원 (XToOne, XToMany 등 )
  • 값 타입 미지원 (Embeddable 등)

 

Repository 작성

MemberRepository

public interface MemberRepository extends ReactiveCrudRepository<Member, Long> {
	
}

 

OrderRepository

public interface OrderRepository extends ReactiveCrudRepository<Order, Long> {

}

 

위에서 만든 레포지토리가 ReactiveCrudRepository를 확장하고 있기 때문에, 리액티브 CRUD 연산을 사용해 엔티티에 접근할 수 있다. 


ReactiveCrudRepository 위에는, PagingAndSortingRepository와 유사한 정렬 기능을 추가로 지원하는 ReactiveSortingRepository 도 있다. 이제 클라이언트에 의존성만 주입하면 바로 이 레포지토리 인스턴스를 사용할 수 있다. 따라서 아래 코드로 모든 MemberOrder 객체를 조회할 수 있다.

 

그외 @Query 를 통해 Native SQL 을 작성하여 직접 메소드를 정의해줄 수 있다.

이 뿐만 아니라, JPA 와 같이 Query Method 역시 제공해주고 있다.

 

public interface OrderRepository extends ReactiveCrudRepository<Order, Long> {

    @Query("select member.*, orders.order_id, orders.order_name from orders join member on orders.member_id = member.member_id")
    Flux<Order> findAllWithMember();

    Flux<Order> findAllByMember(Long memberId);
}

 

테스트 코드 작성

Order Test

@DataR2dbcTest
class OrderRepositoryTest {

    @Autowired OrderRepository orderRepository;

    @DisplayName("주문 저장 테스트")
    @Test
    void saveOrderTest() {
        // given
        int size = 3;
        Long member = 1L;
        String name = "주문 ";

        // when
        IntStream.range(0, size)
                .forEach(id -> orderRepository.save(getOrder(member, name, id)).block());

        // then
        List<Order> orders = orderRepository.findAll()
                .collectList()
                .block();

        assertThat(orders).isNotNull();
        assertThat(orders.size()).isEqualTo(size);
    }
    
    @DisplayName("주문 조회 테스트")
    @Test
    void findOrdersTest() {
        // given
        int size = 3;
        Long memberId = 1L;

        // when
        List<Order> orders = orderRepository.findAllByMember(memberId)
                .collectList()
                .block();

        // then
        assertNotNull(orders);

        assertEquals(size, orders.size());
        assertAll(() -> {
            orders.forEach(order -> assertEquals(memberId, order.getMember()));
        });
    }
}

 

Member Test

@DataR2dbcTest
class MemberRepositoryTest {

    @Autowired MemberRepository memberRepository;

    @DisplayName("멤버 저장 테스트")
    @Test
    void saveTest() {
        // given
        String name = "HEESUTORY";

        Member member = Member.builder()
                .name(name)
                .build();

        // when
        Member saved = memberRepository.save(member)
                .block();

        // then
        assertThat(saved).isNotNull();
        assertThat(saved.getName()).isEqualTo(name);
    }

    @DisplayName("멤버 조회 테스트")
    @Test
    void findByIdTest() {
        // given
        Long id = 1L;

        // when
        Member member = memberRepository.findById(id)
                .block();

        // then
        assertThat(member).isNotNull();
        assertThat(member.getId()).isEqualTo(id);
    }
}

 

지금까지 간단하게 어떻게 R2DBC 를 사용하는지에 대해 알아보았다.

과거에 비해서 상당히 많은 기능들을 사용할 수 있도록 발전했지만, 여전히 사용하는데 있어 불편함은 존재하는 것 같다.

 

Spring Data R2DBC aims at being conceptually easy. In order to achieve this it does NOT offer caching, lazy loading, write behind or many other features of ORM frameworks. This makes Spring Data R2DBC a simple, limited, opinionated object mapper.

이는 R2DBC 가 JPA 와 같은 ORM 이 아니기에 발생되는 현상이라 생각한다. (값 타입, 복잡한 연관관계 등)

사용을 해야한다면 해당 기능들을 어떻게 구현해낼 것인지에 대해서 깊은 고민을 하고 사용해야할 것 같다.

 

참조

 

R2DBC mysql (Spring reactive-2)

R2DBC 포스팅 작성일 기준 (2020.08.17) Spring boot 최신 버전인 spring-boot-2.3.x 에서는 신기술을 탐닉하는 스프링진영의 개발자들이라면 기다려왔을 R2DBC의 GA (General Availability)버전이 Spring-data 프로젝트

taes-k.github.io

 

R2DBC Repositories

스프링 데이터 R2DBC 레포지토리 공식 레퍼런스를 한글로 번역한 문서입니다.

godekdls.github.io

 

※ 주니어 개발자이기에 부족한 점이 많습니다. 해당 부분에 대해서 댓글 달아주시면 감사하겠습니다.

'Spring' 카테고리의 다른 글

Spring Data R2DBC 연관관계 구현 - OneToMany (2)  (0) 2021.03.31
Spring Data R2DBC 연관관계 구현 - OneToMany  (3) 2021.03.28
Custom Validation 사용해보기  (0) 2021.03.20
빈 스코프  (0) 2020.12.26
빈 생명주기 콜백  (0) 2020.12.23

댓글