본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
강의 요약
오늘 강의에서는 R2DBC를 활용한 리액티브 데이터베이스 접근 방식을 실습했다. JDBC의 블로킹 I/O 대신 Flux/Mono와 ConnectionFactory, DatabaseClient를 사용해 논블로킹 방식으로 CRUD를 구현하는 방법을 다루었다. 강의에서는 R2DBC의 간단한 사용법을 소개해줬으니, NoSQL 환경에서의 활용을 위해 Spring Data MongoDB Reactive를 추가로 정리해보자.
Spring Data MongoDB Reactive
MongoDB는 문서지향 모델로 스키마 변경을 유연하게 흡수할 수 있고, 리액티브 드라이버를 통해 높은 동시성 요청을 효율적으로 처리할 수 있다. 반면 강한 트랜잭션이나 복잡한 조인이 필요한 결제, 정산 도메인은 RDB 병행이 현실적이며, 대규모 집계는 별도 ETL이나 OLAP 분리가 적합하다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
- Spring Boot에 다음 의존성을 추가한다.
도메인 모델 정의
@Document(collection = "orders")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
@Id
private String id;
private String userId;
private BigDecimal amount;
private Instant createdAt;
}
- @Document 어노테이션으로 MongoDB 컬렉션과 매핑되는 도메인 클래스를 정의한다.
- @Id는 MongoDB의 _id 필드와 자동 매핑된다. String 타입으로 선언하면 ObjectId가 자동 생성되고, 직접 값을 지정할 수도 있다.
리액티브 리포지토리 구성
public interface OrderRepository extends ReactiveCrudRepository<Order, String> {
Flux<Order> findByUserId(String userId);
@Query("{ 'amount': { $gte: ?0 } }")
Flux<Order> findExpensive(BigDecimal minAmount);
}
- ReactiveCrudRepository를 확장해 기본 CRUD와 커스텀 쿼리를 정의한다.
- ReactiveCrudRepository는 save, findById, findAll, deleteById 등의 메서드를 Mono와 Flux로 제공한다.
- 메서드 이름 규칙으로 자동 쿼리를 생성하거나, @Query로 MongoDB 쿼리를 직접 작성할 수 있다.
리액티브 리포지토리 테스트
@DataMongoTest
public class OrderRepositoryTest {
@Autowired
private OrderRepository orderRepository;
@BeforeEach
public void setup() {
Flux<Order> deleteAndInsert = orderRepository.deleteAll()
.thenMany(orderRepository.saveAll(
Flux.just(
new Order(null, "user1", new BigDecimal("100.00"), Instant.now()),
new Order(null, "user1", new BigDecimal("200.00"), Instant.now()),
new Order(null, "user2", new BigDecimal("50.00"), Instant.now())
)));
StepVerifier.create(deleteAndInsert)
.expectNextCount(3)
.verifyComplete();
}
@Test
public void shouldSaveAndFetchOrders() {
StepVerifier.create(orderRepository.findAll())
.recordWith(ArrayList::new)
.thenConsumeWhile(x -> true)
.consumeRecordedWith(orders -> {
assertThat(orders).hasSize(3);
assertThat(orders)
.extracting(Order::getUserId)
.contains("user1", "user2");
})
.verifyComplete();
StepVerifier.create(orderRepository.findByUserId("user1"))
.expectNextCount(2)
.verifyComplete();
}
}
- @DataMongoTest는 임베디드 MongoDB를 포함한 테스트 슬라이스를 구성해 빠른 통합 테스트를 가능하게 한다.
- StepVerifier는 리액티브 스트림의 흐름을 단계별로 검증한다. expectNextCount로 예상되는 요소 수를 확인하고, recordWith와 consumeRecordedWith로 모든 요소를 수집해 검증할 수 있다.
- thenConsumeWhile은 조건이 true인 동안 요소를 소비하며, 마지막에 verifyComplete로 스트림이 정상 완료되었는지 확인한다.
- @BeforeEach에서 deleteAll().thenMany()로 기존 데이터를 제거한 뒤 테스트 데이터를 삽입하는 패턴은 테스트 간 격리를 보장한다.
참고 출처
- https://www.baeldung.com/spring-data-mongodb-reactive
- https://medium.com/@bubu.tripathy/reactive-data-persistence-with-spring-data-mongodb-adcf1dcf12d5



