JPQL

JPA 단순 문법만으로, 실무에서는 사용 불가하다.
find() 만으로 개발을 할 수는 없을 것이다.

예를 들어, select 하는 값이, 엔티티가 아닌 값일 수도 있다.
또 필요한 데이터 만을 조회하기위해서, 검색 조건을 변경해야 할 때도 있다.

따라서, JPA는 SQL을 추상한 JPQL이라는 객체지향 쿼리를 제공한다.
SQL문법과 유사하지만,
JPQL은 객체 대상의 쿼리, SQL은 데이터베이스 테이블을 대상으로하는 쿼리이다.

JPQL로 쿼리를 짜면, JPA가 SQL로 번역을해서 쿼리를 전송한다.
따라서 JPQL은 특정 DB에 의존적이지 않고,
JPA 방언 설정에 따라 알아서 번역되어 SQL 쿼리를 요청한다.

<br>

JPA의 쿼리

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

  • JPQL
  • JPA Criteria
  • QueryDSL
  • 네이티브 SQL // SQL
  • MyBatis, SpringJdbcTempalte

JPA Criteraia, QueryDSL은 JPQL 쿼리를 자바 코드로 구현 할 수 있도록 하는 것이다.
하지만 JPA Criteria는 코드가 지저분 하므로, 잘 사용하지 않는다.

보통은 JPQL, Spring Data JPA, QueryDSL을 실무에서 주로 사용하고,
실무에서는 JPQL, QueryDSL 조합으로 95%는 가능하다.

하지만, 때때로 정말 불가능한 쿼리를 짜야 할 때, MyBatis, NativeQuery, SpringJdbcTemplate을 이용한다.

JPQL

모든 개념이 그렇듯 , 원리를 이해해야 활용을 할 수 있을것이다.
Spring Data JPA, QueryDSL은 결국 JPQL을 좀더 쉽게 짤 수 있는 uitlity일 뿐이고, JPQL을 이해한다면, utility는 충분히 활용 할 수 있을 것이다.

  • 엔티티의 속성은 대소문자 구분. (Memeber, age)
  • JPQL 키워드는 대소문자 구분X (SELECT, from)
  • 엔티티 이름 사용, 테이블이름이아니다!
  • 별칭은 필수 (as 생략가능)
  • ex) select m from Member mwhere m.age > 18

<br>

다음부터, JPQL의 문법을 정리해보자

Member : Team = 1 : N 관계이다.

<br>

집합과 정렬

select
    COUNT(m),
    SUM(m.age),
    AVG(m.age),
    MAX(m.age),
    MIN(m.age)
from Member m

<br>

TypeQuery, Query

  • TypeQuery : 반환타입 명확
  • Query : 반환타입 명확하지않음
TypedQuery<Member> query = em.createQuery("SELECT m From Member m", Member.class);
    
Query query = em.createQuery("SELECT m.username, m.ageFrom Member m" );

<br>

결과 조회 API

  • query.getResultList() : 결과 하나 이상일떄, 결과가 없으면 빈 리스트 반환.
  • query.getSingleResult() : 결과가 정확히 하나가 아닐경우 Exception이 발생한다. 이 때문에, Spring Data JPA는 이를 추상화하여, null, Optional을 사용한다.

<br>

SetParameter

em.createQuery("select m from Member m where m.username = :useranem", Member.class)
.getParameter("username", "podo")
.getSingleResult();

위치값 을 이용해서, setParameter를 쓰지말자.

<br>

프로젝션

SELECT 절에 조회할 대상을 지정하는 것
프로젝션 대상은 엔티티, 임베디드타입, 스칼라타입(숫자, 문자등 기본데이터 타입)이 될 수 있을것이다.

  • 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로 중복 제거

<br>

프로젝션 JOIN

JPQL은 JPA에 의해 SQL쿼리로 번역되서 전송된다.

예를 들어, 다음 JPQL 쿼리를 보자.

SELECT m.team From Member m

이 쿼리는 실제로 Member안에 Team 엔티티를 조회하게 되고,
번역된 SQLJOIN쿼리가 전송하게 된다.

하지만, 단순히 봤을 때는 JOIN 쿼리가 전송되다는 것을 확인하기 어렵다.
JOIN은 항상 성능이슈의 첫번째 문제이다. 개발자도 모르게 JOIN쿼리가 전송된다면, 나중에 성능이슈가 분명히 발생할 것이다. JPQL쿼리를 보고, 나는 알아도 다른 개발자는 모를 수도 있다.

따라서, JPQLSQL과 최대한 유사하게 짜는 것을 권장하다. 권장 x 100

위에 JPQL 쿼리는 다음과 같이 짜야한다.

SELECT t From Member m join m.team t

<br>

프로젝션, 반환값이 명확하지 않을 때.

이 쿼리의 반환값을 어떻게 명시해야할까?

