@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
//ProductType이 sale인것을 가져오는 쿼리(pagenation 가능)
Page<Product> findByProductTypeOrderByIdDesc(ProductType type, Pageable pageable);
//ProductType이 sale인것을 가져오는 쿼리
List<Product> findAllByProductType(ProductType type);
}
한 페이지에 물품 9개를 올려보려고 합니다.
mysql에서는
select * from product limit 10 offset 1;
이렇게 쓰면
이런식으로 product테이블에 있는 값들이 나옵니다.
limit는 한번에 가져올 데이터의 양
offset은 어디서부터 가져올지 기준을 잡아줍니다.
이 2개를 jpa로 offset값을 9만큼만 옮겨주면 될거같습니다.
ShopController
@GetMapping
public String shop(Model model, @PageableDefault(size = 9) Pageable pageable) {
//페이징화 된 객체
List<ProductRecordDto> content = productService.paged_product(pageable).getContent();
//전체 페이지 수
int totalPages = productService.paged_product(pageable).getTotalPages();
//현제 페이지
int presentPage = productService.paged_product(pageable).getNumber();
//페이징된 물품들 모델로 보내기
model.addAttribute("allProduct", content);
//전체 페이지 수 모델로 보내기
model.addAttribute("totalPages",totalPages);
//현제 페이지 모델로 보내기
model.addAttribute("presentPage",presentPage);
return "shop";
}
페이징을 Pageable라는 객체로 실행했습니다. JPA에는 Pageable이라는 객체가 있는데요. 이객체로 Pagin을 쉽게 구현할 수 있습니다.
가장먼저 Controller 에서 Pageable 인터페이스 타입으로 파라미터를 받으면 됩니다.
메소드 | 설명 | 리턴 타입 |
unpaged() | 페이지 매김 설정이 없음을 나타내는 Pageable 인스턴스를 반환합니다 |
Pageable |
isPaged() | 현재 Pageable에 페이지 매기기 정보가 포함되어 있는지 여부를 반환합니다 |
boolean |
isUnpaged() | 현재 {@link Pageable}에 페이지 매기기 정보가 없는지 여부를 반환합니다. |
boolean |
getPageNumber() | 현재 페이지 번호를 리턴합니다 | int |
getPageSize() | 한 페이지당 보여 줄 개수를 리턴합니다 | int |
getOffset() | 기본 페이지 및 페이지 크기에 따라 취할 오프셋을 반환합니다 |
long |
getSort() | 정렬 매개변수를 반환합니다 | Sort |
getSortOr() | 현재Sort 또는 현재 정렬되지 않은 경우 지정된 정렬을 반환합니다. @param 정렬은 null이 아니어야합니다. |
Sort |
next() | 다음 페이지를 요청하는Pageable 을 반환합니다 | Pageable |
previousOrFirst() | 이전 Pageable 또는 현재 페이지가 이미 첫 번째 인 경우 첫 번째 Pageable 를 반환합니다. |
Pageable |
first() | 첫 페이지를 요청하는 @link Pageable 을 반환합니다. | Pageable |
hasPrevious() | 현재 페이지에서 액세스 할 수있는 이전 Pageable 가 있는지 여부를 반환합니다. 현재 Pageable 이 이미 첫 페이지를 참조하는 경우 false 를 반환합니다. |
boolean |
Pageable 인터페이스 안에는 여러가지 메소드들이 있습니다. 이것들을 활용해서 페이지를 구현해 봅시다.
Repository 구현하기
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
//ProductType이 sale인것을 가져오는 쿼리(pagenation 가능)
Page<Product> findByProductTypeOrderByIdDesc(ProductType type, Pageable pageable);
}
findByProductTypeOrderByIdDesc(ProductType, Pageable)메소드를 보시면 물품의 type을 받아서 Product 객체를 가지고 오고 있습니다. 여기서 반환타입은 Page인터페이스 안에 Product 객체가 들어있는 형태 입니다.
Service에서 DTO로 만들기
@Transactional
public Page<ProductRecordDto> paged_product(Pageable pageable){
Page<ProductRecordDto> sale = productRepository
.findByProductTypeOrderByIdDesc(ProductType.sale, pageable)
.map(this::getProductRecordDto);
return sale;
}
객체의 반환형태는 Page<Product> 입니다. 이것을 Page<ProductRecordDTO>형태로 변경시켜보는 과정입니다.
Page<Product>들을 map을 통해서 Page<ProductRecordDTO>로 변환시키고 있습니다.
여기서 map에 관한 것을 배웠습니다. stream형태로 들어오는 요소를 특정조건에 해당하는 값으로 변환해 주는 역할입니다. 즉, Page로 감싸져있는 Product들을 ProductRecordDto로 만들었습니다.
getProductRecordDto()는 매개변수로 Product를 받아서 ProductRecordDto를 만드는 메서드 입니다.
@Transactional
public ProductRecordDto getProductRecordDto(Product product) {
ProductRecordDto productRecordDto = ProductRecordDto.builder()
.id(product.getId())
.name(product.getName())
.auctionPrice(product.getAuctionPrice())
.instantPrice(product.getInstantPrice())
.explanation(product.getExplanation())
.productType(product.getProductType())
.category(product.getCategory())
.deadLine(productDeadLine(product.getCreatedDate(), product.getProductTime()))
.productTime(product.getProductTime())
.productImagesList(getProductRecordImageListDto(product.getProductImagesList()))
.build();
return productRecordDto;
}
여기서 getContent() 메서드를 쓰면 List형태로 반환이 됩니다.
getTotalPages()는 전체 페이지 수를 나타냅니다.
getNumber()는 현재 페이지 수를 보여줍니다.
페이징 하는 과정
저는 localhost:8080/shop?page=원하는페이지(1,2,3...) 형태로 페이징을 했습니다. Query String 형태로 Controller에 요청을 보내면 작성한 코드를 기반으로 JPA가 페이징 쿼리를 만들어 줍니다.
Pageable은 PageRequest 객체를 내부적으로 생성하여 페이징 작업들을 해줍니다. of 메서드로 객체를 생성합니다.
(변수 이름이 page, size 이기 때문에 Query String엣도 page, size 이름이 사용되는거 같습니다.)
Page안에 있는 객체 입니다.
이걸 그리고 thymeleaf 형태로 페이지를 보여주겠습니다.
<th:block th:with="start = ${T(Math).floor(presentPage/10) * 10 +1},
end = (${start + 9 < totalPages ? start+9 : totalPages})">
<li class="page-item"
th:each="pageButton : ${#numbers.sequence(start, end)}">
<a class="page-link" th:href="@{/shop(page = ${pageButton})}" th:text="${pageButton}"></a>
</li>
</th:block>
th:with는 해당 태그를 scope로 갖는 변수를 선언할 수 있게 해주는 속성입니다.
start = ${T(Math).floor(presentPage/10) * 10 +1}
는 현재 페이지를 통해 현재 페이지 그룹의 시작 페이지를 구하는 로직입니다.
end = (${start + 9 < totalPages ? start+9 : totalPages})
시작페이지 + 9 가 전체페이지 보다 작다면 시작페이지+9가 마지막 블럭이고, 아니면 전체 페이지수가 마지막 블럭 입니다.
th:each로 콜렉션(Collection)을 반복하며 화면을 처리할 수 있습니다.
${#numbers.sequence(start, end)} 로 1~10 까지의 수를 가집니다. 1부터 pageButton에 들어가게 됩니다. 그리고 그 page를 shop?page=수 가 됩니다.
T() 메서드는 스프링이 제공하는 EL 표현식에 접근하는 것입니다. 즉, 스프링 프로젝트 내에 존재하는 클래스에 접근합니다.
참고
https://devlog-wjdrbs96.tistory.com/414
https://ivvve.github.io/2019/01/13/java/Spring/pagination_4/
https://theheydaze.tistory.com/339
https://jaime-note.tistory.com/52
https://tecoble.techcourse.co.kr/post/2021-08-15-pageable/
'JAVA > - Spring' 카테고리의 다른 글
[spring boot] JPA에서 일반 Join과 Fetch Join의 차이 (0) | 2022.06.02 |
---|---|
[lombok] @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor 에 대해 (0) | 2022.05.29 |
[spring boot] db에 이전에 넣었던 값 다시 넣어보기 (0) | 2022.05.14 |
[spring boot] @Transactional이란? (0) | 2022.05.13 |
[spring boot] JPA, 다대일 관계에서 '다'쪽이 주인이다. (0) | 2022.05.01 |