LostCatBox

(JPA) JPA-Basic-CH09(객체지향 쿼리 언어)

Word count: 1.6kReading time: 10 min
2023/04/22 Share

JPA는 다양한 퀴리 방법 지원

  • JPQL
  • JPA Criteria
  • QueryDSL
  • 네이티브 SQL
  • JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용

JPQL

  • 기초 반드시 숙달
  • 가장 단순한 조회방법
  • 검색할때도, 테이블이 아닌 엔티티 객체를 대상으로 검색
  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능
  • 애플리케이션이 필요한 데이터만 DB에서 불러올려면, 검색 조건이 포함된 SQL 문 필요
  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
  • JPQL은 엔티티를 대상으로 작성
  • 하지만 String 으로 작성하기 때문에 동적쿼리 작성 어려움
1
em.createQuery("select m FROM Member m where m.username like '%kim%'", Member.class).getResultList();

Criteria(안씀)

  • JPQL보다 동적 쿼리 짜기는 쉬움
  • java 코드로 짜는거니까 compile에러가 나옴
  • 하지만 표현 자체가 어려움
1
2
3
4
5
6
7
8
//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만 잘하면 QueryDSL따라옴
1
2
3
4
5
6
7
8
//JPOL
//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();

네이티브 SQL

  • JPA가 제공하는 SQL을 직접 사용하는 기능
  • JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능
  • 예) 오라클 CONNECT BY, 특정 DB만 사용하는 SQL 힌트
1
2
String sql = "SELECT ID, AGE, TEAM ID, NAME FROM MEMBER WHERE NAME = 'kim'";
List<Member> resultList = em.createNativeQuery(sql,Member.class).getResultList();

JDBC 직접 사용, SpringJdbcTemplate

  • JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나, 스프링 JdbcTemplate, 마이바티스등을 함께 사용 가능
  • 단 영속성 컨텍스트를 적절한 시점에 강제로 플러시 필요
  • 예) JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트 수동 플러시

모든 기술들은 1차 캐시(영속성 컨텍스트) 고려하기

  • JPA는 em.persist()하기 전까지는 DB에 없다. 근데, 기술을 써서 조회해오면 당연히 없다.
    반드시, flush()등등 활용해서 DB반영한후 사용
  • auto 모드에서 jpa관련된 기술들은 자동으로 쿼리 이전에 flush()가 실행되므로 상관없다.
  • 하지만 DBconnection을 수동으로 가져온후,, 조회하면 없다. 이전에 em.flush()하자

JPQL

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

JPQL 문법

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

스크린샷 2023-04-23 오후 4.34.50

  • COUNT(m)
  • SUM(m.age)
  • AVG(m.age)
  • MAX(m.age)
  • MIN(m.age)
  • GROUP BY, HAVING
  • ORDER BY

TypeQuery, Query

  • TypeQuery: 반환 타입이 명확할 떄 사용
  • Query: 반환 타입이 명확하지 않을때
1
2
3
TypedQuery<Member> select_m_from_member_m = em.createQuery("select m from Member m", Member.class);
TypedQuery<String> query1 = em.createQuery("select m.username, m.age from Member m", String.class);
Query query = em.createQuery("select m.username, m.age from Member m");

결과 조회 API

  • query.getResultList(): 결과가 하나 이상일 때, 리스트 반환
    • 결과가 없으면 빈 리스트 반환
  • query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환
    • 결과가 한개가 아니면 Exception처리 필요해짐
    • 결과가 없으면: javax.persistence.NoResultException
    • 둘 이상이면: javax.persistence.NonUniqueResultException

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

1
2
3
4
5
6
7
8
9
//이름 기준, 강추
SELECT m FROM Member m where m.username=:username

query.setParameter("username", usernameParam);

//위치 기반... 추천 X
SELECT m FROM Member m where m.username=?1

query.setParameter(1, usernameParam);

