티스토리 뷰

ORM/JPA

[JPA] 8. JPQL(1)

mandykr 2022. 3. 29. 13:22

목차

1. JPQL

2. 기본 문법과 쿼리 API

3. 프로젝션

4. 페이징

5. 조인

6. 서브 쿼리

7. JPQL 타입 표현

8. 조건식 - CASE 식

9. JPQL 함수

 

 

 

1. JPQL

JPA를 사용하면 엔티티 객체를 중심으로 개발을 진행하는데

EntityManager.find()나 객체 그래프 탐색(a.get())으로 조회가 어려운

검색 쿼리를 작성할 때 JPQL을 사용한다.

String jpql = "select m From Member m where m.name like ‘%hello%'";
List<Member> result = em.createQuery(jpql, Member.class)
        .getResultList();

 

  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
  • SQL과 문법 유사, SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원
  • JPQL은 엔티티 객체를 대상으로 쿼리
  • JPQL은 결국 SQL로 변환됨

SQL을 추상화해서 제공하기 때문에 특정 데이터베이스 SQL에 의존하지 않는다.

JPQL은 SQL을 문자열 타입으로 작성하기 때문에 컴파일 과정에서

문법에 대한 체크가 이루어 지지 않는다.

따라서 QueryDSL 라이브러리를 함께 사용하도록 한다.

 


 

2. 기본 문법과 쿼리 API

select m from Member as m where m.age > 18

JPQL은 엔티티를 대상으로 쿼리하기 때문에 엔티티와 속성에 대한 대소문자를 구별한다.

(Member, age)

 

JPQL 키워드는 대소문자를 구분하지 않는다.

(select, from, where)

 

TypeQuery, Query

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(): 결과가 하나 이상일 때, 리스트 반환

결과가 없으면 빈 리스트 반환

List<Member> result = em.createQuery("select m from Member m", Member.class)
        .getResultList();

 

query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환

결과가 없으면: javax.persistence.NoResultException

둘 이상이면: javax.persistence.NonUniqueResultException

Member result = em.createQuery("select m from Member m", Member.class)
        .getSingleResult();

 

파라미터 바인딩

이름 기준

SELECT m FROM Member m where m.username=:username
query.setParameter("username", usernameParam);

 

위치 기준

SELECT m FROM Member m where m.username=?1
query.setParameter(1, usernameParam);

 

유지보수를 위해 위치 기준보다는 이름 기준을 사용하자.

 


 

3. 프로젝션

createQuery()의 파라미터로 select 절에 조회할 대상을 지정한다.

프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입)

 

엔티티 프로젝션

List<Member> result = em.createQuery("select m from Member m", Member.class)
        .getResultList();

Member 객체로 반환값을 매핑한다.

 

엔티티 프로젝션 - 조인

연관 관계 사이에서 엔티티 프로젝션을 사용할 때는 조인이 발생한다.

 

묵시적 조인

List<Team> result = em.createQuery("select m.team from Member m", Team.class)
        .getResultList();

 

명시적 조인

List<Team> result = em.createQuery("select t from Member m join m.team t", Team.class)
        .getResultList();

 

실행 결과를 예측 가능하도록 명시적 조인을 사용해서 조인이 발생함을 명확히 명시하자.

 

 

임베디드 타입 프로젝션

List<Address> result = em.createQuery("select o.address from Orders o", Address.class)
        .getResultList();

 

 

프로젝션 - 여러 값 조회

Query 타입으로 조회

List results = em.createQuery("select m.name, m.age from Member m")
        .getResultList();

 

Object[] 타입으로 조회

List<Object[]> results = em.createQuery("select m.name, m.age from Member m")
        .getResultList();

 

new 명령어로 조회

List<MemberDto> results = 
        em.createQuery("select jpabook.jpql.MemberDTO(m.username, m.age) from Member m", MemberDto.class)
        .getResultList();

패키지 명을 포함한 전체 클래스명 입력.

순서와 타입이 일치하는 생성자가 필요하다.

 


 

4. 페이징

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();

페이징 API - 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.NAME DESC LIMIT ?, ?

 

페이징 API - Oracle 방언

