4 분 소요

📌 Topic

  • 💡 JPA는 다양한 쿼리 방법을 지원
  • 💡 JPQL(Java Persistence Query Language)
  • 💡 JPQL 기본 문법
  • 💡 JPQL 집합과 정렬
  • 💡 JPQL TypeQuery와 Query
  • 💡 결과 조회 API
  • 💡 파라미터 바인딩 - 이름 기준, 위치 기준

⚡ 01. JPA는 다양한 쿼리 방법을 지원

JPA는 DataBase에서 데이터를 뽑아오기 위해 다양한 Query를 지원한다

  • JPQL
  • JPA Criteria
  • QueryDSL
  • Native SQL
  • JDBC API 직접 사용, Mybatis, SpringJdbcTemplate과 함께

✅ 01-1 지금까지의 조회 방법

지금까지는 단순히 find() 메서드를 사용하여 전체 조회만 수행 해왔다

  • 가장 단순한 조회 방법
    • EntityManager.find()
    • 객체 그래프 탐색
  • 하지만 만약 나이가 18살 이상인 회원을 모두 검색하고 싶다면?

✅ 01-2 문제점

  • JPA 사용시 엔티티 객체를 중심으로 개발해야 하는데, 문제는 조건 검색 쿼리다.
  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능하다.
  • 또한 필요한 데이터만 DB에서 조회 하려면 검색 조건이 포함된 SQL이 필요하다.

✅ 01-3. JPQL

  • JPA는 SQL을 추상화JPQL이라는 객체 지향 쿼리 언어를 제공한다.
  • SQL과 문법이 유사하며 SELECT, FROM, WHERE, GROUP BY 등을 제공한다.
    • JPQL은 엔티티 객체를 대상으로 쿼리를 날린다.
    • SQL은 데이베이스 테이블을 대상으로 쿼리를 날린다.

✅ 01-4. JPQL

// 호출문
List<Member> resultList = em.createQuery(
        "select m From Member m where m.name like '%kim%'",
        Member.class
    ).getResultList();
// 실행문
Hibernate:
    /* select
        m
    From
        Member m
    where
        m.name like '%kim%' */ select
            member0_.MEMBER_ID as member_i1_10_,
            member0_.INSERT_MEMBER as insert_m2_10_,
            member0_.createdDate as createdd3_10_,
            member0_.lastModifiedBy as lastmodi4_10_,
            member0_.lastModifiedDate as lastmodi5_10_,
            member0_.city as city6_10_,
            member0_.street as street7_10_,
            member0_.zipcode as zipcode8_10_,
            member0_.name as name9_10_
        from
            Member member0_
        where
            member0_.name like '%kim%'
  • JPQL은 Member 엔티티 자체에서 데이터를 조회한다.
  • 일일이 필드를 나열할 필요가 없다.
  • 검색 조건은 문자열이다

✅ 01-5. JPA Criteria

JPQL을 사용하면 동적 쿼리 작성이 까다로운데, 이 때 Criteria를 사용할 수 있다

// 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("name"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList();
  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있다.
  • JPQL 빌더 역할이며, JPA의 공식 기능이다.
  • 하지만 너무 복잡하고 실용성이 없다.
  • Criteria 대신에 QueryDSL 사용을 권장한다.

✅ 01-6. QueryDSL 소개

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

List<Member> list =
    query.selectFrom(m)
         .where(m.age.gt(18))
         .orderBy(m.name.desc())
         .fetch()
  • QueryDSL은 Open source library이며, 별도의 환경 셋팅이 필요하다.
  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있으며, JPQL 빌더 역할을 한다.
  • 컴파일 시점에 문법 오류를 찾을 수 있다.
  • 동적쿼리 작성이 편리하며, 단순하고 쉽기에 실무에서 권장이 된다.

✅ 01-7. Native SQL 소개

String sql = "SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = 'kim'";
List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();
Hibernate:
    /* dynamic native SQL query */ select
        MEMBER_ID,
        city,
        street,
        zipcode,
        USERNAME
    FROM
        MEMBER
  • JPA가 제공하는 SQL을 직접 사용하는 기능.
  • JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능.
  • 쉽게 말해서 쿼리를 직접 작성하는 것이다.
  • ex) 오라클 CONNECT BY, 특정 DB만 사용하는 SQL 힌트.

01-8. JDBC 직접 사용