SELECT m.username, m.age from Member m

첫번째로, Object[]로 조회 할 수 있다.

List result = em.createQuery("SELECT m.username, m.age from Member m").getResultList();

Object[] obj = result.get(0);
obj[0] // username
obj[1] // age

<br>

두번째로, 새로운 dto를 정의한다.

생성자를 정의해야하고, dto의 패키지명까지 다 적어주어야 한다

@AllContructor
class MemberDto{
    String username;
    Integer age;
}

List<MemeberDto> result = em.createQuery("SELECT new package.MemeberDto(m.username, m.age) from Member m", MemeberDto.class).getResultList();

<br>

페이징

JPA의 아트의 경지!

  • setFistResults() : 조회 시작 위치
  • setMaxResults() : 조회할 데이터수

<br>

JOIN

  • SELECT m FROM m Member m JOIN m.team t //내부조인
  • SELECT m FROM m Member m LEFT JOIN m.team t // 외부조인
  • SELECT m FROM Member m, Team t where m.username = t.name //세타조인(관계없는 엔티티와 조인)

<br>

JOIN - ON절

JPA 2.1부터 지원한다.

  • 조인 대상을 필터링할 수있다.
  • 연관관계 없는 엔티티끼리 외부조인을 할 수 있다.(Hibernate 5.1부터), 기존에 세타조인은 inner JOIN만 가능했다.

조인 대상 필터링
ex) SELECT m FROM m Member m JOIN m.team t on t.name = 'A'

연관관계 없는 엔티티 외부조인
ex) SELECT m FROM Member m LEFT JOIN Team t ON m.username = t.name

<br>

서브쿼리

나이가 평균보다 많은 회원

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

메인쿼리의 Member와 서브 쿼리의 Member와 달리해야
성능이 잘나오는 것을 명심하자.

EXISTS, ALL, ANY, IN 등 모두 사용가능하다.

<br>

JPA 서브 쿼리의 한계

JPA는 WHERE, HAVING 절에서만 서브쿼리를 사용할 수있다.
FROM 절에서는 서브쿼리 사용이 불가능하다, 대부분 조인으로 풀어서 해결 할 수있다. 그래도 사용해야 하는 상황이 생긴면, NavtiveQuery 또는, 어플리케이션 단에서 쿼리를 두번 날리는 방법등으로 해결.

<br>

JPA 기타

예를 들어 Memberenum 값을 가지고 있다면, 다음과 같이 사용 할 수 있다.

SLECT m.username FROM  Member m where M.type = package.MemberType.ADMIN
em.createQuery(`SLECT m.username FROM  Member m where M.type = :userType).setParameter("userType", MemberType.ADMIN);

상속관계에서 다음과 같이 사용 할 수 도있다. (잘 쓰지는 않음)

em.createQuery("select i from Item i where type(i) = Book", Item.class);

또한, EXISTS, IN, AND, OR, NOT, >, < 등등 기본적인 문법은 사용 할 수 있다.

<br>

JPA CASE식

기본 CASE식

select
    case when m.age <= 10 then `학생요금`
    case when m.age >= 60 then `어른요금`
            else `일반요금`
    from Member m

<br>

단순 CASE식

select
    case t.name
        when '팀A' then '인센티브100%'
        when '팀B' then '인센티브120%'
        else '인센티브105%'
    end
from Team t

<br>

COALESCE : 하나씩 조회해서 null이 아니면, 반환
select coalesce(m.username, '이름없음') from Member m // username이 없으면,이름 없음 반환

<br>

NULLIF : 두값이 같으면 null반환
select nullif(m.username, '홍길동') from Member m // username이 홍길동이면 null 반환

<br>

JPQL 기본 함수

크게 쓰는 함수를 세가지로 구분 할 수있다.

JPQL에서 제공하는 표준 함수 (데이터베이스에 관계없이 사용할 수 있다)
CONCAT, SUBSTRING, TRIM,
LOWER, UPPER, LENGTH,
LOCATE, ABS, SQRT, MOD

SIZE // 컬렉션의 크기를 반환한다.
INDEX // 컬렉션에서의 위치를 반환한다 (wite @OrederColumn), 가능하면 쓰지말자.

<br>

각 DB별로 정의된 DB의존적 함수
다행히도, 데이터베이스마다 기본으로 사용하는 함수는 방언에 정의되있다!

<br>

사용자 정의 함수, 커스텀 함수를 정의 할 수 있다.
커스컴 방언 클래스를 정의하고, 해당 방언에 함수를 정의하자.
그리고 그 방언 클래스를 사용하자.
필요할 때 라이브러리 코드를 참조해서 사용해보자.

public class MyH2Dialect extends H2Dialect{
    public MyH2Dialect(){
        registFunction(blaba)
    }
}
0
이전 댓글 보기
등록
TOP