JPQL 기본 문법

D A S H B O A R D
D E V E L O P
S E C U R I T Y
 JPQL 문법
 TypeQuery vs Query
 결과 조회 API
 파라미터 바인딩
 프로젝션
 페이징
 조인
 서브쿼리
 타입 표현
 CASE식
Reference

 JPQL 문법

select m from Member as m where m.age > 18
엔티티와 속성은 대소문자 구분O (Member, age)
JPQL 키워드는 대소문자 구분X (SELECT, FROM, where)
엔티티 이름 사용, 테이블 이름이 아님(Member)
별칭은 필수(m) (as는 생략가능)
기본적으로 SQL문과 동일하다!!!
우리가 ANSI SQL이라고 하는 문법들은 다 지원한다고 보면 된다.(COUNT, MIN,MAX 등)
update문 같은 경우는 나중 벌크 연산에서 더 자세하게 할 예정!

 TypeQuery vs Query

TypeQuery

TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
Java
복사
TypeQuery : 반환 타입이 명확할 때 사용
옆에 있는 코드와 같이 type이 Member라는 Entity와 명확히 매칭되는 것처럼, 같은 자료형으로 나올 경우에 사용

Query

Query query = em.createQuery("SELECT m.username, m.age from Member m");
Java
복사
Query : 반환 타입이 명확하지 않을 때 사용
옆에 있는 코드와 같이 type이 String(m.username)과 Int(m.age)와 같이 반환 타입이 여러개일 경우에 사용

 결과 조회 API

query.getResultList()

TypedQuery<Member> query = em.createQuery("select m from Member as m", Member.class); List<Member> resultList = query.getResultList(); for (Member member1 : resultList) { System.out.println("member1 = " + member1); }
Java
복사
query.getResultList(): 결과가 하나 이상일 때, 리스트 반환
결과가 없으면 빈 리스트 반환
그렇기 때문에 NullPoint Execoption에 대해 걱정하지 않아도 된다.

query.getSingleResult()

TypedQuery<Member> query3 = em.createQuery("select m.username, m.age from Member as m where m.username = 'member1'", Member.class); Member singleResult = query3.getSingleResult(); System.out.println("singleResult = " + singleResult);
Java
복사
query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환
결과가 없으면: javax.persistence.NoResultException
둘 이상이면: javax.persistence.NonUniqueResultException
Spring Data JPA의 좋은 기능!!
결과가 없일때 에러가 내는것이 잘못됬다는 논란이 많았다.
Spring Data JPA를 사용할 경우에는 Spring에서 알아서 try catch문으로 잡아주기 때문에 Exception이 터지지 않고 Optional 객체가 반환됨

 파라미터 바인딩

이름 기준

TypeQuery<member> query = em.createQuery("select m from Member as m where m.username = :username", Member.class) query.setParameter("username", usernameParam);
Java
복사
이러한 setParameter를 통해 변수로 파라미터를 설정할 수 있다.
또한 보통 이렇게 변수로 받지않고, Chain형으로 쓴다!!

위치 기준

TypeQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.username=?1 ", Member.class) query.setParameter(1, usernameParam);
Java
복사
얘는 할 수 있는데 쓰지는 말자!!
1과 2라는 위치를 기준으로 들어가고 있는데, 만약 3이 1과 2 중간에 오게 된다면 굉장히 꼬일 수 있기 때문!!
Chain형으로 쓰면 아래와 같다!!
Member singleResult1 = em.createQuery("select m from Member as m where m.username = :username", Member.class) .setParameter("username", "member1") .getSingleResult(); System.out.println("singleResult1 = " + singleResult1);
Java
복사

 프로젝션

SELECT 절에 조회할 대상을 지정하는 것
프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입)
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로 중복 제거
입베디드 타입 프로젝션?
Value Type 중에 Table을 @Embeded, @Embedable 을 통해 만드는 것들이 있는데, 이를 조회할 때 임베디드 타입 프로젝션이라고 한다.

쿼리 예제

특징이 있는 몇개의 쿼리만 알아보겠다!!

SELECT m.team FROM Member m

em.createQuery("select m.team from Member m ", Team.class)
Java
복사
해당 쿼리를 조회하면 Join 쿼리가 나가게 된다.
엄청 편해보이지만 join은 튜닝할 수 있는 요소가 굉장히 많기 때문에, 유지보수 관점에서 JPQL은 SQL과 비슷하게 작성해야 한다.
select t from Member m join m.team t
추후 중급 문법 경로 표현식에서 자세히 설명!

