본문 바로가기
❤️‍🔥TIL (Today I Learned)

[TIL] 2023-02-01(65day) / JPA 심화(2)

by elicho91 2023. 2. 1.

JPA 심화(1)


👉 페이징 처리 프로세스

  1. PageRequest 사용하여 Pageable 페이징 정보를 담아 객체화 한다.
  2. Pageable JpaRepository 상속된 인터페이스의 메서드에 T(Entity) 함꼐 파라미터로 전달한다.
  3. 2번의 메서드의 return 으로 Page<T> 응답 된다.
  4. 응답된 Page<T> 담겨진 Page 정보를 바탕으로 로직을 처리하면 된다.

 

👉 Pageable

- 페이징을 제공하는 중요한 인터페이스

 

# Pageable 만드는 법

PageRequest.of(int page, int size) : 0부터 시작하는 페이지 번호와 개수. 정렬이 지정되지 않음
PageRequest.of(int page, int size, Sort sort) : 페이지 번호와 개수, 정렬 관련 정보
PageRequest.of(int page int size, Sort sort, Direction direction, String ... props) : 0부터 시작하는 페이지 번호와 개수, 정렬의 방향과 정렬 기준 필드들

 

# Pageable 메서드

pageable.getTotalPages() : 총 페이지 수
pageable.getTotalElements() : 전체 개수
pageable.getNumber() : 현재 페이지 번호
pageable.getSize() : 페이지 당 데이터 개수
pageable.hasnext() : 다음 페이지 존재 여부
pageable.isFirst() : 시작페이지 여부
pageable.getContent(), PageRequest.get() : 실제 컨텐츠를 가지고 오는 메서드. getContext는 List<Entity> 반환, get()은 Stream<Entity> 반환

 

# Paging 응답

{
    "content": [
        {"id": 1, "username": "User 0", "address": "Korea", "age": 0},
        ...
        {"id": 5, "username": "User 4", "address": "Korea", "age": 4}
    ],
    "pageable": {
        "sort": {
            "sorted": false, // 정렬 상태
            "unsorted": true,
            "empty": true
        },
        "pageSize": 5, // 페이지 크기
        "pageNumber": 0, // 페이지 번호 (0번 부터 시작)
        "offset": 0, // 해당 페이지의 첫번째 요소의 전체 순번 (다음 페이지에서는 5)
        "paged": true,
        "unpaged": false
    },
    "totalPages": 20, // 페이지로 제공되는 총 페이지 수
    "totalElements": 100, // 모든 페이지에 존재하는 총 원소 수
    "last": false,  // 마지막 페이지 여부
    "number": 0,
    "sort": {
        "sorted": false,    // 정렬 사용 여부
        "unsorted": true,
        "empty": true
    },
    "size": 5,       // Contents 사이즈
    "numberOfElements": 5,  // Contents 의 원소 수
    "first": true,   // 첫페이지 여부
    "empty": false   // 공백 여부
}

 

👉 페이지 반환 타입

Page<T> 타입

  • 게시판 형태의 페이징에서 사용된다.
  • 전체 요소 갯수도 함께 조회. (totalElements)
  • 응답은 위와 동일

Slice<T> 타입

  • 더보기 형태의 페이징에서 사용.
  • 전체 요소 갯수 대신 offset 필드로 조회할 있다.

List<T> 타입

  • 전체 목록보기 형태의 페이징에서 사용된다.
  • 기본 타입으로 count 조회가 발생하지 않는다.

 

👉  정렬

# 컬럼 값으로 정렬하기

 - Sort 클래스를 사용한다.

Sort sort1 = Sort.by("name").descending();     // 내림차순
Sort sort2 = Sort.by("password").ascending();  // 오름차순
Sort sortAll = sort1.and(sort2);      // 2개이상 다중정렬도 가능하다
Pageable pageable = PageRequest.of(0, 10, sortAll);  // pageable 생성시 추가

 

# 컬럼이 아닌값으로 정렬하기

 - @Query 사용시 Alias(쿼리에서 as 로 지정한 문구) 를 기준으로 정렬할 수 있다.

// 아래와 같이 AS user_password 로 Alias(AS) 를 걸어주면
@Query("SELECT u, u.password AS user_password FROM user u WHERE u.username = ?1")
List<User> findByUsername(String username, Sort sort);

// 이렇게 해당 user_password 를 기준으로 정렬할 수 있다.
List<User> users = findByUsername("user", Sort.by("user_password"));

 

# JpaSort 를 사용해서 쿼리 함수를 기준으로 정렬할 수 있다.

// 아래와 같이 일반적인 쿼리에서
@Query("SELECT u FROM user u WHERE u.username = ?1") // 이건 없어도됨
List<User> findByUsername(String username, Sort sort);

// 이렇게 쿼리함수 LENGTH() 조건을 걸어서 password 문자길이 기준으로 정렬할 수 있다.
List<User> users = findByUsername("user", JpaSort.unsafe("LENGTH(password)"));

 

👉  페이징 & 정렬 실무 팁

# List<T>가 필요하면 응답을 Page<T>로 받지말고 List<T> 로 받기

 - 전체 count 쿼리가 추가로 발생하는 Page<T> 보다는 List<T>가 대용량 처리할때 더 안정적이고 빠르다!

//전체셀러 목록 조회
    @Override
    @Transactional(readOnly = true)
    public List<SellerProfileResponseDto> allSellerList(PageDTO pageDTO){

				// 이부분은 List로 받아도됨 (userRepository 도 수정)
        Page<User> usersByUserRole = userRepository
                .findUsersByUserRole(UserRole.SELLER, pageDTO.toPageable());

        List<SellerProfileResponseDto> sellerProfileResponseDtos = new ArrayList<>();

        for(User user:usersByUserRole){
            if(!user.getProfile().getIntroduce().isEmpty()) {
                sellerProfileResponseDtos.add(new SellerProfileResponseDto(user.getUserName(), user.getProfile().getIntroduce()));
            }
        }

        return sellerProfileResponseDtos;

    }

 

# Pageable 과 실제 페이지사이의 -1 문제 해결하기

 - JPA 페이지는 0부터인데 화면은 1부터시작하는 문제 (-1 처리를 중복으로 해줘야하는 이슈)

 - PageDTO 를 만들어서 toPageable() 메소드를 사용

public class PageDTO {
  @Positive // 0보다 큰수
  private Integer currentPage;
  private Integer size;
  private String sortBy;

  public Pageable toPageable() {
    return PageRequest.of(currentPage-1, size, Sort.by(sortBy).descending());
  }
}

// ----------------------------------------------------------------------------

// UserService 일부
public List<User> findAll(PageDTO pageDTO){
	return userRepository.findUsers(pageDTO.toPageable());
}

🙋‍♂️ 소감 : 

이전 프로젝트에서 페이징 처리를 할때 API 요청필드에서 받아왔었는데 현업에서는 잘 쓰지 않는 방법이라고 한다.

다음 프로젝트에는 실무에서 사용하는 방법으로 작업해봐야겠다.

😈 아는 내용이라고 그냥 넘어가지 않기! 😈

댓글