Repository Methods Returning Collections or Iterables

2024. 10. 19. 20:45Spring Boot/Spring Data JPA

Spring Data JPA는 여러 결과를 리턴하는 쿼리 메서드에서 다양한 Java 컬렉션 타입을 사용할 수 있습니다. 여기서는 Iterable, List, Set와 같은 표준 Java 컬렉션 타입 외에도, Spring Data의 Streamable이라는 커스텀 Iterable 확장 타입과 Vavr 라이브러리에서 제공하는 컬렉션 타입을 지원합니다. 이 섹션에서는 이러한 리턴 타입에 대해 자세히 설명하겠습니다.

Streamable을 쿼리 메서드 리턴 타입으로 사용하기

StreamableIterable 또는 기타 컬렉션 타입의 대안으로 사용할 수 있습니다. Streamable은 non-parallel Stream에 접근할 수 있는 편의 메서드를 제공하고, 직접 .filter(…), .map(…)와 같은 연산을 수행할 수 있습니다. 또한 다른 Streamable 객체와 연결(concatenate)할 수 있는 기능도 제공합니다.

예시: Streamable로 쿼리 메서드 결과 결합하기
interface PersonRepository extends Repository<Person, Long> {
  Streamable<Person> findByFirstnameContaining(String firstname);
  Streamable<Person> findByLastnameContaining(String lastname);
}

// 사용 예
Streamable<Person> result = repository.findByFirstnameContaining("av")
  .and(repository.findByLastnameContaining("ea"));

위 예시에서, findByFirstnameContaining 메서드는 주어진 이름이 포함된 사람들을 찾고, findByLastnameContaining 메서드는 주어진 성이 포함된 사람들을 찾습니다. 두 Streamable 결과는 and 메서드를 통해 결합됩니다.

사용자 정의 Streamable 래퍼 리턴

컬렉션을 위한 전용 래퍼 타입을 제공하는 것은 여러 요소를 리턴하는 쿼리 결과를 API로 제공하는 일반적인 패턴입니다. 이러한 타입은 컬렉션과 유사한 타입을 리턴하는 리포지토리 메서드를 호출하고, 수동으로 래퍼 타입의 인스턴스를 생성하여 사용됩니다. Spring Data는 이러한 래퍼 타입을 쿼리 메서드 리턴 타입으로 사용할 수 있게 해주는데, 다음 조건을 만족해야 합니다:

  1. 타입이 Streamable을 구현해야 합니다.
  2. 타입이 Streamable을 아규먼트로 받는 생성자 또는 of(…) 또는 valueOf(…)라는 이름의 정적 팩토리 메서드를 노출해야 합니다.
예시
class Product {                   // (1)
  MonetaryAmount getPrice() { … }
}

@RequiredArgsConstructor(staticName = "of")
class Products implements Streamable<Product> {  // (2)     

  private final Streamable<Product> streamable;

  public MonetaryAmount getTotal() {  // (3)         
    return streamable.stream()
      .map(Product::getPrice)
      .reduce(Money.of(0), MonetaryAmount::add);
  }

  @Override
  public Iterator<Product> iterator() {  // (4)               
    return streamable.iterator();
  }
}

interface ProductRepository extends Repository<Product, Long> {
  Products findAllByDescriptionContaining(String text);  // (5)
}
  1. Product 엔티티는 제품 가격에 접근하는 API를 노출합니다.
  2. ProductsStreamable<Product>에 대한 래퍼 타입으로, Products.of(…) 메서드를 사용하여 생성할 수 있습니다. 표준 생성자도 가능합니다.
  3. 이 래퍼 타입은 Streamable<Product>에서 새로운 값을 계산하는 추가 API를 제공합니다.
  4. Streamable 인터페이스를 구현하고 실제 결과에 위임(delegate)합니다.
  5. Products 래퍼 타입을 쿼리 메서드 리턴 타입으로 직접 사용할 수 있습니다. 즉, Streamable<Product>를 리턴하고 결과를 수동으로 래핑할 필요가 없습니다.
  6. @RequiredArgsConstructor

Vavr 컬렉션 지원

Vavr는 Java에서 함수형 프로그래밍 개념을 수용하는 라이브러리로, 여러 커스텀 컬렉션 타입을 제공합니다. Vavr의 컬렉션 타입을 쿼리 메서드 리턴 타입으로 사용할 수 있으며, 다음 표와 같이 매핑됩니다:

Vavr 컬렉션 타입 사용된 Vavr 구현 타입 유효한 Java 소스 타입
io.vavr.collection.Seq java.util.Iterable  
io.vavr.collection.List java.util.Iterable  
io.vavr.collection.Set java.util.Iterable  
io.vavr.collection.LinkedHashSet java.util.Iterable  
io.vavr.collection.Map java.util.Map  
io.vavr.collection.LinkedHashMap java.util.Map  

 

위 표의 첫 번째 열에 있는 타입(또는 그 하위 타입)을 쿼리 메서드 리턴 타입으로 사용하면, 두 번째 열의 타입이 구현 타입으로 사용됩니다. 쿼리 결과의 실제 Java 타입(세 번째 열)에 따라 달라집니다. 또는 Traversable(Vavr의 Iterable 동등 타입)을 선언할 수 있으며, 이 경우 실제 리턴값에 따라 구현 클래스를 파생합니다. 즉, java.util.List는 Vavr의 List 또는 Seq로 변환되고, java.util.Set은 Vavr의 LinkedHashSet으로 변환됩니다.

이와 같이 Spring Data는 다양한 컬렉션 타입을 지원하여 더 유연하고 기능적인 쿼리 메서드를 제공합니다.

'Spring Boot > Spring Data JPA' 카테고리의 다른 글

Paging, Iterating Large Results, Sorting & Limiting  (0) 2024.10.20
Streaming Query Results, Asynchronous Query Results  (0) 2024.10.20
Property Expressions  (0) 2024.10.19
Reserved Method Names  (1) 2024.10.19
Query Creation  (0) 2024.10.19