JPQL(Java Persistence Query Language)
- JPQL은 객체지향 쿼리 언어다. 따라서 테이블을 대상으로 쿼리 하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
- JPQL은 SQL을 추상화해서 특정데이터베이스 SQL에 의존하지 않는다.
- JPQL은 결국 SQL로 변환된다.
JPQL 문법
- select u from User as u where u.age > 10
엔티티와 속성은 대소문자 구분한다.
User 는 엔티티 age는 속성 - JPQL 키워드는 대소문자 구분하지 않아도 된다. (SELECT, FROM, where)
테이블의 이름이 아닌 엔티티 이름 사용한다.
tbUser 가 아닌 User. - 문법상 별칭은 필수이다. (as는 생략가능)
TypeQuery, Query
- TypeQuery : 반환 타입이 명확할 때 사용
- Query : 반환 타입이 명확하지 않을 때 사용
User user = new User();
user.setName("coco");
user.setAge(10);
entityManager.persist(user);
TypedQuery<User> userTypedQuery = entityManager.createQuery("select u from User as u", User.class);
System.out.println(userTypedQuery.getResultList().get(0).getClass());
Query query = entityManager.createQuery("select u from User as u");
System.out.println(query.getResultList().get(0).getClass());
TypedQuery 시 지정해준 클래스 타입으로 가져온 것을 확인할 수 있다.
물론, Query 시에도 맵핑 정보를 가지고 쿼리 결과의 클래스 타입인지 알 수 있다.
Query는 다음과 같이 가져오는 속성의 타입을 지정할 수 없을 경우에 사용할 수 있다.
Query query2 = entityManager.createQuery("select u.id, u.name from User as u");
System.out.println(query2.getResultList().get(0).getClass());
결과 조회 API
- query.getResultList() : 결과가 하나 이상일 때, 리스트 반환
- query.getSingleResult() : 결과가 정확히 하나, 단일 객체 반환
query.getSingleResult() 사용 예시
ypedQuery<User> userTypedQuery = entityManager.createQuery("select u from User as u where u.id = 1", User.class);
System.out.println(userTypedQuery.getSingleResult().toString());
query.getResultList() 의 경우 결과가 없으면 빈 리스트 반환한다.
그러나, query.getSingleResult() 의 경우 결과가 없으면 javax.persistence.NoResultException를
결과가 둘 이상이면 javax.persistence.NonUniqueResultException 를 발생시킨다.
TypedQuery<User> userTypedQuery2 = entityManager.createQuery("select u from User as u where u.id = 2", User.class);
try {
System.out.println(userTypedQuery2.getSingleResult().toString());
} catch (NoResultException e) {
System.out.println("result is null");
}
Spring Data JPA 에서는 결과가 없으면 try-catch 로 optional 또는 결과가 없으면 null 을 반화 해준다.
파라미터 바인딩
User coco = entityManager.createQuery("select u from User as u where u.name = :userName", User.class)
.setParameter("userName", "coco")
.getSingleResult();
System.out.println(coco.toString());
프로젝션
프로젝션이란 SELECT 절에 조회할 대상을 지정할 수 있는 기능이다.
프로젝션으로 데이터를 가져오면 가져온 모든 엔티티는 영속성 컨택스트에서 관리된다.
프로젝션 대상
- 엔티티
SELECT u FROM User u
SELECT u.team FROM User u - 임베디드 타입
SELECT u.address FROM User u - 스칼라 타입(숫자, 문자등 기본 데이터 타입)
SELECT u.name, u.age FROM User u
🔎 엔티티 프로젝션
Team resultTeam = entityManager.createQuery("select u.team from User as u", Team.class).getSingleResult();
System.out.println(resultTeam.toString());
연관관계의 엔티티를 가져올 경우, 내부적으로 JOIN 쿼리가 발생한다. (묵시적 조인)
쿼리에서 JOIN 쿼리가 발생할 것을 확인할 수 있도록, 아래와 같이 쓰는 것이 좋다. (명시적 조인)
Team resultTeam = entityManager.createQuery("select t from User as u join u.team t", Team.class).getSingleResult();
🔎 엔티티 프로젝션
Address resultAddress = entityManager.createQuery("select u.address from User as u", Address.class).getSingleResult();
System.out.println(resultAddress.toString());
🔎 스칼라 타입 프로젝션
Object[] 로 타입 변환 조회
List<Object[]> resultList = entityManager.createQuery("select distinct u.name, u.age from User as u").getResultList();
resultList.stream().forEach(o -> System.out.println("name = " + o[0] + " age = " + o[1]));
new 명령어로 조회
List<UserDTO> resultList = entityManager.createQuery("select new com.juns.playground.dto.UserDTO(u.name, u.age) from User as u", UserDTO.class).getResultList();
resultList.stream().forEach(dto -> System.out.println("name = " + dto.getName() + " age = " + dto.getAge()));
-
단순 값을 DTO로 바로 조회 할 수 있다.
select new com.juns.playground.dto.UserDTO(u.name, u.age) from User as u -
패키지명을 포함한 전체 클래스명 입력해야 한다.
QueryDSL 에서 극복 가능 -
순서와 타입이 일치하는 생성자 필요하다.
페이징 API
JPA는 페이징을 다음 두 API로 추상화되어 있어, 두 API 만 설정해주면 된다.
- setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
- setMaxResults(int maxResult) : 조회할 데이터 수
List<User> pagingList = entityManager.createQuery("select u from User as u order by u.id desc ", User.class)
.setFirstResult(0)
.setMaxResults(10)
.getResultList();
pagingList.stream().forEach(resultUser -> System.out.println(resultUser.toString())); .setFirstResult(0)
조인
- 내부 조인
SELECT u FROM User u [INNER] JOIN u.team t - 외부 조인
SELECT u FROM User u LEFT [OUTER] JOIN u.team t - 세타 조인 (cross join)
select count(u) from User u, Team t where u.username = t.name
✔︎JPA 2.1부터 ON절을 활용한 조인을 지원한다.
- 조인 대상을 필터링할 수 있고
- 연관관계 없는 엔티티도 외부 조인이 가능하다. (하이버네이트 5.1부터)
조인 대상을 필터링
List<User> joinQueryList = entityManager.createQuery("select u from User as u left join u.team as t on t.name = 'juns team'", User.class).getResultList();
joinQueryList.stream().forEach(resultUser -> System.out.println(resultUser.toString()));
서브 쿼리
- 나이가 평균보다 많은 회원
select u from User u where u.age > (select avg(u2.age) from User u2)
=> 새로운 u2를 정의하여 성능 개선 - 한 건이라도 주문한 고객
select u from User u where (select count(o) from Order o where u = o.user) > 0
서브 쿼리 지원 함수
✔︎ [NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참 {ALL | ANY | SOME} (subquery)
- EXISTS
팀A 소속인 회원
select u from User u where exists (select t from u.team t where t.name = ‘juns team') - ALL 모두 만족하면 참
전체 상품 각각의 재고보다 주문량이 많은 주문들
select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p) - ANY, SOME: 같은 의미, 조건을 하나라도 만족하면 참
어떤 팀이든 팀에 소속된 회원
select u from User u where u.team = ANY (select t from Team t)
✔︎ [NOT] IN (subquery): 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
JPA 서브 쿼리 한계
- JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
- SELECT 절도 가능(하이버네이트에서 지원)
- FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
=> 조인으로 풀 수 있으면 풀어서 해결
JPQL 타입 표현
- 문자: 'JPA'
- Boolean: TRUE, FALSE
- 숫자: 10L(Long), 10D(Double), 10F(Float)
- ENUM의 경우 패키지명 포함하여 엔티티 타입을 지정하거나 파라미터 표현식으로
- 엔티티 타입 (상속 관계에서 사용)
TYPE(m) = Member
세팅한 DiscriminatorValue (DTYPE)로 검색
List<User> res = entityManager.createQuery("select u from User as u where u.type = com.juns.playground.model.enumeration.UserType.Admin", User.class).getResultList();
OR
List<User> res = entityManager.createQuery("select u from User as u where u.type = :type", User.class)
.setParameter("type", UserType.Admin)
.getResultList();
SQL과 문법이 같은 식
- EXISTS, IN
- AND, OR, NOT
- =, >, >=, <, <=, <> BETWEEN, LIKE, IS NULL
조건식 - CASE 식
- 기본 CASE 식
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m - 단순 CASE 식
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t - COALESCE: 하나씩 조회해서 null이 아니면 반환
사용자 이름이 없으면 이름 없는 회원을 반환
select coalesce(m.username,'이름 없는 회원') from Member m - NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
사용자 이름이 ‘관리자’면 null을 반환하고 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m
JPQL 기본 함수
- CONCAT
- SUBSTRING
- TRIM
- LOWER, UPPER
- LENGTH
- LOCATE
- ABS, SQRT, MOD
- SIZE, INDEX(JPA 용도)
사용자 정의 함수 호출
- 하이버네이트는 사용전 방언에 추가해야 한다.
사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다.
select function('group_concat', i.name) from Item i
'Spring > JPA & Hibernate' 카테고리의 다른 글
Spring Data JPA - H2 연동 (0) | 2020.12.24 |
---|---|
JPQL(Java Persistence Query Language) - 2 (0) | 2020.12.22 |
JPA 쿼리 (0) | 2020.12.20 |
연관관계 관리 (즉시 로딩과 지연 로딩) (0) | 2020.12.18 |
Transaction (0) | 2020.12.15 |