다수의 데이터가 있을 경우, 전부를 보내주지 않고 Paging 하여 리턴 할 수 있도록 한다. 기본구조 4개중 Repository Service, Controller 세곳 모두 연관이 있다. 기본적으로 정렬과 연관이 있다. 정렬된 데이터를 리턴하기 때문이다.
페이징 방식은 크게 두개로 나뉜다
페이지네이션 (offset - 시작위치 / limit - 사이즈)
- 페이지 건너뛰어 탐색 가능
- 앞 페이지에 데이터가 삽입 되었을 경우, 다음페이지 탐색시 중복 데이터가 노출된다.
- 스프링부트에서 쉽게 사용할 수 있다.
- Page 를 사용한다
- 기본 Page로 비즈니스 로직을 사용할 수 없다면, @Query 등으로 수동으로 쿼리문을 통해 Page와 조합하여 구현한다
- Page 라는 자료형을 사용하고, count 쿼리도 같이 날린다. 전체 페이지 수를 계산해야 하기 떄문이다. DB에 부담될 수 있다.
- @Query 어노테이션을 이용할 경우 따로 countQuery 쿼리도 포함해 줘야 한다.
무한 스크롤 (커서 기반)
- 앞의 보여준 페이지(?) 에 데이터가 삽입 되어도 상관없이 이어서 보여줄 수 있다.
- 커서가 중요하다. 커서는 정렬조건으로 주로 createdAt 혹은 ID가 많이 쓰인다.
- 이 커서를 클라이언트로 부터 받기도 해야하고, 주기도 해야 한다. 이를 통해 정렬된 데이터 중 다음 데이터를 줄 위치를 파악한다.
- Slice 를 이용한다.
- 기본 Slice로 비즈니스 로직을 구현할 수 없을 경우 @Query 등으로 수동으로 쿼리문을 통해 Slice와 조합하여 구현해 주어야 한다. (where 조건을 추가 하는것이 중요)
- 예 : 생성일 기준 최신순으로 보여줄때. 1번은 최초 탐색, 2번은 다음 데이터 탐색 쿼리문 (여기서 생성일이 커서가 된다)
- select * from comments order by created_at desc limit 10
- select * from comments where created_at < 날짜 order by created_at desc limit 10
- Slice 자료형을 사용하고, hasNext 맴버변수로 다음이 있는지 확인하기 때문에 count쿼리를 날리지 않는다.
일반 Offset 기반 Page
- Repository
- Page 를 리턴하는 함수를 하나 생성한다.
- 인자값으로는 Pageable을 받는다.
public interface MemberRepository extends JpaRepository<Member, Long> {
// 이름으로 검색하면서 페이징
Page<Member> findByNameContaining(String name, Pageable pageable);
}
- 만일, @Query 어노테이션을 이용하여 JPQL을 이용할 경우, countQuery 쿼리 또한 작성을 해 주어야 한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query(
value = "SELECT m FROM Member m WHERE m.name LIKE %:name%",
countQuery = "SELECT COUNT(m) FROM Member m WHERE m.name LIKE %:name%"
)
Page<Member> findByNameContainingCustom(
@Param("name") String name,
Pageable pageable);
}
- Service
- Repository 에서 선언한 함수를 호출하여 값을 리턴한다
- PageRequest 를 이용해 조건을 생성하여 조회 한다.
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
public Page<MemberDTO> searchMembersByName(String keyword, int page, int size, String[] sort) {
Sort.Direction direction = sort[1].equalsIgnoreCase("desc")
? Sort.Direction.DESC
: Sort.Direction.ASC;
Sort sortCondition = Sort.by(direction, sort[0]);
Pageable pageable = PageRequest.of(page, size, sortCondition);
Page page = memberRepository.findByNameContaining(keyword, pageable);
return page.map(MemberDTO변환);
}
}
- PageRequest.of 에서 size 는 1페이지에 들어갈 데이터의 수 이다.
- Controller
- 인자값으로 페이지 번호를 받아야 한다
- Service 함수를 통해 Page를 받아온다
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
// 특정 이름 검색 (페이징)
// ex) /members/search?keyword=홍&page=0&size=5&sort=id,asc
@GetMapping("/search")
public Page<MemberDTO> searchMembers(
@RequestParam String keyword,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id,asc") String[] sort) {
return memberService.searchMembersByName(keyword, page, size, sort);
}
}
무한스크롤을 위한 Slice 조회 예
- Slice를 기본으로 이용하는 예제.
- Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
Slice<Member> findByNameContaining(String name, Pageable pageable);
}
- Service
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
// 특정 이름 검색 (Slice 기반 무한 스크롤)
public Slice<Member> searchMembersByName(String keyword, int page, int size, String[] sort) {
Sort.Direction direction = sort[1].equalsIgnoreCase("desc")
? Sort.Direction.DESC
: Sort.Direction.ASC;
Sort sortCondition = Sort.by(direction, sort[0]);
Pageable pageable = PageRequest.of(page, size, sortCondition);
return memberRepository.findByNameContaining(keyword, pageable);
}
}
- Controller
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
// 특정 이름 검색 (무한 스크롤)
// ex) /members/search?keyword=홍&page=0&size=10&sort=id,asc
@GetMapping("/search")
public Slice<Member> searchMembers(
@RequestParam String keyword,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id,asc") String[] sort) {
return memberService.searchMembersByName(keyword, page, size, sort);
}
}
- Page 내장 속성
- 변수명.isEmpty
- .totalPages
- .size
- 페이지당 보여줄 수
- .number
- 현재 페이지 번호
- .hasPrevious
- .hasNext 등이 있다.
'Backend > SpringBoot' 카테고리의 다른 글
| Spring Security 기본 설정 (0) | 2024.10.25 |
|---|---|
| DTO (W. Data, Build 어노테이션) (0) | 2024.10.24 |
| [기본구조4] 서비스 (0) | 2024.10.23 |
| 응답으로 템플릿 리턴하기 (model, 타임리프, layout) (0) | 2024.10.23 |
| 의존성 주입 (List, Map 포함) (0) | 2024.10.23 |