[JPA + Stream] 각 그룹의 최댓값을 가진 데이터만 보고 싶다면
728x90
반응형

⚠️ 경고 ⚠️

본 카테고리, Dev Memo는 필자가 깊게 다루기는 귀찮지만 궁금한 것들을 체험해보고 간단하게 기록을 남기는 공간입니다. 디버깅 노트, 써드파티 라이브러리 사용기, 버전 업데이트, 어이없는 실수, 오탈자 발견 등. 각종 시덥지 않은 내용이 들어 갈 수 있다는 점 참고바랍니다. (우헤헿)

 

 

미소녀의 요구사항을 들어주자

 

일단 다음과 같은 환경을 갖춘 상태이다.

  • 스프링부트 + JPA + Kotlin
  • User와 Club 테이블은 양방향 조인
  • ID는 Auto Increment

 

다음은 요구사항이다.

  • 클럽에 가입 신청 혹은 신청 승인된 User를 찾는다
  • 특정 기간 내 가입 신청한 User들을 찾는다
  • 해당 유저들은 클럽별로 묶는다
  • 클럽별 가장 최근에 가입 신청한 User를 모아서 Return해준다

 

 

고민해보았다. 과연 Spring Data JPA가 제공하는 쿼리 메소드만으로 이 요구사항을 만족시킬 수 있을까? 수시간을 고민했지만 답을 찾을 수 없었다. 네이티브 쿼리를 사용한다고 해도 무리가 있었다. JPA 서브 쿼리의 한계와 from에 등장한 테이블을 서브쿼리에 재등장시킬 수 없는 점 등이 발목을 잡았다. 만약 QueryDSL을 사용했다면 가능했을 수 있지만 당장 개발을 하기 위해서는 다른 대안이 필요했다. 그래서 얻어낸 해결 방법은 쿼리 메소드와 스트림을 합친 방법이다. 혹시 이 방법보다 좋은 방법을 아는 분이 있다면 댓글 등으로 알려주길 바란다.

 

먼저 기간과 상태를 기준으로 유저를 조회한다. 이를 위한 메소드는 아래와 같다.

interface UserRepository : JpaRepository<User, Long> {

    fun findAllByCreatedAtBetweenAndStatusIn(
        startAt: LocalDateTime,
        endAt: LocalDateTime,
        status: List<UserStatus>
    ): List<User>
    
}

 

그룹핑은 스트림을 통해서 해결했다. 먼저 groupBy로 Club별 map으로 묶는다. 다음 mapNotNull로 Null이 아닌 결과값만 가지도록 하였다. Key가 Club이고 Value가 User인 상태에서 maxByOrNull로 Club에서 ID가 가장 큰 User 데이터를 찾는다. 클럽에 신청했을 때 데이터가 생성되므로 ID가 가장 큰 값을 가진 User가 최신에 가입 신청한 유저이다.

fun getRecentList(startDate: LocalDate, endDate: LocalDate): List<User> {
    // 해당 기간의 모든 user 조회
    val users = userRepository.findAllByCreatedAtBetweenAndStatusIn(
        startDate.atStartOfDay(),
        endDate.atStartOfDay(),
        listOf(REQUESTED, APPROVED)
    )

    // 개별 클럽의 최신 유저만 고르기
    return users.groupBy {
        it.club
    }.mapNotNull {
        it.value.maxByOrNull { user ->
            user.id
        }
    }
}

 

이렇게 해서 요구사항에 만족하는 최신 유저 데이터를 얻어보았다. 단순히 쿼리 혹은 스트림에서 그룹을 만든다고 하여 원하는 조건을 만족하는 결과를 얻을 수 있는 것이 아님을 알았다. 경우에 따라서는 퍼포먼스가 조금 하락하더라도 복합적인 사용을 염두해야 한다.

 

🏖 참고자료
- JPQL 서브쿼리, 조건식, 기본 함수
- GROUP BY 최대/최소값을 가진 ROW에 있는 다른 COLUMN 값 구하기
- java map 최대값 key,value 구하기

 

728x90
반응형