JPQL - 1

Jaca·2021년 8월 29일
0

JPQL

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

JPQL의 기본 문법

  • 큰 틀은 SQL과 동일하다.
  • 엔티티와 속성은 대소문자 구분O (Member, age)
  • JPQL 키워드는 대소문자 구분X (SELECT, FROM, where)
  • 엔티티 이름 사용, 테이블 이름이 아님(Member)
  • 별칭은 필수(m) (as는 생략가능)

TypeQuery, Query

  • TypeQuery : 반환 타입이 명확할 때
  • Query : 반환 타입이 명확하지 않을 때

직관적으로 차이를 알 수있다.
Option + Command + v 를 통해 자동적으로 얼추 잡아주니 크게 신경 쓸 필요는 없을 것 같다.

결과 조회 API

  • .getResultList(): 결과가 하나 이상일 때, 리스트 반환
    결과가 없으면 빈 리스트를 반환함.
  • .getSingleResult(): 결과가 정확히 하나, 단일 객체 반환
    결과가 반드시 한개가 있어야한다. 없어도 에러, 2개 이상이어도 에러

파라미터 바인딩

쿼리를 쏠 때, 매개변수를 넣어주는 것이다.
쿼리 내에 매개변수에 :변수명 을 넣고, .setParameter("name", "value") 를 통해 매개변수를 바인딩 한다.

매개변수의 변수명의 이름은 .setParameter() 의 이름과 같기만 하다면 상관 없으나 직관성을 생각하자.

매개변수를 이름이 아닌 인덱션을 기반으로도 사용이 가능하나,
DB의 특성상 실제 인덱스에 변화가 있어도 DB의 인덱스에는 변화가 없으니, 조심하도록 하자

프로젝션

프로젝션이란, SELECT 절에 조회할 대상을 지정하는 것 이다.

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

  • SELECT m FROM Member m -> 엔티티 프로젝션
    아주 직관적으로 Member 엔티티를 대상으로 하고있다.

  • SELECT m.team FROM Member m -> 엔티티 프로젝션
    Member 엔티티의 연관관계 Team 엔티티를 대상으로 하고있다.

  • SELECT m.address FROM Member m -> 임베디드 타입 프로젝션
    일전 예제에서 보았듯 Address는 값 타입. 임베디드 타입을 대상으로 하고있다.

  • SELECT m.username, m.age FROM Member m -> 스칼라 타입 프로젝션
    엔티티내의 기본 데이터 타입. 스칼라 타입을 대상으로 하고있다.

  • DISTINCT로 중복 제거

위 내용이 무슨 의미를 가지나면..

위 코드는 어떤 쿼리를 내보낼까.
em.persist(member); 를 통해 insert 문이 나가고
영속성 컨테이너를 flush, clear 해주었으니, 다음엔 Select 쿼리가 나갈 것이다.

그리고 가져온 findMember의 나이를 바꾸어주었을때, 이 데이터가 영속성 컨테이너에 의해 관리되고 있다면 update문이 나갈것이고, 아니라면 나가지 않을것이다.

여러 값을 조회 할 땐?

위 예시에서도 보았듯 여러가지 스칼라 타입도 함께 가져올수 있다.
이럴 땐 저장을 어떻게 해야할까?

정말 똑똑한 Intelli J, 단축키를 통해 iter를 돌려보니, object 타입으로 받고 있다.
하지만 실제로 살펴보면 이는 username과 age로 이루어진 배열의 형태이다.
그래서 배열로 받는 편이 깔끔하다.
또한 Object의 배열을 설정하여 TypeQuery로 받을 수 도 있다.

가장 추천 하는 방법은 단순 값을 DTO로 바로 조회 하는 것이다.

사용할 스칼라 타입을 넣고 생성자와 게터,세터를 만들고,

생성자를 통해 새 객체를 만드는 것이다.
주의 할 점은 패키지명을 포함한 전체 클래스 명을 입력하고,
순서와 타입이 일치하는 생성자 필요하다.

페이징

페이징의 개념은 데이터베이스나 정처기.. 등을 공부해봤다면 대충 개념은 알것이다.

페이징에서 중요한 메서드는 하이라이트 된 두 블럭.
솔직히 메서드 이름, 코드의 로직과 실행 로직만 보면 가타부타 설명하지 않아도 눈칫밥으로들 다 알 것이다.

그런데 페이징이 왜 중요할까. JPA의 장점 중 하나인, DB의 방언을 맞춰준다는 것..

H2 DB의 SQL

Oracle DB의 SQL

