Skip to content

Commit be4f51c

Browse files
authored
Merge pull request #150 from billilge/fix/#142-filesort
fix: 납부자 목록 조회 정렬 중복 제거 및 criteria 파라미터 오동작 수정
2 parents a89803d + 7c95288 commit be4f51c

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

‎src/main/kotlin/site/billilge/api/backend/domain/payer/repository/PayerRepository.kt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ interface PayerRepository : JpaRepository<Payer, Long?> {
1313
@Query("select p from Payer p where p.id in :ids")
1414
fun findAllByIds(@Param("ids") ids: List<Long>): List<Payer>
1515

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

1921
fun findAllByEnrollmentYear(enrollmentYear: String): List<Payer>

‎src/main/kotlin/site/billilge/api/backend/domain/payer/service/PayerService.kt‎

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,42 @@ class PayerService(
6565
val pageRequest = PageRequest.of(
6666
pageableCondition.pageNo,
6767
pageableCondition.size,
68-
Sort.by(Sort.Direction.DESC, pageableCondition.criteria ?: "enrollmentYear")
69-
.and(Sort.by(Sort.Direction.ASC, "id"))
68+
resolveSort(pageableCondition.criteria)
69+
)
70+
val search = searchCondition.search
71+
72+
// 검색어가 없으면 LIKE '%%'는 한 건도 걸러내지 못하면서 선행 와일드카드 때문에
73+
// 인덱스 접근만 막는다. 목록 첫 진입이 이 경로이므로 술어 자체를 빼서
74+
// 정렬 인덱스를 타고 LIMIT에서 끊게 한다.
75+
if (search.isBlank()) {
76+
return payerRepository.findAll(pageRequest)
77+
}
78+
79+
return payerRepository.findAllByNameContaining(search, pageRequest)
80+
}
81+
82+
/**
83+
* 납부자 목록의 정렬 기준을 만드는 유일한 지점.
84+
*
85+
* 기본 정렬은 `idx_payer_enrollment_year_name(enrollment_year DESC, name)` 의 순서와 맞춘다.
86+
* InnoDB 보조 인덱스는 뒤에 PK가 붙으므로 `id ASC` 까지 인덱스 순서로 읽을 수 있고,
87+
* 정렬 키가 인덱스와 어긋나면 조건에 걸린 전체 행을 filesort 하게 된다.
88+
*
89+
* criteria는 클라이언트가 넘기는 값이라 화이트리스트로 받는다. 선언형 @Query에는
90+
* 프로퍼티명이 검증 없이 그대로 붙어서, 없는 이름이 들어오면 쿼리 자체가 깨진다.
91+
*/
92+
private fun resolveSort(criteria: String?): Sort = when (criteria) {
93+
"name" -> Sort.by(
94+
Sort.Order.asc("name"),
95+
Sort.Order.desc("enrollmentYear"),
96+
Sort.Order.asc("id")
97+
)
98+
99+
else -> Sort.by(
100+
Sort.Order.desc("enrollmentYear"),
101+
Sort.Order.asc("name"),
102+
Sort.Order.asc("id")
70103
)
71-
return payerRepository.findAllByNameContaining(searchCondition.search, pageRequest)
72104
}
73105

74106
@Transactional

0 commit comments

Comments
 (0)