Spring/JPA

JPA - JPQL 기본 문법과 기능

jddng 2022. 3. 11. 14:56
728x90
반응형

JPQL 기본 문법과 기능

 


 

JPA가 지원하는 다양한 쿼리

 

JPQL

 

  • 검색을 할 때 테이블이 아닌 엔티티 객체를 대상으로 검색
  • 필요한 데이터만 DB에서 불러오기 위해 검색 조건이 포함된 SQL
  • SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어(특정 DB SQL에 의존 X)
  • SQL과 유사하며 SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원
  • JPQL은 엔티티 객체를 대상으로 쿼리를 작성한다.

 

//검색
 String jpql = "select m from Member m where m.age > 18"; 
 List<Member> result = em.createQuery(jpql, Member.class)
 		.getResultList();
실행된 SQL 
select 
    m.id as id, 
    m.age as age, 
    m.USERNAME as USERNAME, 
    m.TEAM_ID as TEAM_ID 
from 
    Member m 
where 
    m.age>18

 

Criteria

 

  • 문자가 아닌 자바코드로 JPQL을 작성하는 방법
  • JPQL 빌더 역할
  • JPA 공식 기능
  • 너무 복하고 실용성이 없어서 QueryDSL을 사용하는 것을 권장

 

//Criteria 사용 준비
CriteriaBuilder cb = em.getCriteriaBuilder(); 
CriteriaQuery<Member> query = cb.createQuery(Member.class); 

//루트 클래스 (조회를 시작할 클래스)
Root<Member> m = query.from(Member.class); 

//쿼리 생성 CriteriaQuery<Member> cq = 
query.select(m).where(cb.equal(m.get("username"), “kim”)); 
List<Member> resultList = em.createQuery(cq).getResultList();

 

QueryDSL

 

  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있다.
  • JPQL 빌더 역할
  • 컴파일 시점에 문법 오류를 찾을 수 있다.
  • 동적 쿼리 작성이 편리하다
  • 단순하고 쉽다
  • 실무에서 사용 권장!

 

//JPQL 
//select m from Member m where m.age > 18
JPAFactoryQuery query = new JPAQueryFactory(em);
QMember m = QMember.member; 

List<Member> list = 
    query.selectFrom(m)
        .where(m.age.gt(18)) 
        .orderBy(m.name.desc())
        .fetch();

 

JPQL 기본 문법

 

  • 엔티티와 속성은 대소문자 구분을 해야 한다. (Member, age)
  • JPQL 키워드는 대소문자 구분을 하지 않아도 된다. (SELECT, select)
  • 테이블 이름이 아닌 엔티티 이름을 사용한다(예시 : Member)
  • 별칭은 필수(as는 생략가능)

 


 

집합과 정렬

 

  • COUNT, SUM, AVG, MAX, MIN 등 사용 가능
  • GROUP BY, HAVING, ORDER BY 사용 가능

 

select
    COUNT(m), //회원수
    SUM(m.age), //나이 합
    AVG(m.age), //평균 나이
    MAX(m.age), //최대 나이
    MIN(m.age) //최소 나이
from Member m

 

반환 타입

 

  • TypeQuery : 반환 타입이 명확할 때 사용
  • Query : 반환 타입이 명확하지 않을 때 사용

 

TypedQuery<Member> query = 
         em.createQuery("SELECT m FROM Member m", Member.class);
TypedQuery<String> query = 
         em.createQuery("SELECT m.username FROM Member m", String.class);
         
Query query = 
         em.createQuery("SELECT m.username, m.age from Member m");

 

결과 조회

 

  • query.getResultList() : 결과가 하나 이상일 때 리스트 반환
              - 결과가 없을 시 : 빈 리스트 반환
  • query.getSingleResult() : 결과가 정확히 하나인 단일 객체 반환
              - 결과가 없을 시 : javax.persistence.NoResultException 예외 발생
              - 결과가 둘 이상 : javax.persistence.NonUniqueResultException 예외 발생

 

List<Member> members = 
         em.createQuery("SELECT m FROM Member m", Member.class)
         .getResultList();
List<String> usernames = 
         em.createQuery("SELECT m.username FROM Member m", String.class)
         .getResultList();
         
Member member = 
         em.createQuery("SELECT m from Member m.username = :username", Member.class)
         .setParameter("username", usernameParam)
         .getSingleResult;

 

파라미터 바인딩

 

  • 이름 기준 : 파라미터 명을 기준으로 바인딩
  • 위치 기준 : 파라미터 위치 기준으로 바인딩, 사용 권장 X

 