SELECT * FROM
	( SELECT ROW_.*, ROWNUM ROWNUM_
	FROM
		( 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.NAME
        ) ROW_
    WHERE ROWNUM <= ?
	)
WHERE ROWNUM_ > ?

 


 

5. 조인

1) 조인 기본 문법

엔티티간의 연관관계를 통해 조인 SQL을 만든다.

( Member m join m.team t )

 

내부 조인

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

 

2) ON 절을 활용한 조인

조인 대상 필터링

ON 절을 추가해 조인 대상을 필터링 한다.

예) 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인

SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'

SQL의 ON 절의 조인 조건으로 team.name = 'A' 가 추가된다.

 

 

연관관계 없는 엔티티 외부 조인

예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인

SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name

SQL ON 절에 id 조건이 빠지고 member.username = team.name 조건이 추가된다.

 


 

6. 서브 쿼리

1) 서브 쿼리 기본 문법

예) 나이가 평균보다 많은 회원

select m from Member m
where m.age > (select avg(m2.age) from Member m2)

 

예) 한 건이라도 주문한 고객

select m from Member m
where (select count(o) from Order o where m = o.member) > 0

 

2) 서브 쿼리 지원 함수

[NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참

  • {ALL | ANY | SOME} (subquery)
  • ALL 모두 만족하면 참
  • ANY, SOME: 같은 의미, 조건을 하나라도 만족하면 참

[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)

 

3) JPA 서브 쿼리 한계

  • JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
  • SELECT 절도 가능(하이버네이트에서 지원)
  • FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
    (조인 > 쿼리 분할 > 네이티브 쿼리 순서로 풀 수 있으면 풀어서 해결)

 

7. JPQL 타입 표현

  • 문자: ‘HELLO’, ‘She’’s’
  • 숫자: 10L(Long), 10D(Double), 10F(Float)
  • Boolean: TRUE, FALSE
  • ENUM: jpabook.MemberType.Admin (패키지명 포함)
  • 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용) 상속관계 매핑 조인전략 참고

 

JPQL 기타 문법

  • EXISTS, IN
  • AND, OR, NOT
  • =, >, >=, <, <=, <>
  • BETWEEN, LIKE, IS NULL

 


 

8. 조건식

1) 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

 

2) COALESCE, NULLIF

COALESCE

하나씩 조회해서 null이 아니면 반환

예) 사용자 이름이 없으면 이름 없는 회원을 반환

select coalesce(m.username,'이름 없는 회원') from Member m

 

NULLIF

두 값이 같으면 null 반환, 다르면 첫번째 값 반환

예) 사용자 이름이 ‘관리자’면 null을 반환하고 나머지는 본인의 이름을 반환

select NULLIF(m.username, '관리자') from Member m

 


 

9. JPQL 함수

1) JPQL 기본 함수

  • CONCAT
  • SUBSTRING
  • TRIM
  • LOWER, UPPER
  • LENGTH
  • LOCATE
  • ABS, SQRT, MOD
  • SIZE, INDEX(JPA 용도)
select size(t.members) from Team t

 

2) 사용자 정의 함수

사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다.

H2 데이터베이스 경우 H2Dialect 클래스에 JPQL 함수를 정의해 놓았다.

 

[MyH2Dialect]

public class MyH2Dialect extends H2Dialect {

public MyH2Dialect() {
    registerFunction("group_concat", 
               new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}

[persistence.xml]

 

<!--<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>-->
<property name="hibernate.dialect" value="jpql.MyH2Dialect"/>
select function('group_concat', i.name) from Item i

 

 

 

 

 

 

 

 

출처

https://www.inflearn.com/course/ORM-JPA-Basic 자바 ORM 표준 JPA 프로그래밍 - 기본편(김영한)

 

 

728x90

'ORM > JPA' 카테고리의 다른 글

[JPA] 조회 성능 최적화  (0) 2023.04.28
[JPA] 8. JPQL(2)  (0) 2022.04.14
[JPA] 7. 값 타입  (0) 2022.03.02
[JPA] 6. 프록시와 연관관계 관리  (0) 2021.12.14
[JPA] 5. 고급 매핑  (0) 2021.12.01