SELECT m.address FROM Member m

em.createQuery("select o.address from Order o ", Address.class)
Java
복사
임베디드 타입의 경우 Entity가 아니기 때문에 임베디드 타입으로 바로 조회하는 것이 불가능하다.
그렇기 때문에 엔티티 위임해준 엔티티로부터 받아와야 한다.

SELECT m.username, m.age FROM Member m

em.createQuery("select m.username, m.age from Member m ")
Java
복사
여기서 고민이 생기는데, username과 age는 각각 다른 타입인데 어떤 타입으로 가지고 와야 할까?
1.
Query 타입으로 조회 →  TypeQuery vs Query에서 말한것과 같이 타입이 불분명할 때 사용하는 쿼리 타입
2.
Object[] 타입으로 조회 → 형변환하는 귀찮음이 존재
List resultList = em.createQuery("select m.username, m.age from Member m ") .getResultList(); Object o = resultList.get(0); Object[] result = (Object[]) o;
Java
복사
3.
new 명령어로 조회
단순값을 DTO로 바로조회 → 얘는 username과 age를 가지고 있는 Class를 하나 생성해 해당 자료형으로 받아오는 것
em.createQuery("SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m",MemberDTO.class)
Java
복사
패키지명을 포함한 전체 클래스명 입력
순서와 타입이 일치하는 생성자 필요

 페이징

JPA는 페이징을 다음 두 API로 추상화
setFirstResult(int startPosition) : 조회 시작 위치(0부터 시작)
setMaxResults(int maxResult) : 조회할 데이터 수
String jpql = "select m from Member m order by m.name desc"; List<Member> resultList = em.createQuery(jpql, Member.class) .setFirstResult(10) .setMaxResults(20) .getResultList();
Java
복사

 조인

내부 조인: SELECT m FROM Member m [INNER] JOIN m.team t
외부 조인: SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
세타 조인: select count(m) from Member m, Team t where m.username = t.name → 세타 조인은 막 조인으로 연관관계가 전혀 없는 엔티티들을 엮어서 조회

On 절

ON절을 활용한 조인(JPA 2.1부터 지원)
1.
조인 대상 필터링 SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
2.
연관관계 없는 엔티티 외부 조인(하이버네이트 5.1부터) SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name

 서브쿼리

[NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참
{ALL | ANY | SOME} (subquery)
ALL : 모두 만족하면 참
[NOT] IN (subquery): 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참

예시

팀 A 소속 회원 select m from Member m where exists (select t from m.team t where t.name = ‘팀A')
전체 상품 각각의 재고보다 주문량이 많은 주문들 select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p)
어떤 팀이든 팀에 소속된 회원 select m from Member m where m.team = ANY (select t from Team t)

 타입 표현

문자: ‘HELLO’, ‘She’’s’숫자: 10L(Long), 10D(Double), 10F(Float)
Boolean: TRUE, FALSE
ENUM: jpabook.MemberType.Admin (패키지명 포함) → 파라미터 바인딩을 이용하면 jpabook까지는 이용하지 않아도 됨
엔티티 타입: TYPE(m) = Member (상속 관계에서 사용)

 CASE 식

기본 CASE 식
select case when m.age <= 10 then '학생요금' when m.age >= 60 then '경로요금' else '일반요금' end from Member m
Java
복사
단순 CASE 식
select case t.name when '팀A' then '인센티브110%' when '팀B' then '인센티브120%' else '인센티브105%' end from Team t
Java
복사
조건 CASE 식
COALESCE: 하나씩 조회해서 null이 아니면 반환
사용자 이름이 없으면 이름 없는 회원을 반환 elect coalesce(m.username,'이름 없는 회원') from Member m
NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
사용자 이름이 ‘관리자’면 null을 반환하고 나머지는 본인의 이름을 반환 select NULLIF(m.username, '관리자') from Member m

 함수

기본 함수
CONCAT
SUBSTRING
TRIM
LOWER, UPPER
LENGTH
LOCATE
ABS, SQRT, MOD• SIZE, INDEX(JPA 용도)
사용자 정의 함수 호출허
하이버네이트는 사용전 방언에 추가해야 한다.
사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다. → 이후, 세팅 파일에서 파일 위치를 새로 지정해주어야 한다!! select function('group_concat', i.name) from Item i