: 프로그램을 한번에 처리할 수 있는 적당한 크기(페이지)로 분할해 페이지 단위로 처리하는 것
데이터 수만개를 한번에 내보내면 부하가 심해지니까
페이징
a. page : 조회할 페이지 번호 (1부터 시작)
b. size : 한 페이지에 보여줄 상품 개수 (10개로 고정)
정렬
a. sortBy(정렬항목)
ㄱ. id: product table의 id
ㄴ. title : 상품명
ㄷ. lprice : 최저가
b. isAsc (오름차순?)
ㄱ. true : 오름차순(asc)
ㄴ. false : 내림차순(desc)
쿼리스트링 예시
/api/products?sortBy=String&isAsc=boolean&size=int&page=int
Server -> Client
스프링에는 Page 구현체가 존재한다.
number: 조회된 페이지 번호 (0부터 시작)
content: 조회된 상품 정보 (배열)
size: 한 페이지에 보여줄 상품 개수
numberOfElements: 실제 조회된 상품 개수
totalElements: 전체 상품 개수 (회원이 등록한 모든 상품의 개수)
totalPages: 전체 페이지 수 totalElement / size 한 결과를 소수점 올림
first: 첫 페이지인지? (boolean)
last: 마지막 페이지인지? (boolean)
Page객체는 import org.springframework.data.domain.Page; 임포트 하면 나온다.
// 관심 상품 조회하기
@GetMapping("/products")
public Page<Product> getProducts(
@RequestParam("page") int page,
@RequestParam("size") int size,
@RequestParam("sortBy") String sortBy,
@RequestParam("isAsc") boolean isAsc,
HttpServletRequest request
) {
// 응답 보내기 1페이지가 0번 인덱스이니 page -1을 파라미터로 넣는다(컨트롤러에서만 한번 빼주면 됨)
return productService.getProducts(request, page-1, size, sortBy, isAsc);
}
@Transactional(readOnly = true)
public Page<Product> getProducts(HttpServletRequest request,
int page, int size, String sortBy, boolean isAsc) {
// 페이징 처리 isAsc가 true면 .ASC false면 .DESC
Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
Sort sort = Sort.by(direction, sortBy); // 파라미터로 (정렬방향, 정렬기준)
Pageable pageable = PageRequest.of(page, size, sort); // pagable 객체를 만들어 페이지 size sort를 넣어준다.
// Request에서 Token 가져오기
String token = jwtUtil.resolveToken(request);
Claims claims;
// 토큰이 있는 경우에만 관심상품 조회 가능
if (token != null) {
// Token 검증
if (jwtUtil.validateToken(token)) {
// 토큰에서 사용자 정보 가져오기
claims = jwtUtil.getUserInfoFromToken(token);
} else {
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
() -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
);
// 사용자 권한 가져와서 ADMIN 이면 전체 조회, USER 면 본인이 추가한 부분 조회
UserRoleEnum userRoleEnum = user.getRole();
System.out.println("role = " + userRoleEnum);
Page<Product> products;
if (userRoleEnum == UserRoleEnum.USER) {
// 사용자 권한이 USER일 경우
products = productRepository.findAllByUserId(user.getId(), pageable);
} else {
products = productRepository.findAll(pageable);
}
return products;
} else {
return null;
}
}
: 위의 페이지 기능 만으로는 원하는 관심상품 찾기 힘듬.
폴더 별로 관심상품 저장/관리할 수 있는 기능을 추가.
폴더생성 : 회원별 폴더 추가. 한번에 1~N개 추가 가능
관심 상품에 폴더 설정 : 관심상품에 폴더 아이콘 추가. N개 설정 가능. 관심상품 등록 시점에서는 폴더 저장x. 관심상품 별로 1번에서 생성한 폴더를 선택하여 추가할 수 있다.
폴더별 조회 : 회원은 폴더별로 관심상품 조회 가능. 전체 클릭시 폴더와 상관없이 전체 상품 조회. 폴더명 클릭시 폴더별 ㅓㅈ장된 관심상품 조회.
public class User {
@OneToMany
private List<Folder> folders;
}
public class Folder{
@ManyToOne
@JoinColumn(name = "USER_ID", nullable = false)
private User user;
}
name : 외래키 이름 / nullable : 외래키 null 허용 여부
객체의 연관관계 : 폴더를 소유한 회원 id 가 아닌 객체를 저장
DB의 연관관계 : 외래키를 통한 관계 생성
@ManyToMany
private List<Folder> folderList = new ArrayList<>();
product 안에서 단방향으로 folderList를 manytomany 일단 설정.
이렇게 되면 JPA가 내가 만들지 않은 PRODUCT_FOLDER_LIST라는 테이블을 알아서 하나 생성한다. 이 중간테이블은 더 넣고싶은 정보가 있어도 수정이 불가능. 오로지 mapping되는 정보만 들어감. 그리고 숨겨져있어서 예상못한 query가 JPA에서 자동으로 나갈 수 있다. 그래서 쓰지 않는게 좋다.
// 입력으로 들어온 폴더 이름을 기준으로, 회원이 이미 생성한 폴더들을 조회합니다.
List<Folder> existFolderList = folderRepository.findAllByUserAndNameIn(user, folderNames);
List<Folder> folderList = new ArrayList<>();
// 생성자를 통해 폴더이름과 토큰으로 확인한 유저 정보 넣고 폴더를 새로 만들어서 폴더 리스트에 삽입
for (String folderName : folderNames) {
// 이미 생성한 폴더가 아닌 경우만 폴더 생성
if (!isExistFolderName(folderName, existFolderList)) {
Folder folder = new Folder(folderName, user);
folderList.add(folder);
}
}
// 리스트라 saveAll
return folderRepository.saveAll(folderList);
--------------------------------------------------------------------------------
private boolean isExistFolderName(String folderName, List<Folder> existFolderList) {
// 기존 폴더 리스트에서 folder name 이 있는지?
for (Folder existFolder : existFolderList) {
if (existFolder.getName().equals(folderName)) {
return true;
}
}
return false;
}