//이름 기준
Member member = 
         em.createQuery("SELECT m from Member m.username = :username", Member.class)
         .setParameter("username", usernameParam)
         .getSingleResult;
         
//위치 기준   
Member member = 
         em.createQuery("SELECT m from Member m.username = ?1", Member.class)
         .setParameter(1, usernameParam)
         .getSingleResult;

 

프로젝션(SELECT)

 

  • SELECT 절에 조회할 대상을 지정하는 것
  • 대상 : 엔티티, 임베디드 타입, 스칼라 타입(기본 데이터 타입)
  • DISTINCT으로 중복을 제거할 수 있다.

 

-- 엔티티 프로젝션
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 -> 스칼라 타입 프로젝션

 

프로젝션(SELECT) - 여러 값 조회

 

  • Query 타입으로 조회
  • Object[] 타입으로 조회
  • new 명령어로 조회
            - 단순 값을 DTO로 바로 조회할 수 있다.
            - 패키지 명을 포함한 전체 클래스 명 입력
            - 순서와 타입이 일치하는 생성자 필요
            - SELECT new 패키지명.클래스명(파라미터 명) FROM Member m

 

List<UserDTO> result = 
	em.createQuery(
    	"SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m", UserDTO.class)

 

페이징 API

 

  • JPA는 페이징을 다음 두 API로 추상화되어 있다.
  • setFirstResult(int startPosition) : 조회 시작 위치(0부터 시작)
  • setMaxResults(int maxResult) : 조회할 데이터 수
  • DB에 맞게 페이징 SQL을 만들어 준다.

 

//페이징 쿼리
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();

 

-- 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 ?, ?
 
 -- 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_ > ?

 

조인

 

  • 내부 조인
        - 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절을 활용한 조인

 

  • 조인 대상 필터링
  • 연관관계 없는 엔티티도 외부 조인이 가능

 

-- JPQL 조인 대상 필터링
SELECT 
    m, t 
FROM 
    Member m LEFT JOIN m.team t 
    on t.name = 'A' 

-- SQL 조인 대상 필터링
SELECT 
    m.*, t.* 
FROM 
    Member m LEFT JOIN Team t 
    ON m.TEAM_ID=t.id and t.name='A'
-- JPQL 외부 조인
SELECT 
    m, t 
FROM
    Member m LEFT JOIN Team t 
    on m.username = t.name

-- SQL 외부 조인
SELECT 
    m.*, t.* 
FROM 
    Member m LEFT JOIN Team t 
    ON m.username = t.name

 

서브 쿼리

 

  • [NOT] EXISTS (subquery) : 서브쿼리에 결가가 존재하면 참
            - { ALL | ANY | SOME } (subquery)
            - ALL 모두 만족하면 참
            - ANY, SOME : 같은 의미, 조건이 하나라도 만족하면 참
  • [NOT] IN (subquery) : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
  • WHERE, HAVING, SELECT 절에서 서브 쿼리 사용이 가능하다
  • FROM 절의 서브 쿼리는 현재 JPQL에서 불가능하다

 

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

 

JPQL 타입 표현

 

  • 문자 : 'hello', 'spring'
  • 숫자 : 10L(Long), 10D(Double), 10F(Float)
  • Boolean : TRUE, FALSE
  • ENUM : jpabook.MemberType.ADMIN (패키지명 포함해야한다.)
              (이 방법은 패키지명까지 넣어줘야 하므로 파라미터 바인딩으로 하면 편하다)
  • 엔티티 타입 : TYPE(m) = Member (상속 관계에서 사용)
              ex) SELECT i FROM Item i WHERE type(i) = BOOK

 

조건식 - CASE

 

  • CASE 조건식
select
    case when m.age <= 10 then '학생요금'
        when m.age >= 60 then '경로요금'
        else '일반요금'
    end
from Member m

select
    case t.name 
        when '팀A' then '인센티브110%'
        when '팀B' then '인센티브120%'
        else '인센티브105%'
    end
from Team t

 

  • COALESCE : 하나씩 조회해서 NULL이 아니면 반환
  • NULLIF : 두 값이 같으면 NULL 반환, 다르면 첫번째 값 반환
select coalesce(m.username,'이름 없는 회원') from Member m

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

 

JPQL 기본 함수

 

  • CONCAT
  • SUBSTRING
  • TRIM
  • LOWER, UPPER
  • LENGTH
  • LOCATE
  • ABS, SQRT, MOD
  • SIZE, INDEX

 

이외에도 사용자 정의 함수를 만들어 등록해주고 사용하는 방법이 있지만 지금은 내가 쓸일이 없을 것 같애서 생략...

 

 

728x90
반응형