DevKim

[JPA] JPQL 문법 정리 본문

JPA

[JPA] JPQL 문법 정리

on_doing 2021. 6. 24. 10:54
728x90

SQL은 DB 테이블을 대상으로 쿼리를 하지만, JPQL은 엔티티 객체를 대상으로 검색하는 객체 지향 쿼리이다.

어차피 결국 JPQL은 SQL로 변환되기 때문이어서 그런지, SQL이랑 문법은 거의 비슷한 것 같다.

String jpql="select m from Member as m where m.username like '%kim%'"
em.createQuery(jpql,Member.class)
            .getResultList();

JPQL은 단순 문자열이므로, 동적 쿼리를 만들거나 할 때는 QueryDSL이 더 유용하다.

QueryDSL은 문자가 아닌 자바코드로 JPQL을 작성할 수 있으므로

컴파일 시점에 문법 오류를 찾을 수 있다는 장점이 있다.

JPQL 문법을 먼저 정리하고, 메뉴얼보고 QueryDSL에 대해서도 정리를 해봐야겠다.

 

실무에선 도대체 어떻게 사용하는건지.. 짬뽕해서 사용하는건지 궁금했는데 마침 비슷한 질문에 대한 답변을 찾았다.

 실무에서는 결국 관계형 데이터베이스를 사용하기 때문에 80:20의 법칙이 존재합니다.
80% 정도는 정말 단순한 쿼리고, 20% 정도는 복잡한 쿼리라는 것이지요.
JPA를 사용하면 80% 정도의 단순한 쿼리들을 매우 편리하게, 나중에 스프링 데이터 JPA와 Querydsl까지 사용하면 거의 작성하지 않아도 될 정도로 정말 편리하고 쉽게 사용할 수 있습니다.
그런데 20% 정도의 복잡한 쿼리들이 남게 되는데,
어느정도는 SQL 툴에서 돌려보고 이걸 JPQL로 작성해야 하는 경우도 있습니다.
그리고 5% 정도의 정말 네이티브 쿼리를 작성해야 하는 경우도 있습니다.
그래서 JPA와 MyBatis나 JdbcTemplate를 함께 사용하는 경우도 있습니다.
(물론 JPA가 중심이고, 어쩔 수 없는 네이티브 쿼리를 사용하는 경우에 추가로 사용합니다.)

[ JPQL 문법 ]

SQL에서 제공하는 문법들은 기본적으로 거의 제공을한다.

update 문과 delete 문은 한번에 여러개를 업데이트할 때하는 벌크 연산이다.

SQL의 집계함수들도 모두 사용할 수 있다.

그러니 물론 GROUP BY,HAVING,ORDER BY 모두 가능하다.


[ TypeQuery & Query ]

TypeQuery반환 타입이 명확할 때 사용하고, Query반환 타입이 명확하지 않을 때 사용한다.

 

이 경우엔 Member 타입을 명시해주었으므로 타입이 명확하고,

TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);

이 경우는 타입을 명시할 수 없다.

Query query = em.createQuery("select m.username,m.age from Member m");

[ 결과 조회 ]

(1) 결과가 하나 이상일 때, 리스트 반환 - 결과가 없으면 빈 리스트 빈환

query.getResultList();

(2) 결과가 정확히 하나일 때 사용해야함 - 결과가 없거나 둘 이상이면 예외 터짐

query.getSingleResult();

* Spring Data JPA 에서는 optional로 반환해주도록 코드가 짜져있음 *


[ 파라미터 바인딩 ] 

.setParameter

Member result = em.createQuery("select m.username from Member m where m.username= :username", Member.class)
                    .setParameter("username", "member1")
                    .getSingleResult();

[ 프로젝션 ]

SELETE 절에 조회할 대상을 지정하는 것 " 뭘 가져올꺼야! "

- 엔티티,임베디드 타입, 스칼라 타입 (숫자,문자 등 기본 데이터 타입) 모두 가능

* DISTINCT 로 중복 제거 가능 *


(1) 엔티티 프로젝션

이렇게 하면  resultList는 모두 영속성 컨텍스트에서 관리하나? - yes! 

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

 

* 연관관계인 애 *

Member와 Team은 다른 테이블이기 때문에 실행하면 자동으로 join쿼리가 나가긴하지만,

모르는 사람이 봤을 땐  join쿼리가 왜 나가는지 예측이 안되니,

join 쿼리 써줘서 명시적으로 써주는게 좋다고한다.

-> 명시적 조인

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

(2) 임베디드 타입 프로젝션

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

(3) 스칼라 타입 프로젝션

username 과 age 두개를 가져오므로, 타입을 명시할 수 없다.

em.createQuery("select distinct m.username,m.age from Member m");

그러면 타입이 두개인데 어떻게 가져와야할까?

 

* 여러 값을 조회하는 방법*

1.Query 타입으로 조회

2.Object[] 타입으로 조회

3. new 명령어로 조회

- 단순 값을 DTO로 바로 조회할 수 있다.

 

MemberDto를 임의로 하나 생성해준 뒤,

@NoArgsConstructor
@AllArgsConstructor
@Getter @Setter
public class MemberDto {