프로젝션(!!!)

  • !!!!!!!!!!엔티티 프로젝션을 한다면 모두 영속성 컨텍스트에서 관리됨!!!!!!!!!!!

  • 참고로, 내부적으로 묵시적으로 join이 쓰인다면, 명시적으로 join으로 JPQL로 명시적으로 하자

  • SELECT 절에 조회할 대상을 지정하는 것

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

  • Member 엔티티의 alias 가 m

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

  • DISTINCT로 중복 제거 가능

프로젝션 - 여러 값 조회

SELECT m.username, m.age FROM Member m

-> 타입을 명시못하니 Object로 나오니까.

  1. Query 타입으로 조회

  2. Object[] 타입으로 조회

  3. new 명령어로 조회(추천)

    • 단순값을 MemberDTO 로 바로조회

    • SELECT new jpabook.jpql.MemberDTO(m.username, m.age) FROM Member m

    • 생성자를 호출하는것 new 가 반드시 필요!!!

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

    • 순서와 타입이 일치하는 생성자 필요

    • 추후 queryDSL사용시 이것과 똑같이가능

페이징 API

  • JPA는 페이징을 다음 두 API로 추상화
  • setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
  • setMaxResults(int maxResult) : 조회할 데이터 수
1
2
3
4
5
//페이징 쿼리
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();
1
2
3
4
5
6
7
8
9
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 ?, ?

조인

  • 내부 조인:
    SELECT m FROM Member m [INNER] JOIN m.team t
  • 외부 조인:
    SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
  • 세타 조인: -> m*t 곱하기로 나옴…모든 테이블 생성됨
    select count(m) from Member m, Team t where m.username = t.name

조인 - ON절

  • ON절을 활용한 조인(JPA 2.1부터 지원)
    1. 조인 대상 필터링
    2. 연관관계 없는 엔티티 외부 조인(하이버네이트 5.1부터)

조인 대상 필터링(연관관계 있음 + 로 필터링가능)

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

  • 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

서브 쿼리

보통은 연관되지 않는 값을 서브쿼리에 쓰는것이 성능이좋다

  • 나이가 평균보다 많은 회원(성능!)

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

  • 한 건이라도 주문한 고객(성능X)

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

서브 쿼리 지원 함수

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

JPA 서브 쿼리 한계

  • JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
  • SELECT 절도 가능(하이버네이트에서 지원)
  • FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
    • 조인으로 풀 수 있으면 풀어서 해결
    • 또는 쿼리 2번 날려서 해결. 나머지는 애플리케이션 단에서 해결
  • 하이버네이트6 부터는 FROM 절의 서브쿼리를 지원합니다.

JPQL 타입 표현

  • 문자: ‘HELLO’, ‘She’’s’
  • 숫자: 10L(Long), 10D(Double), 10F(Float)
  • Boolean: TRUE, FALSE
  • ENUM: jpabook.MemberType.Admin (패키지명 포함)
  • 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용)(상속관계 DType쓸때지만..보통안씀)

JPQL 기타

  • SQL과 문법이 같은 식
  • EXISTS, IN
  • AND, OR, NOT
  • =, >, >=, <, <=, <>
  • BETWEEN, LIKE, IS NULL

조건식 - CASE 식

  • 기본 CASE 식

    1
    2
    3
    4
    5
    6
    select
    case when m.age <= 10 then ' 학생요금'
    when m.age >= 60 then ' 경로요금'
    else ' 일반요금'
    end
    from Member m
  • 단순 CASE 식

    1
    2
    3
    4
    5
    6
    7
    select
    case t.name
    when '팀&' then '인센티브110%'
    when '팀B' then '인센티브120%'
    else ' 인센티브105%'
    end
    from Member m
  • COALESCE: 하나씩 조회해서 null이 아니면 반환

    • 사용자 이름이 없으면 이름 없는 회원을 반환
    • select coalesce(m.username,'이름 없는 회원') from Member m
  • NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환

    • 사용자 이름이 ‘관리자’면 null을 반환하고 나머지는 본인의 이름을 반환
    • select NULLIF(m.username, '관리자') from Member m

