스프링 부트는 데이터 관리를 단순히 수행하는 수준을 넘어서 즐겁게 처리할 수 있다!
리액티브 데이터 스토어의 요건
웹 컨트롤러,서비스 계층이 리액티브 방식으로 동작하는데 블로킹 방식으로 연결되는 데이터베이스를 연결하면 리액티브는 무너진다. 블로킹 방식으로 데이터베이스를 호출한 스레드는 응답을 받을 때까지 다른 작업을 못하기 때문
JPA 와 JDBC는 블로킹 API다. 트랜잭션을 시작하는 메시지를 전송하고, 쿼리를 포함하는 메시지를 전송하고, 결과가 나올 때 클라이언트에게 스트리밍해주는 개념 자체가 없다.모든 데이터베이스 호출은 응답을 받을 때까지 블로킹되어 기다려야 한다.
최신형 리액티브 패러다임을 지원하는 데이터베이스
리액티브 데이터베이스를 위한 의존관계 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
</dependency>
spring.mongodb.embedded.version=3.3.4
리포지토리 만들기
서로 다른 NoSQL 엔진을 하나의 API로 통일해서 표준화하려면 매우 복잡함.
하지만 그게 전부는 아니다. 몽고디비를 비롯해 다양한 데이터베이스를 지원하는 스프링 데이터 모듈에는 리포지토리 계층이 있다.
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
public interface ItemRepository extends ReactiveCrudRepository<Item, String>{
}
테스트 데이터 로딩
MongoTemplate을 사용하여 데이터 로딩. 스프링 부트와 스프링 데이터 몽고디비 자동설정 기능 덕분에 MongoTemplate(블로킹)과 ReactiveMongoTemplate(논블로킹,비동기) 모두 사용 가능
@Component
public class TemplateDatabaseLoader {
@Bean
CommandLineRunner initialize(MongoOperations mongo){
return args -> {
mongo.save(new Item("Alf alarm clock","alarm clock",19.99));
mongo.save(new Item("Smurf TV tray","TV tray",24.99));
};
}
}
MongoOperations?
리포지토리 저장 서비스 작성예시
public Mono<Cart> addToCart(String cartId, String id) {
return this.cartRepository.findById(cartId)
.defaultIfEmpty(new Cart(cartId))
.flatMap(cart -> cart.getCartItems().stream()
.filter(cartItem -> cartItem.getItem()
.getId().equals(id))
.findAny()
.map(cartItem -> {
cartItem.increment();
return Mono.just(cart);
})
.orElseGet(() -> {
return this.itemRepository.findById(id)
.map(CartItem::new)
.map(cartItem -> {
cart.getCartItems().add(cartItem);
return cart;
});
}))
.flatMap(this.cartRepository::save);
}
데이터베이스 쿼리
쿼리 방법 종류
public interface ItemRepository extends ReactiveCrudRepository<Item, String>, ReactiveQueryByExampleExecutor<Item> {
}
// Example 쿼리 예제
public Flux<Item> searchByExample(String name, String description, boolean useAnd){
Item item = new Item(name,description,0.0);
ExampleMatcher matcher = (useAnd
? ExampleMatcher.matchingAll() //
: ExampleMatcher.matchingAny()) //
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)
.withIgnoreCase()
.withIgnorePaths("price");
Example<Item> probe = Example.of(item,matcher);
return repository.findAll(probe);
}