LECTURE/JPA
페이징 Paging, 그룹함수
heywoo
2023. 4. 11. 11:50
JPA의 페이징처리
반복적이고 지저분한 페이징 처리용 SQL은 심지어 DBMS에 따라 각각 문법이 다른 문제점을 안고 있다.
JPA는 이러한 페이징을 API를 통해 추상화해서 간단하게 처리할 수 있도록 제공해준다.
@Test
public void 페이징_API를_이용한_조회_테스트() {
//given
int offset = 10; //조회를 건너 뛸 행 수
int limit = 5; //조회할 행 수
//when
String jpql = "SELECT m FROM section04_menu m ORDER BY m.menuCode DESC";
List<Menu> menuList = entityManager.createQuery(jpql, Menu.class)
.setFirstResult(offset) //조회를 시작할 위치(0부터 시작)
.setMaxResults(limit) //조회할 데이터의 수
.getResultList();
//then
assertNotNull(menuList);
menuList.forEach(System.out::println);
}
쿼리 실행 결과를 보면 offset과 limit을 활용하는 문법으로 실행되어 있는데 이는 오라클 12버전 이후 추가된 문법이다.
-> 오라클 11이하 버전에서는 rownum을 이용한 인라인뷰 방식의 쿼리가 동작한다.
..생략..
order by
menu0_.MENU_CODE DESC offset ? rows fetch next ? rows only
-> 오라클 11버전 이후 문법. 그 이전까지는 rownum 사용. 지금도 사용함
그룹함수
JPQL의 그룹합수는 COUNT, MAX, MIN, SUM, AVG로 SQL의 그룹함수와 별반 차이가 없다.
단 몇가지 주의사항이 있다.
1. 그룹함수의 반환 타입은 결과 값이 정수면 Long, 실수면 Double로 반환된다.
2. 값이 없는 상태에서 count를 제외한 그룹 함수는 null이 되고 count만 0이 된다.
따라서 반환 값을 담기 위해 선언하는 변수 타입을 기본자료형으로 하게되면, 조회 결과를 언박싱 할 때 NPE가 발생한다.
(int는 기본자료형, null을 담을 수 없다. notnull을 의미한다고 볼 수 있다. Integer는 객체 타입으로 nullable하다)
3. 그룹 함수의 반환 자료형은 Long or Double 형이기 때문에 Having 절에서 그룹 함수 결과값과 '비교'하기 위한 파라미터 타입은 Long or Double로 해야 한다.
@Test
public void 특정_카테고리의_등록된_메뉴_수_조회() {
//given
int categoryCodeParameter = 4;
//when
String jpql = "SELECT COUNT(m.menuPrice) FROM section05_menu m WHERE m.categoryCode = :categoryCode";
long countOfMenu = entityManager.createQuery(jpql, Long.class)
.setParameter("categoryCode", categoryCodeParameter)
.getSingleResult();
//then
assertTrue(countOfMenu >= 0);
System.out.println(countOfMenu);
}
Wrapper 타입 선언
- 반환 값을 담을 변수의 타입을 기본 자료형으로 하는 경우 Wrapper 타입을 언박싱 하는 과정에서 NPE이 발생하게 되고, 반환 값을 담을 변수를 Wrapper 타입으로 선언해서 null 값이 반환되어도 NPE이 발생하지 않는다.
@Test
public void count를_제외한_다른_그룹함수의_조회결과가_없는_경우_테스트() {
//given
int categoryCodeParameter = 2; //존재하지 않는 값을 넣어본다
//when
String jpql = "SELECT SUM(m.menuPrice) FROM section05_menu m WHERE m.categoryCode = :categoryCode";
//then
//테스트코드(assertThrows)를 작성하면 실행 에러는 발생하지 않는다.
assertThrows(NullPointerException.class, () -> {
long sumOfPrice = entityManager.createQuery(jpql, Long.class)
.setParameter("categoryCode", categoryCodeParameter)
.getSingleResult(); //실행 시 NullPointerException 발생
});
assertDoesNotThrow(() -> {
Long sumOfPrice = entityManager.createQuery(jpql, Long.class)
.setParameter("categoryCode", categoryCodeParameter)
.getSingleResult();
});
}
- 그룹함수의 반환 타입은 Long이므로 비교를 위한 파라미터도 Long 타입을 사용해야 한다.
@Test
public void groupby절과_having절을_사용한_조회_테스트() {
//given
long minPrice = 50000L;
//when
String jpql = "SELECT m.categoryCode, SUM(m.menuPrice)"
+ " FROM section05_menu m"
+ " GROUP BY m.categoryCode"
+ " HAVING SUM(m.menuPrice) >= :minPrice"; //개행 시 꼭 띄어쓰기
List<Object[]> sumPriceOfCategoryList = entityManager.createQuery(jpql, Object[].class)
.setParameter("minPrice", minPrice)
.getResultList();
//then
assertNotNull(sumPriceOfCategoryList);
sumPriceOfCategoryList.forEach(row -> {
for(Object column : row) System.out.print(column + " ");
});
}