JPQL 기본 함수

  • CONCAT
  • SUBSTRING
  • TRIM
  • LOWER, UPPER
  • LENGTH
  • LOCATE
  • ABS, SQRT, MOD
  • SIZE, INDEX(JPA 용도)

사용자 정의 함수 호출

  • 하이버네이트는 사용전 방언에 추가해야 한다.
    사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다.

    select function('group_concat', i.name) from Item i

JPQL - 경로 표현식

경로 표현식 정의 및 예시

.(점)을 찍어 객체 그래프를 탐색하는 것

  • 상태 필드

    • select m.username from Member m
    • select m.username from Team t join t.members m -> 성공(명시적 조인)
  • 단일 값 연관 필드

    • join m.team t where t.name = ‘팀A’
    • select o.member.team from Order o -> 성공(묵시적 조인 2번)
  • 컬렉션 값 연관 필드

    • join m.orders o
    • select t.members from Team -> 성공(묵시적 조인1번)
    • select t.members.username from Team t -> 실패(Collection이므로 경로 못들어감)

경로 표현식 특징

  • 상태 필드(state field): 단순히 값을 저장하기 위한 필드 (ex: m.username)

    • 경로 탐색의 끝, 탐색 X
  • 연관 필드(association field): 연관관계를 위한 필드

    • 단일 값 연관 필드:

      • 묵시적 내부 조인(inner join) 발생, 탐색O
      • @ManyToOne, @OneToOne, 대상이 엔티티(ex: m.team)
    • 컬렉션 값 연관 필드:

      • 묵시적 내부 조인 발생, 탐색X
      • @OneToMany, @ManyToMany, 대상이 컬렉션(ex: m.orders)

결론으로 묵시적 내부 조인 하지말자(!!!)

  • 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시 적 조인으로 인해 SQL의 FROM (JOIN) 절에 영향을 줌
  • 명시적 조인 사용
  • 조인은 SQL 튜닝에 중요 포인트
CATALOG
  1. 1. JPA는 다양한 퀴리 방법 지원
  2. 2. JPQL
  3. 3. Criteria(안씀)
  4. 4. QueryDSL(권장, 강추, !!!)
  5. 5. 네이티브 SQL
  6. 6. JDBC 직접 사용, SpringJdbcTemplate
  7. 7. 모든 기술들은 1차 캐시(영속성 컨텍스트) 고려하기
  8. 8. JPQL
    1. 8.1. JPQL 문법
    2. 8.2. TypeQuery, Query
    3. 8.3. 결과 조회 API
    4. 8.4. 파라미터 바인딩 - 이름 기준, 위치 기준
    5. 8.5. 프로젝션(!!!)
    6. 8.6. 프로젝션 - 여러 값 조회
    7. 8.7. 페이징 API
    8. 8.8. 조인
    9. 8.9. 조인 - ON절
      1. 8.9.1. 조인 대상 필터링(연관관계 있음 + 로 필터링가능)
      2. 8.9.2. 연관관계 없는 엔티티 외부 조인(!!!)
    10. 8.10. 서브 쿼리
    11. 8.11. 서브 쿼리 지원 함수
    12. 8.12. JPA 서브 쿼리 한계
    13. 8.13. JPQL 타입 표현
    14. 8.14. JPQL 기타
    15. 8.15. 조건식 - CASE 식
    16. 8.16. JPQL 기본 함수
    17. 8.17. 사용자 정의 함수 호출
  9. 9. JPQL - 경로 표현식
    1. 9.1. 경로 표현식 정의 및 예시
    2. 9.2. 경로 표현식 특징
    3. 9.3. 결론으로 묵시적 내부 조인 하지말자(!!!)