DB의 방언에 맞춰 SQL을 알아서 날려주기 때문에 페이징 문법이 까다로운 DB에서 아주 편리하다.

조인

  • 내부 조인:
    SELECT m FROM Member m [INNER] JOIN m.team t
  • 외부 조인:
    SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
  • 세타 조인:
    select count(m) from Member m, Team t where m.username
    = t.name

내부, 외부 조인은 익숙하지만 세타 조인이라는 용어가 익숙하지 않다.
하지만 문법을 보니 일반적으로 select문 으로 두 테이블을 비교하는 쿼리와 같다는 점을 명심하자.
연관관계가 없는 것을 막 조인 하는 것을 세타 조인이라고 하신다.. 명한선생님 께서

조인은 중요한 개념이지만, 어느정도 알고있기도 하고, 앞으로도 계속 보게 될 터이니 지금은 간단히 넘어가도록 하자

조인 대상 필터링

예) 회원과 팀을 조인하면서, 팀 이름이 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

일반 select문의 where 절이라고 생각하면 쉽다.

서브 쿼리

예) 나이가 평균보다 많은 회원
select m from Member m where m.age > (select avg(m2.age) from Member m2)
예) 한건 이라도 주문한 고객
select m from Member m where (select count(o) from Order o where m = o.member) > 0

별로 어려운 내용은 아니다.
기초 SQL 문법

서브 쿼리 지원 함수

[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는 select, where, having 절에서만 서브 쿼리를 사용 가능하다.

FROM 절의 서브 쿼리는 현재 JPQL에서 불가능하다.
-> 조인으로 풀수있으면 풀어서 해결 (이걸로 안되면 안댐 .ㅠ sql을 쪼개서 해야되려나)

JPQL 타입 표현

  • 문자
    'HELLO', 'She''s'

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

  • Boolean
    TRUE, FALSE

  • ENUM

    • Jpabook.MemberType.Admin(풀 패키지명 포함)
    • 보통은 파라미터 바인딩으로 처리
    • QueryDSL에서는 복잡하지 않게 자바 코드로 사용한다.
  • 엔티티 타입

    • TYPE(m) = Member (상속 관계에서 사용)
    • Item을 상속받은 여러개의 클래스 중 Book만 조회하고 싶은 경우
em.createQuery("select i from Item i where type(i) = Book", Item.class)
    .getResultList();

JPQL 기타

  • EXISTS, IN
  • AND, OR, NOT
  • =, >, >=, <, <=, <>
  • BETWEEN, LIKE, IS NULL

CASE 식

  • 기본 CASE 식
select
    case when m.age <= 10 then '학생요금'
         when m.age >= 60 then '경로요금'
         else '일반요금'
    end 
from Member m
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();
Hibernate: 
    /* select
        case 
            when m.age <= 10 then '학생요금'     
            when m.age >= 60 then '경로요금'     
            else '일반요금'
        end 
    from
        Member m */ select
            case 
                when member0_.age<=10 then '학생요금' 
                when member0_.age>=60 then '경로요금' 
                else '일반요금' 
            end as col_0_0_ 
        from
            Member member0_
  • 단순 CASE 식
select
    case t.name
      when '팀A' then '인센티브110%'
      when '팀B' then '인센티브120%'
      else '인센티브105%'
    end
from Team t
  • COALSESCE
    하나씩 조회해서 null이 아니면 반환
String query =
    "select coalesce(m.name, '이름 없는 회원') from Member m";
  • NULLIF
    두 값이 같으면 null 반환, 다르면 첫번째 값 반환
    사용자 이름이 '관리자'면 null을 반환하고 나머지는 본인의 이름을 반환함.
    특정 이름을 숨겨야 할 경우
String query =
    "select nullif(m.name, '관리자') from Member m";

JPQL 기본 함수

JPQL이 제공하는 표준 함수

  • CONCAT

  • SUBSTRING

  • TRIM

  • LOWER, UPPER

  • LENGTH

  • LOCATE -> indexOf와 같이 자리수 반환

  • ABS, SQRT, MOD

  • SIZE, INDEX(JPA 용도)
    값 타입 컬렉션에 컬렉션의 위치 값을 구할 때 @OrderColumn 사용. INDEX로 위치 참조 가능

String query = "select size(t.members) from Team t";
String query = "select index(t.members) from Team t";

사용자 정의 함수 호출

하이버네이트는 사용전 방언에 추가해야 한다.

DB에 커스텀 펑션 만든 후 사용

select function ('group_concat', i.name) from Item i
select 'group_concat'(i.name) from Item i
profile
I am me

0개의 댓글