Posts [자바 ORM 표준 JPA 프로그래밍-기본편] JPQL 기본 문법과 쿼리 API
Post
Cancel

[자바 ORM 표준 JPA 프로그래밍-기본편] JPQL 기본 문법과 쿼리 API

본 포스팅은 인프러의 JPA 기본편을 수강하고 정리하는 내용입니다.


JPQL(Java Persistence Query Language) - 기본 문법과 기능

JQPL 소개

  • JPQL은 객체지향 쿼리 언어다.따라서 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
  • JPQL은 SQL을 추상화해서 특정데이터베이스 SQL에 의존하지 않는다.
  • JPQL은 결국 SQL로 변환된다.

image

JPQL 문법

image

  • select m from Member as m where m.age > 18
  • 엔티티와 속성은 대소문자 구분O (Member, age)
  • JPQL 키워드는 대소문자 구분X (SELECT, FROM, where)
  • 엔티티 이름 사용, 테이블 이름이 아님(Member)
  • 별칭은 필수(m) (as는 생략가능)

집합과 정렬

1
2
3
4
5
6
7
select
  COUNT(m), //회원수
  SUM(m.age), //나이 합
  AVG(m.age), //평균 나이
  MAX(m.age), //최대 나이
  MIN(m.age) //최소 나이
from Member m

집합과 정렬

  • GROUP BY, HAVING
  • ORDER BY

TypeQuery, Query

  • TypeQuery: 반환 타입이 명확할 때 사용
  • Query: 반환 타입이 명확하지 않을 때 사용
1
2
3
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);

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

결과 조회 API

  • query.getResultList(): 결과가 하나 이상일 때, 리스트 반환
    • 결과가 없으면 빈 리스트 반환, NPE걱정 안해도됨
  • query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환
    • 결과가 없으면: javax.persistence.NoResultException
    • 둘 이상이면: javax.persistence.NonUniqueResultException
    • 뭔가 결과가 없는데 Exception이 나오면 굉장히 별로다.. try catch로 처리해야되고..
    • 나중에 Sping Data Jpa를 사용하면 결과가 없으면 Null이나 Optional로 반환한다.
    • Spring쪽 코드를 살펴보면 try catch로 잡아주는게 알아서 다 짜여있다.
  • 둘의 차이를 명확하게 알아둘 것

파라미터 바인딩 - 이름 기준, 위치 기준

1
2
3
4
5
6
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); 
// 이처럼 위치기반은 사용하지 말 것, 왜냐하면 중간에 뭐하나 끼어넣으면 순서가 밀려 장애로 이어진다.

일반적으로 체이닝을 활용한다.

1
2
3
4
Member result = em.createQuery("select m from Member as m where m.username = :username", Member.class)
        .setParameter("username", "member1")
        .getSingleResult();
System.out.println("result = " + result.getUsername());

프로젝션

  • SELECT 절에 조회할 대상을 지정하는 것
  • 프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입)
  • SELECT m FROM Member m -> 엔티티 프로젝션
1
2
3
4
5
List<Member> result = em.createQuery("select m from Member as m", Member.class)
                    .getResultList();

Member findMember = result.get(0);
findMember.setAge(20);

엔티티 프로젝션을 하면 결과로 나온 대상들 전부 영속성 컨텍스트에서 관리된다.

  • SELECT m.team FROM Member m -> 엔티티 프로젝션
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<Team> result = em.createQuery("select m.team from Member as m", Team.class)
        .getResultList();

SQL: 
Hibernate: 
    /* select
        m.team 
    from
        Member as m */ select
            team1_.id as id1_3_,
            team1_.name as name2_3_ 
        from
            Member member0_ 
        inner join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id

Member와 Team을 조인하는 쿼리가 나간다. 참고로 조인이라는게 성능에 영향을 줄 수 있는 요소가 많기에 심플하게 가줘야 한다. 위에 코드처럼 JPQL을 작성하면 쿼리를 예측하기 힘들 수 있기 때문에 아래처럼 명시적으로 나타낼 수 있도록 변경해야 한다.

1
2
List<Team> result = em.createQuery("select t.team from Member as m join m.team t", Team.class)
        .getResultList();
  • SELECT m.address FROM Member m -> 임베디드 타입 프로젝션
    • 엔티티에 종속되있기 때문에 어디소속인지 엔티티를 정해줘야한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<Address> result = em.createQuery("select o.address from Order as o", Address.class)
        .getResultList();

SQL:
Hibernate: 
    /* select
        o.address 
    from
        
    Order as o */ select
        order0_.city as col_0_0_,
        order0_.street as col_0_1_,
        order0_.zipcode as col_0_2_ from
            ORDERS order0_
  • SELECT m.username, m.age FROM Member m -> 스칼라 타입 프로젝션
    • 타입이 두 개인데 어떻게 타입을 명시해야 하지? 아래 단락에서 설명해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<Address> result = em.createQuery("select m.username, m.age from Member as m")
        .getResultList();

SQL:
Hibernate: 
    /* select
        m.username,
        m.age 
    from
        Member as m */ select
            member0_.username as col_0_0_,
            member0_.age as col_1_0_ 
        from
            Member member0_
  • DISTINCT로 중복 제거

프로젝션 - 여러 값 조회

  • SELECT m.username, m.age FROM Member m
  • 1)Query 타입으로 조회
1
2
3
4
5
6
7
List resultList = em.createQuery("select m.username, m.age from Member as m")
        .getResultList();

Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
  • 2)Object[] 타입으로 조회
1
2
3
4
5
6
List<Object[]> resultList = em.createQuery("select m.username, m.age from Member as m")
        .getResultList();

Object[] result = resultList.get(0);
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
  • 3)new 명령어로 조회
    • 단순 값을 DTO로 바로 조회 ~~~ ex1. SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m

    ex2. public class MemberDTO {

    private String username; private int age;

    public MemberDTO(String username, int age) { this.username = username; this.age = age; }

    … }

    List 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 = “ + memberDTO.getUsername()); System.out.println(“memberDTO = “ + memberDTO.getAge()); ~~~

    • 문자이기 때문에 패키지 명을 포함한 전체 클래스명 입력해야되는게 단점이다. (패키지명이 길어진다면..)
      • 이런 단점은 QueryDSL쓰면 극복이 된다. 패키지명을 다 import해서 쓸 수 있다.
    • 순서와 타입이 일치하는 생성자 필요
This post is licensed under CC BY 4.0 by the author.

[자바 ORM 표준 JPA 프로그래밍-기본편] 객체지향 쿼리(JPQL) 언어 소개

[자바 ORM 표준 JPA 프로그래밍-기본편] 페이징