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 도 있다. 이제 클라이언트에 의존성만 주입하면 바로 이 레포지토리 인스턴스를 사용할 수 있다. 따라서 아래 코드로 모든 Member 와 Order 객체를 조회할 수 있다.
그외 @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 이 아니기에 발생되는 현상이라 생각한다. (값 타입, 복잡한 연관관계 등)
사용을 해야한다면 해당 기능들을 어떻게 구현해낼 것인지에 대해서 깊은 고민을 하고 사용해야할 것 같다.
참조
※ 주니어 개발자이기에 부족한 점이 많습니다. 해당 부분에 대해서 댓글 달아주시면 감사하겠습니다.
'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 |
댓글