본문 바로가기
Develop/Spring˙Spring Boot

[Spring Reactive] 리액티브 데이터 Repository 정의하기

by 독서왕뼝아리 2023. 3. 12.

리포지토리를 생성하기 앞서 도메인은 다음과 같다.

public class Item {

    private @Id String id;
    private String name;
    private double price;

    private Item(){}

    public Item(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getId() {
        return id;
    }
}

 
 
블로킹API인 JPA를 사용하지 않지만 JPA와 매우 유사한 인터페이스가 정의되어 있다. 
ReactiveCrudRepository 인터페이스를 상속하면 된다. 주석 내용은 기본적으로 제공하는 메서드이다. JPA와 거의 동일하게 제공한다는 것을 알 수 있다.

public interface ItemRepository extends ReactiveCrudRepository<Item, String> {
    /**
     * save(), saveAll()
     * findById(), findAll(), findAllById()
     * existsById(), count()
     * deleteById(), delete(), deleteAll()
     */
}

 
 
추가적인 쿼리를 정의해야 한다면 JPA와 동일하게 키워드를 사용해 기능을 구현한다. 몽고디비나 JPA 또는 그 어떤 쿼리문도 직접 작성할 필요가 없다. 메서드 이름 규칙만 잘 활용한다면 스프링 데이터가 자동으로 만들어준다.

public interface ItemRepository extends ReactiveCrudRepository<Item, String> {
    Flux<Item> findByNameContaining(String partialName);
}

 

 


쿼리문 자동 생성 메서드로 충분하지 않을 때

 

  • @Query 애너테이션 사용하여 직접 쿼리문 작성하기
    (몽고디비 쿼리는 처음 써본다...)
@Query("{'name':  ?0, 'age': ?1}")
Flux<Item> findItemForCustomerMonthlyReport(String name, int age);

@Query(sort="{'age': -1}")
Flux<Item> findSortedStuffForWeeklyR

 

  • Example 쿼리 사용하기

여러 조건을 조립해서 스프링 데이터에 전달하면, 스프링 데이터는 필요한 쿼리문을 만들어준다. 조건이 추가될 때마다 계속 복잡해지는 코드를 유지 관리할 필요가 없다
(findByNameContainingOrDescriptionContainingAllIgnoreCase(String partialName, String partialDesc) 과 같은...)

public interface ItemByExampleRepository extends ReactiveQueryByExampleExecutor<Item> {
}

Example 쿼리를 사용하려면 ReactiveQueryByExampleExecutor<T>를 상속받아야 한다.
 

Flux<Item> searchByExample(String name, String description, boolean useAnd){
    Item item = new Item(description, 0.0);

    ExampleMatcher matcher = (useAnd
            ? ExampleMatcher.matchingAll()
            : ExampleMatcher.matchingAny())
                .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) // 1
                .withIgnoreCase() // 2
                .withIgnorePaths("price"); // 3

    Example<Item> probe = Example.of(item, matcher); // 4

    return exampleRepository.findAll(probe); // 5
}

복잡한 요구 조건을 Example 쿼리로 구현한 코드이다.

  1. StringMatcher.CONTAINING을 사용해서 부분 일치 검색을 수행한다. 
  2. 대소문자를 구분하지 않는다.
  3. ExampleMatcher는 기본적으로 null 필드를 무시하지만, 기본 타입인 double(price 필드) 에는 null이 올 수 없으므로 price 필드가 무시되도록 명시적으로 지정한다.
  4. Item 객체와 matcher를 사용해 Example.of(...)로 감싸서 Example을 생성한다.
  5. 쿼리를 실행한다.

가벼워 보이지만 기능은 막강하다. 주어진 요구사항을 모두 충족할 뿐만 아니라 향후 검색 조건 필드가 추가되더라도 어렵지 않게 수용할 수 있다.
 

  • 평문형 연산 (Fluent Operation)
Flux<Item> searchByFluentExample(String name, String description){
    return fluentOperations.query(Item.class)
            .matching(query(where("TV tray").is(name).and("Smurf").is(description)))
            .all();
}