JDBC를 직접 사용하는 것이 JPA와는 전혀 상관이 없는 부분이다

JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나, 스프링 JdbcTemplate, Mybatis 등을 함께 사용할 수 있다. 단 영속성 컨텍스트를 적절한 시점에 강제로 플러시 할 필요가 있으며, 대표적으로는 JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트를 수동으로 플러시 하는 경우를 한 가지 예로 들 수 있다.

⚡ 02. JPQL(Java Persistence Query Language)

✅ 02-1. JPQL 소개

JPQL은 객체지향 쿼리 언어다. 따라서 테이블을 대상으로 쿼리 하는 것이 아니라 엔티티 객체를 대상으로 쿼리를 수행한다. 또한 JPQL은 SQL을 추상화하여 특정 DB에 의존하지 않으며 결국은 SQL로 변환이 되어 실행이 된다.

✅ 02-2. 객체 모델과 DB 모델

2022_03_10_jpql_객체모델_db모델

위 모델링을 한번 살펴보면, 여러명의 회원은 하나의 팀에 속하게 되며 하나의 회원이 여러개의 상품을 주문할 수 있는 모델 구조다.

⚡ 03. JPQL 기본 문법 (bulk?)

select_문 :: =
    select_절
    from_절
    [where_절]
    [groupby_절]
    [having_절]
    [orderby_절]

update_문 :: = update_절 [where_절]
delete_문 :: = delete_절 [where_절]
  • bulk 연산을 사용한 JPQL 예제.
  • 네트워크 트래픽을 여러번 타지 않기 위해 사용한다.

✅ 03-1. JPQL 문법

// 1. 엔티티와 속성은 대소문자를 구분한다.
// 2. 키워드는 대소문자를 구분하지 않는다.
em.createQuery("select m from Member where m.age > 18")
  • select m from Member as m where m.age > 18
  • 엔티티와 속성(필드)은 대소문자를 구분한다.
    • Member, age
  • JPQL 키워드는 대소문자를 구분하지 않는다.
    • SELECT, FROM, where
  • 엔티티 이름 명시해야 한다. 즉, 테이블 이름이 아니다.
  • 별칭은 필수다(m), 하지만 as는 생략이 가능하다.

⚡ 04. JPQL 집합과 정렬

select
    COUNT(m), // 회원수
    SUM(m.age), // 나이 합
    AVG(m.age), // 평균 나이
    MAX(m.age), // 최대 나이
    MIN(m.age) // 최소 나이
from Member m
  • JPQL은 ANSI 표준 함수가 제공된다.

✅ 04-1. 집합과 정렬

  • GROUP BY, HAVING
  • ORDER BY

⚡ 05. TypeQuery, Query

  • TypeQuery : 반환 타입이 명확할 때 사용한다.
  • Query : 반환 타입이 명확하지 않을 때 사용한다.
TypedQuery<JpqlMember> query = em.createQuery("select m from JpqlMember m", JpqlMember.class);
TypedQuery<String> query2 = em.createQuery("select m.username from JpqlMember m", String.class);

// 반환 타입이 명확하지 않은 경우
// TypedQuery<JpqlMember> query3 = em.createQuery("select m.username, m.age from JpqlMember m");
Query query3 = em.createQuery("select m.username, m.age from JpqlMember m");

⚡ 06. 결과 조회 API

// return List or empty List
query.getResultList();
  • 결과가 하나 이상일 때, 리스트를 반환한다.
  • 결과가 없으면 빈 리스트를 반환한다
// return Sigle object
query.getSingleResult();
  • 결과가 정확히 하나 반환 된다. 즉, 단일 객체를 반환 한다.
    • 결과가 없으면 javax.persistence.NoResultException 반환.
    • 둘 이상이면 javax.persistence.NonUniqueResultException 반환.

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

파라미터 바인딩은 위치 기준이 아닌 이름 기준을 사용하자
위치 기준으로 하면 욕 먹음

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);
Hibernate:
    /* select
        m
    from
        JpqlMember m
    where
        m.username = :username */ select
            jpqlmember0_.id as id1_10_,
            jpqlmember0_.age as age2_10_,
            jpqlmember0_.username as username3_10_
        from
            JPQL_MEMBER jpqlmember0_
        where
            jpqlmember0_.username=?
result ==> member1
  • ?에 바인딩 된 것을 확인 가능.

참고 자료

댓글남기기