Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
Tags
- 데이크스트라
- zsh theme
- 알고리즘
- 인프런워밍업클럽
- 스터디2기
- zsh
- table status
- 네트워킹데이
- 동적 프로그래밍 방법
- 분할정복 방법
- mysql 표
- 이진탐색
- 오일러 경로
- 욕심쟁이 방법
- cs
- 오블완
- 터미널
- 순차탐색
- VI
- 티스토리챌린지
- 인프런
- 맥
- CS스터디
- Less
- spring boot
- Pager
- oh-my-zsh
- mysql 표 출력
- MySQL
- mycli
Archives
- Today
- Total
Develop
[Spring Boot] 게시판 - 11. 동적 정렬과 필터링으로 사용자 경험 개선하기 본문
안녕하세요 .ᐟ
오늘은 어제 구현한 QueryDSL 검색 기능을 확장해서, 실제 게시판에서 필수적인 정렬과 필터링 기능을 추가했습니다!
사용자가 원하는 방식으로 게시글을 조회할 수 있도록 만드는 과정을 공유해보겠습니다.~
오늘 구현한 기능
- 동적 정렬: 최신순, 조회수순, 제목순 자유롭게 선택
- 카테고리 필터링: 특정 카테고리의 게시글만 보기
- 복합 조건 검색: 검색어 + 카테고리 + 정렬 조합
- 조회수 자동 증가: 게시글을 볼 때마다 조회수 증가
- 성능 최적화: DB 인덱스 추가
1. 조회수 기능 추가
Post.java
@Column(nullable = false)
private Long viewCount = 0L;
public void increaseViewCount() {
this.viewCount++;
}
Service에서 조회수 증가 로직
@Transactional
public Post findByIdWithViewCount(Long id) {
Post post = postRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException(...));
post.increaseViewCount(); // 조회할 때마다 증가
return post;
}
조회 - 별도 메서드로 분리
- findById: 단순 조회용 (관리자가 조회수 증가 없이 보고 싶을 때)
- findByIdWithViewCount: 사용자 조회용 (조회수 증가 포함)
2. QueryDSL로 동적 정렬 구현
- Spring Data JPA의 Pageable과 Sort를 활용해서 사용자가 선택한 정렬 기준을 적용
- 동작 방식
- Pageable에서 Sort 정보 추출
- 각 정렬 조건을 OrderSpecifier로 변환
- QueryDSL 쿼리에 적용
private OrderSpecifier<?>[] getOrderSpecifiers(Sort sort) {
List<OrderSpecifier<?>> orders = new ArrayList<>();
if (sort.isEmpty()) {
orders.add(post.createdAt.desc()); // 기본: 최신순
} else {
sort.forEach(order -> {
Order direction = order.isAscending() ? Order.ASC : Order.DESC;
String property = order.getProperty();
switch (property) {
case "createdAt":
orders.add(new OrderSpecifier<>(direction, post.createdAt));
break;
case "viewCount":
orders.add(new OrderSpecifier<>(direction, post.viewCount));
break;
case "title":
orders.add(new OrderSpecifier<>(direction, post.title));
break;
default:
orders.add(post.createdAt.desc());
}
});
}
return orders.toArray(new OrderSpecifier[0]);
}
3. 카테고리 필터링
- 검색 조건에 카테고리 필터를 추가
- BooleanBuilder를 사용하면 조건을 동적으로 조합할 수 있습니다.
public Page<Post> searchPostsWithFilters(
String keyword,
Long categoryId,
Pageable pageable
) {
BooleanBuilder builder = new BooleanBuilder();
// 검색어 조건
if (keyword != null && !keyword.isBlank()) {
builder.and(
post.title.containsIgnoreCase(keyword)
.or(post.content.containsIgnoreCase(keyword))
.or(post.user.username.containsIgnoreCase(keyword))
);
}
// 카테고리 필터
if (categoryId != null) {
builder.and(post.category.id.eq(categoryId));
}
// ... 쿼리 실행
}
4. Controller에서 사용하기
@GetMapping
public ResponseEntity<Page<PostResponse>> getPosts(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long categoryId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "createdAt,desc") String[] sort
) {
Sort sortObj = Sort.by(
sort[1].equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC,
sort[0]
);
Pageable pageable = PageRequest.of(page, size, sortObj);
Page<PostResponse> posts = postService.searchPostsWithFilters(
keyword, categoryId, pageable
);
return ResponseEntity.ok(posts);
}
API 호출 예시
# 최신순 정렬
GET /posts?sort=createdAt,desc
# 조회수순 정렬
GET /posts?sort=viewCount,desc
# 카테고리 필터링
GET /posts?categoryId=1
# 복합 조건
GET /posts?keyword=스프링&categoryId=1&sort=viewCount,desc
5. 성능 최적화 - 인덱스 추가
- 정렬과 필터링이 빠르게 동작하도록 DB 인덱스를 추가
- created_at: 최신순 정렬 시 빠른 조회
- view_count: 조회수 순 정렬 시 빠른 조회
- category_id: 카테고리 필터링 시 빠른 조회
@Entity
@Table(
name = "posts",
indexes = {
@Index(name = "idx_post_created_at", columnList = "created_at"),
@Index(name = "idx_post_view_count", columnList = "view_count"),
@Index(name = "idx_post_category_id", columnList = "category_id")
}
)
public class Post extends BaseEntity {
// ...
}
느낀 점
1. 동적 정렬의 필요성
사용자마다 원하는 정렬 기준이 다르기 때문에, 하나의 API로 여러 정렬 옵션을 제공하는 게 중요한 것 같습니다.
2. 메서드 네이밍의 중요성
findById와 findByIdWithViewCount처럼 내용은 비슷해도 관리자용/ 사용자용으로 나누어 생각해볼 수 있었습니다.
3. 인덱스의 중요성
정렬과 필터링이 자주 일어나는 컬럼에는 인덱스를 추가해야 성능이 좋아집니다.
4. Pageable + Sort 조합
Spring Data JPA의 Pageable은 페이징뿐만 아니라 정렬까지 한 번에 처리할 수 있어서 편리했습니다.

'백엔드 > 게시판 만들기' 카테고리의 다른 글
| [Spring Boot] 게시판 - 13. 코드 리팩토링과 배포 파이프라인 개선 (0) | 2025.12.11 |
|---|---|
| [Spring Boot] 게시판 - 12. JPA N+1 문제 해결하기 (EntityGraph vs Fetch Join) (0) | 2025.12.09 |
| [Spring Boot] 게시판 - 10. QueryDSL로 동적 검색 기능 구현하기 (0) | 2025.12.05 |
| 초보 백엔드가 무중단 배포와 CI/CD를 직접 구축하면서 만난 에러들 (0) | 2025.12.04 |
| [Spring Boot] 게시판 - 09. 게시글 이미지 업로드 구현하기 (feat. 이미지 여러개 업로드 & 순서 관리) (0) | 2025.11.30 |