JPA (11) JPQL문법, Projection, Pagination
20 Mar 2022김영한님의 자바 ORM 표준 JPA 프로그래밍 강의 정리
JPQL 문법
select m from Member as m where m.age > 18
- 엔티티와 속성은 대소문자 구분
- JPQL 키워드는 대소문자 구문하지 않음
- 테이블 이름이 아닌 엔티티 이름을 사용(
@Entity(name = "member")
) - 별칭 필수(as 생략 가능)
-
집계 함수 가능
select COUNT(m), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age) from Member m
- 집합 & 정렬
- GROUP BY, HAVING
- ORDER BY
- TypeQuery
- 반환 타입이 명확할 때 사용
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
- Query
- 반환 타입이 명확하지 않을 때 사용
Query query = em.createQuery("SELECT m.username, m.age FROM Member m");
- 결과 조회 API
query.getResultList()
- 결과가 하나 이상일 때
- 리스트를 반환하고 결과가 없으면 빈 리스트 반환
- NullPointerException에 안전
query.getSignleResult()
- 결과가 하나일 때
- 단일 객체 반환
- 결과가 없으면
javax.persistence.NoResultException
- 결과가 둘 이상이면
java.persistence.NonUniqueResultException
- 파라미터 바인딩
- 이름 기준
em.createQuery("select m from Member m where m.username = :username", Member.class) .setParameter("username", "John") .getSingleResult();
- 위치 기준
em.createQuery("select m from Member m where m.username = ?username", Member.class) .setParameter(1, "John") .getSingleResult();
- 위치 기준은 파라미터가 새로 추가될 경우 에러가 발생할 가능성이 크므로 이름 기준으로 바인딩
Projection
- SELECT 절에 조회할 대상을 지정하는 것
- 대상
- Entity
- Embedded type
- Scalar type(숫자, 문자 등 기본 데이터 타입)
SELECT m FROM Member m // 엔티티 프로젝션 SELECT m.team FROM Member m // 엔티티 프로젝션 SELECT m.address FROM Member m // 임베디드 타입 프로젝션 SELECT m.username, m.age FROM Member m // 스칼라 타입 프로젝션
-
DISTINCT
로 중복 제거 - JOIN
- SQL과 비슷하게 JPQL을 짜는 것이 좋음
// 실제 쿼리는 inner join으로 team 데이터를 가져옴 em.createQuery("select m.team from Member m", Team.class).getResultList(); // 실제 쿼리와 맞춰서 join으로 jpql을 짜는 것이 좋음 em.createQuery("select t from Member m join m.team t", Team.class).getResultList();
- 프로젝션을 하면 대상이 모두 영속성 컨텍스트에 의해 관리됨
- 여러 값 조회
- Query 타입으로 조회
List list = em.createQuery("select m.username, m.age from Member m") .getResultList(); Object o = list.get(0); Object[] result = (Object[]) o; System.out.println("username: " + result[0] + " age: " + result[1]);
- Object[] 타입으로 조회
List<Object[]> list = em.createQuery("select m.username, m.age from Member m") .getResultList(); Object[] result = list.get(0); System.out.println("username: " + result[0] + " age: " + result[1]);
- new 명령어로 단순 값을 DTO로 조회
- 패키지 명을 포함한 전체 클래스 명을 입력하며 순서와 타입이 일치하는 생성자 필요
// MemberDTO package entity; public class MemberDTO { private String username; private int age; public MemberDTO(String username, int age) { this.username = username; this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } // main List<MemberDTO> list = em.createQuery("select new entity.MemberDTO(m.username, m.age) from Member m", MemberDTO.class) .getResultList(); System.out.println("username: " + list.get(0).getUsername()); System.out.println("age: " + list.get(0).getAge());
Pagination
- 2개의 API로 추상화
setFirstResult(int startPsotion)
: 조회 시작 위치(0부터 시작)setMaxResults(int maxResult)
: 조회할 데이터 수
- 각 DB마다 맞는 쿼리가 실행됨(ex. Oracle의 rownum)
em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(1)
.setMaxResults(5)
.getResultList();
-- MySQL
SELECT m.id as id, m.age as age, m.team_id as team_id, m.name as name
FROM member m ORDER BY m.age DESC LIMIT ?, ?