Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ interface PayerRepository : JpaRepository<Payer, Long?> {
@Query("select p from Payer p where p.id in :ids")
fun findAllByIds(@Param("ids") ids: List<Long>): List<Payer>

@Query("SELECT p FROM Payer p WHERE p.name LIKE CONCAT('%', :name, '%') ORDER BY p.enrollmentYear DESC, p.name")
// 정렬은 Pageable의 Sort에만 맡긴다. 여기에 ORDER BY를 같이 두면 Spring Data가
// Sort를 지우지 않고 뒤에 이어붙여 정렬 키가 중복된다. (PayerService.resolveSort 참고)
@Query("SELECT p FROM Payer p WHERE p.name LIKE CONCAT('%', :name, '%')")
fun findAllByNameContaining(@Param("name") name: String, pageable: Pageable): Page<Payer>

fun findAllByEnrollmentYear(enrollmentYear: String): List<Payer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,42 @@ class PayerService(
val pageRequest = PageRequest.of(
pageableCondition.pageNo,
pageableCondition.size,
Sort.by(Sort.Direction.DESC, pageableCondition.criteria ?: "enrollmentYear")
.and(Sort.by(Sort.Direction.ASC, "id"))
resolveSort(pageableCondition.criteria)
)
val search = searchCondition.search

// 검색어가 없으면 LIKE '%%'는 한 건도 걸러내지 못하면서 선행 와일드카드 때문에
// 인덱스 접근만 막는다. 목록 첫 진입이 이 경로이므로 술어 자체를 빼서
// 정렬 인덱스를 타고 LIMIT에서 끊게 한다.
if (search.isBlank()) {
return payerRepository.findAll(pageRequest)
}

return payerRepository.findAllByNameContaining(search, pageRequest)
}

/**
* 납부자 목록의 정렬 기준을 만드는 유일한 지점.
*
* 기본 정렬은 `idx_payer_enrollment_year_name(enrollment_year DESC, name)` 의 순서와 맞춘다.
* InnoDB 보조 인덱스는 뒤에 PK가 붙으므로 `id ASC` 까지 인덱스 순서로 읽을 수 있고,
* 정렬 키가 인덱스와 어긋나면 조건에 걸린 전체 행을 filesort 하게 된다.
*
* criteria는 클라이언트가 넘기는 값이라 화이트리스트로 받는다. 선언형 @Query에는
* 프로퍼티명이 검증 없이 그대로 붙어서, 없는 이름이 들어오면 쿼리 자체가 깨진다.
*/
private fun resolveSort(criteria: String?): Sort = when (criteria) {
"name" -> Sort.by(
Sort.Order.asc("name"),
Sort.Order.desc("enrollmentYear"),
Sort.Order.asc("id")
)

else -> Sort.by(
Sort.Order.desc("enrollmentYear"),
Sort.Order.asc("name"),
Sort.Order.asc("id")
)
return payerRepository.findAllByNameContaining(searchCondition.search, pageRequest)
}

@Transactional
Expand Down
Loading