[Spring] 스프링부트를 활용한 데이터 액세스

Beanzinu·2022년 5월 16일
0

스프링부트

목록 보기
3/7

스프링 부트는 데이터 관리를 단순히 수행하는 수준을 넘어서 즐겁게 처리할 수 있다!

리액티브 데이터 스토어의 요건

  1. 리액티브 프로그래밍을 사용하려면 모든 과정이 리액티브여야 한다.
  • 웹 컨트롤러,서비스 계층이 리액티브 방식으로 동작하는데 블로킹 방식으로 연결되는 데이터베이스를 연결하면 리액티브는 무너진다. 블로킹 방식으로 데이터베이스를 호출한 스레드는 응답을 받을 때까지 다른 작업을 못하기 때문

  • JPA 와 JDBC는 블로킹 API다. 트랜잭션을 시작하는 메시지를 전송하고, 쿼리를 포함하는 메시지를 전송하고, 결과가 나올 때 클라이언트에게 스트리밍해주는 개념 자체가 없다.모든 데이터베이스 호출은 응답을 받을 때까지 블로킹되어 기다려야 한다.

  • 최신형 리액티브 패러다임을 지원하는 데이터베이스

    • 몽고디비(MongoDB)
    • 레디스(Redis)
    • 아파치 카산드라
    • 엘라스틱서치
    • 네오포제이
    • 카우치베이스

리액티브 데이터베이스를 위한 의존관계 추가

  1. pom.xml에 몽고디비 의존관계 추가
<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>
  1. application.properties에 embedded mongodb 버전 추가
spring.mongodb.embedded.version=3.3.4

리포지토리 만들기

서로 다른 NoSQL 엔진을 하나의 API로 통일해서 표준화하려면 매우 복잡함.

  • 스프링 데이터는 어떻게 해결하는 것일까?
  • 템플릿 패턴: 타입 안전 방식으로 연산을 처리하고 다루기 복잡하고 귀찮은 것들을 추상화해서 데이터베이스 등 협력 대상과의 상호작용이 바르게 동작하도록 권장
    -몽고디비용으로는 MongoTemplate과 ReactiveMongoTemplate이 제공된다.
  • 몽고QL 쿼리문을 타입 안전 방식으로 작성하는 것

하지만 그게 전부는 아니다. 몽고디비를 비롯해 다양한 데이터베이스를 지원하는 스프링 데이터 모듈에는 리포지토리 계층이 있다.

  • 저장,조회,삭제 같은 단순하고 공통적인 연산은 추상화를 담당하는 계층
import org.springframework.data.repository.reactive.ReactiveCrudRepository;

public interface ItemRepository extends ReactiveCrudRepository<Item, String>{

}
  • 첫 번째 제네릭 파라미터인 Item은 리포지토리가 저장하고 조회하는 타입을 의미
  • 두 번째 제네릭 파라미터인 String은 저장되는 데이터의 식별자의 타입이 String이라는 의미다.
  • ItemRepository는 자체로는 인터페이스라 아무런 구현 코드도 포함되어 있지 않다. 부모 인터페이스로부터 여러 가지 풍부한 CRUD 연산을 상속 받아 사용한다.

테스트 데이터 로딩

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?

  • 스프링 팀이 JdbcTemplate에서 일부를 추출해서 JdbcOperations라는 인터페이스를 만들었다. 인터페이스를 사용하면 계약과 세부 구현 내용을 분리할 수 있다. 따라서 애플리케이션과 몽고디비의 결합도를 낮추려면 MongoOpeartions를 사용하는 것이 좋다.

리포지토리 저장 서비스 작성예시

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);
    }
  • @Service 애너테이션은 이 컴포넌트가 자체 상태를 가지고 있지 않은 서비스임을 의미한다. 스프링 부트의 클래스 패스 탐색에 의해 자동으로 감지되어 빈으로 등록되고 완전한 컴포넌트로 동작한다.
  • 컨트롤러와 마찬가지로 생성자를 통해 필요한 레포지토리를 주입받는다.
  • map 과 flatmap의 활용 참조: https://madplay.github.io/post/difference-between-map-and-flatmap-methods-in-java

데이터베이스 쿼리

쿼리 방법 종류

  1. 표준 CRUD 메소드 ( findAll , findById )
  2. 메소드 이름 기반 쿼리
  3. Example 쿼리
  4. @Query 애너테이션 사용 쿼리
  5. 평문형 API
  • 이 중 쿼리를 자동 생성해주고 모든 쿼리 조건을 미리 알 수 없을 때 사용 가능한 Example 쿼리에 대해 알아보겠다.
    -> 주어진 요구사항을 모두 충족할 뿐만 아니라 검색 조건 필드가 추가되더라도 어렵지 않게 수용할 수 있기 때문에
  1. 레포지토리 인터페이스에 상속 추가 ( ReactiveQueryByExampleExecutor<>)
public interface ItemRepository extends ReactiveCrudRepository<Item, String>, ReactiveQueryByExampleExecutor<Item> {

}
  1. ItemService에 Example Query 작성예시
// 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);
    }
  • Item 객체 생성
  • And 쿼리를 사용할 건 지에 따라 3항 연산자로 분기해서 ExampleMatcher를 생성한다.
  • StringMatcher.CONTAINING을 사용하여 부분 일치 검색을 수행. ( CONTAINING 외에도 다양한 옵션이 있다.
  • .withIgnoreCase()를 통해 대소문자 구분 X
  • price 필드는 무시되도록 명시적으로 지정
  • Item 객체와 matcher를 함께 Example.of(...)로 감싸서 Example을 생성 후 쿼리를 실행!
profile
당신을 한 줄로 소개해보세요.

0개의 댓글