    private String username;
    private int age;
}

타입은 DTO로 설정해주고,

엔티티가 아닌 다른 것들에 대해서는

생성자로 생성하듯이 new 명령어로 패키지명을 포함한 전체 클래스명을 입력해주어야한다.

List<MemberDto> resultList = em.createQuery("select new jpql.MemberDto(m.username,m.age) from Member as m", MemberDto.class)
                    .getResultList();

MemberDto memberDto = resultList.get(0);
System.out.println("memberDto.getUsername() = " + memberDto.getUsername());
System.out.println("memberDto.getAge() = " + memberDto.getAge());

지금은 일단 문자니까 패키지를 다 적어줘야하고,

나중에 querydsl을 사용하면 자바 코드로 작성하기 때문에 패키지명 import해서 사용하면 더 간단해진다고한다.


[페이징]

조회시작 위치 부터 몇개 가져올건지?

*setFirstResult(int 시작위치) : 조회 시작 위치

*setMaxResults(int 조회할 데이터 수) : 조회할 데이터 수

List<Member> resultList = em.createQuery("select m from Member as m order by m.age desc", Member.class)
                    .setFirstResult(1)
                    .setMaxResults(10)
                    .getResultList();

[ 조인 ]

 

*조인 참고*

https://advenoh.tistory.com/23

 

관계형 데이터베이스에서 조인(join)이란?

1.JOIN에 대한 기본 개념 관계형 데이터베이스에서는 중복 데이터를 피하기 위해서 데이터를 쪼개 여러 테이블로 나눠서 저장합니다. 이렇게 분리되어 저장된 데이터에서 원하는 결과를 다시 도

advenoh.tistory.com


(1) 내부 조인

em.createQuery("select m from Member m inner join m.team t",Member.class)

//inner 생략가능

(2) 외부 조인

em.createQuery("select m from Member m left join m.team t",Member.class)

//outer 생략가능

(3) 세타 조인 = 막 조인 ( 교차 조인 )

 

연관관계가 전혀 없는걸 비교해보고 싶을 때!

곱하기로 쭉 나열을 해서 합친 다음에 where문으로 조건에 부합하는 것만 뽑아준다.

 em.createQuery("select m from Member m,Team t where m.username=t.name",Member.class)

(4) ON 절

<01> 조인할 때 미리 조인 대상을 필터링할 수 있다.

<02> 연관관계없는 엔티티를 외부 조인 할 수 있다.

 

<ex 01> 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인하고 싶을 때

em.createQuery("select m,t from Member m left join m.team t on t.name='A' ",Member.class)

<ex 02> 연관관계 없는 엔티티 외부 조인

em.createQuery("select m,t from Member m left join Team t on m.username=t.name ",Member.class) 

[ 서브 쿼리 ]

- 쿼리 안에 또 쿼리를 만드는 것

 

<ex> 나이가 평균보다 많은 회원

em.createQuery("select m from Member m where m.age > (select avg(m2.age) from Member m2) ",Member.class)

** 서브 쿼리 지원 함수 **

 

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

팀 A 소속인 회원

em.createQuery("select m from Member m where exists (select t from m.team as t where t.name='A')",Member.class)

  • ALL (서브쿼리) : 모두 만족하면 참

전체 상품 각각의 재고보다 주문량이 많은 주문들

em.createQuery("select o from Order o where o.orderAmount > ALL(select p.stockAmount from Product p)",Order.class)  

  • ANY/SOME (서브쿼리) : 조건이 하나라도 만족하면 참

어떤 팀이든 팀에 소속된 회원

em.createQuery("select m from Member m where m.team= Any(select t from m.team as t)",Member.class)

  • [NOT] IN (서브쿼리) : 서브쿼리 결과 중 하나라도 같은 것이 있으면 참

 

** 서브 쿼리 한계 **

- WHERE,HAVING,SELECT(하이버네이트 지원) 절에서만 서브 쿼리 사용가능하다.

- FROM 절의 서브쿼리는 현재는 불가능하나, 조인으로 풀 수 있으면 풀어서 해결해야한다고 한다.


[ JPQL 기타 ]

기본적으로 표준 SQL은 모두 지원한다.

(ex). EXISTS,IN,AND,OR,NOT,BETWEEN,LIKE,IS NULL


[ CASE 조건식 ]

1. 기본 CASE 식

- 조건식1이 만족하면 식1 반환, 그게 아니면 식2.. 다 아니면 식3 반환

CASE WHEN 조건식1 THEN 식1
       WHEN 조건식2 THEN 식2
       ELSE 식3 
END 
select
 	case when m.age <= 10 then '학생요금'
 		 when m.age >= 60 then '경로요금'
 		 else '일반요금'
 	end
from Member m

2.단순 CASE 식

- 식1의 값이 식2와 같으면 식3 반환..

CASE 식1
       WHEN 식2 THEN 식3
       WHEN 식4 THEN 식5
       ELSE 식6 
END
select
	case t.name 
 		when '팀A' then '인센티브110%'
 		when '팀B' then '인센티브120%'
 		else '인센티브105%'
 	end
from Team t

 

728x90
Comments