[JPA] 10. 객체지향 쿼리 언어 - JPQL

13554 단어 JPAJPA

자바 ORM 표준 JPA 프로그래밍 공부 기록


목차

  • 객체지향 쿼리 소개
  • JPQL
  • Criteria
  • QueryDSL
  • Native SQL
  • 객체지향 쿼리 심화

📌 10.2 JPQL

  • 객체지향 쿼리 언어
    테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리함

  • SQL을 추상화해서 특정데이터베이스 SQL에 의존하지 않는다


📍 JPQL 문법

  • 엔티티와 속성은 대소문자 구분 O (Member, age)
  • JPQL 키워드는 대소문가 구분 X (SELECT ...)
  • Entity 명 사용 (테이블 이름 X)
  • 별칭은 필수 (Member m)

TypeQuery, Query

작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 한다.
쿼리 객체는 TypeQuery와 Query가 있다

  • TypeQuery : 반환할 타입을 명확하게 지정할 수 있으면 사용
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
  • Query : 반환 타입이 명확하지 않을 때 사용
Query query = em.createQuery("select m.userName, m.age from Member m");

결과 조회 API

  • query.getResultList
    결과가 하나 이상일 때, 리스트 반환.
    결과가 없으면 빈 컬렉션 반환

  • query.getResultList
    결과가 정확히 하나, 단일 객체 반환
    예외 발생

    • 결과가 없으면: javax.persistence.NoResultException
    • 둘 이상이면: javax.persistence.NonUniqueResultException



📍 파라미터 바인딩

JDBC는 위치 기준 파라미터 바인딩만 지원하지만 JPQL은 이름 기준 파라미터 바인딩도 지원한다.

  • 이름 기준 파라미터
    이름 기준 파라미터는 앞에 :를 사용한다
String usernaeParam = "User1";

TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.username=:username", Member.class);
query.setParameter("username", usernameParam);
  • 위치 기준 파라미터
    ? 다음에 위치 값을 주면 된다
String usernaeParam = "User1";

TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.username=?1", Member.class);
query.setParameter(1, usernameParam);

위치 기준 파라미터 방식 보다는 이름 기준 파라미터 바인딩 방식을 사용하는게 더 명확하다.

SQL 인젝션 공격이나 전체적인 성능 향상을 위해 파라미터 바인딩 방식은 선택이 아닌 필수이다!



📍 프로젝션

SELECT 절에 조회할 대상을 지정하는 것을 프로젝션이라고 한다.
프로젝션 대상 : 엔티티, 임베디드 타입, 스칼라 타이

  • 엔티티 프로젝션
    em.createQuery("select m from Member m", Member.class).getResultList();
    엔티티 프로젝션에서 조회한 엔티티는 영속성 컨텍스트에서 다 관리된다.

  • 임베디드 타입 프로젝션
    em.createQuery("select o.address from Order o", Address.class).getResultList();
    임베디드 타입은 엔티티 타입이 아닌 값 타입이다. 따라서 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.

  • 스칼라 타입 프로젝션
    문자, 숫자, 날짜와 같은 기본 데이터 타입을 스칼라 타입이라 한다.


new 명령어로 조회


public class MemberDTO {
    private String userName;
    private int age;

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




List<MemberDTO> list = em.createQuery("select new jpql.MemberDTO(m.userName, m.age) from Member m", MemberDTO.class).getResultList();

select 다음에 new 명령어를 사용하면 반환받을 클래스를 지정할 수 있는데 이 클래스의 생성자에 JPQL 조회 결과를 넘겨줄 수 있다. 그리고 new 명령어를 사용한 클래스로 TypeQuery를 사용할 수 있어 객체 변환 작업을 줄일 수 있다.



📍 페이징 API

setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
setMaxResults(int maxResult) : 조회할 데이터 수

데이터베이스 방언 (Dialect) 덕분에 각기 다른 DB마다 같은 API로 페이징 처리를 할 수 있다.



📍 서브 쿼리

  • FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
    => 조인으로 풀 수 있으면 풀어서 해결



📍 조건식

💭 타입 표현

  • 문자
    작은 따옴표 사이에 표현한다. 작은 따옴표 표현하려면 연속 두개('') 사용
    ex) 'Hello', 'She''s'

  • 숫자 : 10L(Long), 10D(Double), 10F(Float)

  • Boolean: TRUE, FALSE

  • Enum
    패키지명을 포함한 전체 이름을 사용해야 한다

ex ) jpql.MemberType.ADMIN

            String query = "select m.userName, 'HELLO', TRUE from Member m "
                    + "where m.memberType = :userType";

            List<Object[]> result = em.createQuery(query)
                    .setParameter("userType", jpql.MemberType.ADMIN)
                    .getResultList();

            for(Object[] objects : result){
                System.out.println(objects[0]);
                System.out.println(objects[1]);
                System.out.println(objects[2]);
            }
            
            
 Hibernate: 
    /* select
        m.userName,
        'HELLO',
        TRUE 
    from
        Member m 
    where
        m.memberType = :userType */ 
        select
            member0_.userName as col_0_0_,
            'HELLO' as col_1_0_,
            1 as col_2_0_ 
        from
            Member member0_ 
        where
            member0_.memberType=?
TEST
HELLO
true
  • 엔티티
    엔티티의 타입을 표현한다. 주로 상속 관련해서 사용
    ex) TYPE(m) = Member

💭 조건식 - CASE 식

특정 조건에 따라 분기할 때 CASE 식을 사용함. 종류는 다음과 같다

  • 기본 CASE
            String query = "select " +
                    "case when m.age <= 10 then '학생요금' " +
                    "     when m.age >= 60 then '경로요금' " +
                    "       else '일반요금' " +
                    "end "+
                    "from Member m " +
                    "";

            List<String> result = em.createQuery(query, String.class)
                    .getResultList();
  • 심플 CASE
    조건식이 없고 문법이 단순 (자바의 switch와 유사)

  • COALESCE
    스칼라식을 차례대로 조회해서 null이 아니면 반환한다.

  • NULLIF

좋은 웹페이지 즐겨찾기