JPA. JPQL

무지성개발자·2024년 2월 26일
0

JPA

목록 보기
11/12

JPA 쿼리

JPA는 아래와 같은 다양한 쿼리방법을 지원한다.

  • JPQL
  • JPA Criteria
  • QueryDSL
  • 네이티브 SQL

JPQL은 SQL을 추상화한 객체지향 쿼리이며 문자열인 JPQL을 자바코드로 사용하도록 지원하는 것이 Criteria과 QueryDSL이다.

JPQL

JPQL은 JPA에서 SQL을 추상화한 쿼리로 SQL문법과 상당히 유사하다. SQL은 테이블을 기준으로 데이터에 접근하지만 JPQL은 객체지향 쿼리여서 엔티티를 기준으로 데이터에 접근한다.

// sql
select * from member;
// jpql
select m from Member m;

특징

  • 엔티티 대상 쿼리
  • sql을 추상화 하여서 특정 DB문법에 의존하지 않음
  • 아주 복잡한 쿼리를 짜기에는 부적합
    • mybatis 또는 jdbc 등으로 네이티브 SQL을 사용해야함.
    • 네이티브 SQL을 사용하면 영속성 컨텍스트의 데이터를 적절한 시점에 flush하여 db를 갱신시켜주고 사용해야함.
  • 결국 SQL문으로 바껴서 쿼리됨

JPQL 사용법

기본 문법

select_문 :: =  
    select_절 
    from_절 
    [where_절] 
    [group by_절] 
    [having_절] 
    [order by_절]
update_문 :: = update_절 [where_절] 
delete_문 :: = delete_절 [where_절]

리턴 타입

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

결과 조회 API

  • query.getResultList() : 결과가 하나 이상일 때, List반환
  • query.getSingleResult() : 결과가 정확히 하나, 단일 객체 반환
    • 없거나, 하나 이상이면 Exception발생

파라미터 바인딩

파라미터 바인딩은 Prepared Statement와 매우 유사하다.

  • 이름 기준
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);

추천하는 방식은 이름 기준 바인딩이다. 위치 기준 바인딩은 중간에 파라미터가 추가 된다면 뒤에 번호들도 다 밀어 써야하는 단점이 있다.

프로젝션

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

스칼라 타입은 select m.name, m.age from Member m과 같이 스트링 컬럼, 숫자 컬럼 등 다양한 컬럼을 조회하는 것을 말하는데 이 때 리턴 타입을 특정해서 맞춰주기가 애매하다.

방법으로는 몇 가자가 있는데 개인적으론 DTO를 사용하는 것이 좋아보인다.

  • Query 타입으로 조회
  • Object[] 타입으로 조회
    • object[0] 문자, object[1]숫자 같은 방식
  • DTO사용
    • SELECT new package.UserDTO(m.username, m.age) FROM Member m
    • package이름까지 다 적어줘야함.
    • 조회 컬럼 순서와 타입이 일치하는 생성자가 필요

JPQL 쿼리 예문

페이징

JPQL은 페이징을 추상화한 API를 제공하여 DB에 종속적이지 않고 아주 간단하게 페이징 쿼리를 작성할 수 있다.

em.createQuery(SELECT m FROM Member m, Member.class)
	.setFristResult(0)
    .setMaxResults(10)

조인

JPQL도 조인을 지원하며 SQL과 아주 유사하다.

  • inner join
    • SELECT m FROM Member m [INNER] JOIN m.team t
  • outer join
    • SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
  • 세타 join
    • select count(m) from Member m, Team t where m.username = t.name

ON절을 사용한 조인

JPQL도 SQL과 동일하게 조인시 필더링을 위해 ON절을 사용할 수 있다.

SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A' 

서브쿼리

JPQL은 SQL과 비슷하게 서브쿼리도 지원하지만 하이버네트6 버전 이하는 FROM절에서 서브쿼리는 지원하지 않는다.

때문에 FROM절에 서브쿼리를 사용해야한다면 조인으로 풀어본다, 각각 쿼리를 여러번 날려서 조합한다, 네이티브 SQL을 활용한다 정도가 있다.

나중에 사용한다면 하이버네트6을 사용하도록 해야겠다.

CASE 조건

  • 범위 CASE식
select
 	case when m.age <= 10 then '학생요금' 
		when m.age >= 60 then '경로요금' 
		else '일반요금'
 	end
 from Member m
  • 단순 CASE식
select 
	case t.name 
		when '팀A' then '인센티브110%'
 		when '팀B' then '인센티브120%'
 		else '인센티브105%' 
	end 
from Team t
  • COALESCE : 하나씩 조회해서 null이 아니면 반환
// 엔티티의 m.username이 null이면 '이름 없는 회원'반환
select coalesce(m.username,'이름 없는 회원') from Member m
  • NULLIF : 두 값이 같으면 null, 다르면 원래 값 반환
    • IFNULL과 다르므로 헷갈리지 말자
// 엔티티의 m.username이 관리자면 null반환
select NULLIF(m.username, '관리자') from Member m

JPQL 함수

JPQL은 기본적인 ANSI함수는 다 제공 된다고 보면 되며, 특정 DB에 종속적이여서 제공되지 않는 함수는 사용자가 추가해서 사용할 수 있다.

hibernate6 이하

  • Dialect 구현 class생성
// 본인 DB에 맞는 Dialect상속 받아서 구현
public class MyH2Dialect extends H2Dialect {
    public MySQLDialect() {
        registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
    }
}
  • hibernate.dialect 변경
    • org.hibernate.dialect.H2Dialect에서 package.MyH2Dialect로 변경

hibernate6 이상

  • FunctionContributer의 구현 class생성
public class CustomFunctionContributor implements FunctionContributor {
    @Override
    public void contributeFunctions(FunctionContributions functionContributions) {
        functionContributions.getFunctionRegistry()
                .register("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
    }
}
  • src/main/resources/META-INF/services 경로에 org.hibernate.boot.model.FunctionContributor파일 생성
    • 파일명이 org.hibernate.boot.model.FunctionContributor임
  • org.hibernate.boot.model.FunctionContributor파일에 CustomFunctionContributor 등록
    • 패키지경로.CustomFunctionContributor 방식으로 저장.
profile
no-intelli 개발자 입니다. 그래도 intellij는 씁니다.

0개의 댓글