Spring

스프링 데이터 저장소: 목록 대 스트림

기록만이살길 2022. 11. 29. 23:45
반응형

스프링 데이터 저장소: List 대 스트림

1. 질문(문제점):

list메서드를 정의 하고 streamSpring 데이터 저장소에 있을 때 권장 사항은 무엇입니까 ?

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-streaming

예시:

interface UserRepository extends Repository<User, Long> {

  List<User> findAllByLastName(String lastName);

  Stream<User> streamAllByFirstName(String firstName);                    
         
  // Other methods defined.
}

여기서는 Page , Slice 에 대해 묻지 않습니다 . 그것들은 나에게 분명하며 문서 에서 설명을 찾았습니다 .


내 가정(내가 틀렸나요?):

  1. Stream은 모든 레코드를 Java 힙에 로드하지 않습니다. 대신 k레코드를 힙에 로드하고 하나씩 처리합니다. 그런 다음 다른 k레코드를 로드하는 식입니다.

  2. List은 한 번에 모든 레코드를 Java 힙에 로드합니다.

  3. 백그라운드 배치 작업(예: 분석 계산)이 필요한 경우 모든 레코드를 한 번에 힙에 로드하지 않기 때문에 스트림 작업을 사용할 수 있습니다.

  4. 모든 레코드와 함께 REST 응답을 반환해야 하는 경우 어쨌든 레코드를 RAM에 로드하고 JSON으로 직렬화해야 합니다. 이 경우 List을 한 번에 로드하는 것이 좋습니다.


일부 개발자는 응답을 반환하기 전에 스트림을 List으로 수집하는 것을 보았습니다.

class UserController {

    public ResponseEntity<List<User>> getUsers() {
        return new ResponseEntity(
                repository.streamByFirstName()
                        // OK, for mapper - it is nice syntactic sugar. 
                        // Let's imagine there is not map for now...
                        // .map(someMapper)  
                       .collect(Collectors.toList()), 
                HttpStatus.OK);
    }
}

list이 경우 Stream을 사용 하면 동일한 결과를 얻을 수 있으므로 Stream의 이점을 볼 수 없습니다 .

그렇다면 사용 list이 정당화되는 예가 있습니까?

2. 해결방안:

tl;박사

CollectionVS 의 주요 차이점 Stream은 다음 두 가지 측면입니다.

  1. 첫 번째 결과까지의 시간 – 클라이언트 코드는 언제 첫 번째 요소를 볼 수 있습니까?
  2. 처리 중 리소스 상태 - 스트림이 처리되는 동안 기본 인프라 리소스는 어떤 상태입니까?

컬렉션 작업

예를 들어 이야기해 봅시다. Customer리포지토리에서 100,000개의 인스턴스 를 읽어야 한다고 가정해 보겠습니다 . 결과를 처리하는 방식은 위에서 설명한 두 가지 측면 모두에 대한 힌트를 제공합니다.

List<Customer> result = repository.findAllBy();

클라이언트 코드는 그 이전이 아니라 기본 데이터 저장소에서 모든 요소를 ​​완전히 읽은 후에 해당 List을 수신합니다. 그러나 기본 데이터베이스 연결 이 닫힐 수도 있습니다. 예를 들어 Spring Data JPA 애플리케이션 에서 주변 메소드에 어노테이션 EntityManager달거나 . 또한 리소스를 적극적으로 닫을 필요가 없습니다.@TransactionalOpenEntityManagerInViewFilter

스트림 작업

스트림은 다음과 같이 처리되어야 합니다.

@Transactional
void someMethod() {

  try (Stream result = repository.streamAllBy()) {
    // … processing goes here
  }
}

를 사용하면 Stream첫 번째 요소(예: 데이터베이스의 행)가 도착하고 매핑되는 즉시 처리를 시작할 수 있습니다. 즉, 다른 결과 집합이 처리되는 동안 이미 요소를 사용할 수 있습니다. 즉, 기본 리소스는 일반적으로 리포지토리 메서드 호출에 바인딩되므로 기본 리소스를 적극적으로 열어 두어야 합니다. 기본 리소스를 바인딩할 때 Stream또한 리소스를 적극적으로 닫아야(try-with-resources)하고 어떻게든 리소스를 닫으라는 신호를 보내야 합니다.

JPA를 사용하면 메서드 반환 시 기본이 닫히 므로 제대로 처리 @TransactionalStream수 없습니다 . EntityManager몇 가지 요소가 처리되는 것을 볼 수 있지만 처리 중에 예외가 발생합니다.

다운스트림 사용

따라서 이론적으로는 예를 들어 JSON 배열을 효율적으로 구축하는 데 사용할 수 있지만 모든 요소 Stream를 ​​작성할 때까지 핵심 리소스를 열어 두어야 하므로 그림이 상당히 복잡해집니다 . 이는 일반적으로 개체를 JSON에 매핑하는 코드를 작성하고 수동으로 응답에 작성하는 것을 의미합니다(예: Jackson .ObjectMapperHttpServletResponse

메모리 풋프린트

ResultSet메모리 사용 공간이 개선될 가능성이 높지만 이는 대부분 매핑 단계( -> Customer-> CustomerDTO-> JSON 개체) 에서 컬렉션 및 추가 컬렉션의 중간 생성을 피하는 것과 같다는 사실에서 비롯됩니다 . 이미 처리된 요소 는 다른 이유로 유지될 수 있으므로 메모리에서 제거된다는 보장 이 없습니다 . 다시 말하지만, 예를 들어 JPA EntityManager에서는 리소스 수명 주기를 제어하므로 열린 상태를 유지해야 하므로 모든 요소가 해당 리소스에 바인딩된 상태로 유지되고 모든 요소가 처리 EntityManager될 때까지 유지 됩니다.

반응형