ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA - JPQL 기본 문법과 기능
    Spring/JPA 2022. 3. 11. 14:56
    반응형

    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

     

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

     

     

    반응형

    댓글

Designed by Tistory.