Dev/JPA

JPQL 의 개념과 간단한 사용 방법 ( 1 / 2 )

린네의 2024. 2. 17. 01:57

 

 

게시글을 하나로 통합할까 하다가,  이론적인 부분과 실제 사용하는 구문을 분리하는 게 좋겠다는 생각이 들어서  두 개로 나눴다.

 

이 게시글은 JPQL 의 개념에 대한 내용이 주가 되었으니 참고 바란다. 

 

1.  JPQL 이 왜 필요한가?

 

JPA는 엔티티 객체를 중심으로 개발한다.  이렇게 엔티티 객체를 대상을 검색하게 되면 데이터 검색 시에도 테이블이 아닌 엔티티 객체를 대상으로 검색해야 한다.  

 

 

애플리케이션에서, 검색 시 필요한 데이터만 DB에서 불러 오려면 결국 검색조건이 포함된 SQL 이 필요한데  이때 JPA 에서 SQL 을 추상화한 JPQL 이라는 객체 지향 쿼리 언어를 제공한다. JPQL을 사용하면 엔티티 객체 대상으로 쿼리를 실행해서 원하는 데이터를 뽑아올 수 있다!

 

  •  참고 -  SQL 은 DB 에서 테이블을 기준으로 데이터를 조회하는 언어이다. 

 

 

 

 

2.  JPQL 을 사용해 보자

 

JPQL 은, 엔티티 객체를 대상으로 조회하기 때문에 SQL 문과 유사하지만 From 뒤에  존재하는 객체명이 와야 한다. 

 

간단한 예시는 다음과 같다.

em.createQuery (
    "select m From Member m where m.username like '%kim%'",
    Member.class 
).getResultList();

 

기존에 MyBatis를 사용하던 입장에서, 이렇게 사용하게 되면 문법적으로 다른 점은 크게 없지만 동적쿼리를 짤 때 굉장히 불편하겠다고 생각했다.

 

 그래서 hibernate에서는 대안을 제공하는데, 바로 criteria 다.

 

  •  criteria 사용하기
CriteriaBuilder cb = em.getCriteriaBuilder();
criteriaQuery<Member> query = cb.createQuery(Member.class);

Root<Member> m = query.from(Member.class)

query.select(m).where(cb.equal(m.get("username"), "kim"));
em.createQuery(cq).getResultList();


query.select(m). where(cb.equal(m.get("username"), "kim")); 

 

코드를 보면 username 항목이 kim 인 사용자를 검색하고 있음을 알 수 있다.  공식적으로 제공하기도 하고,  기본 JPQL 보다는 동적 쿼리를 편하게 짤 수 있지만  criteria 도 사실 문법면에서 굉장히 불편하다.

 

  • 네이티브 sql 사용하기
 em
 .createNativeQuery("select MEMBER_ID, city, street, zipcode, username from Member")
 .getResultList();

 

 

 

또 다른 방법으로는 네이티브 SQL 이 있다. 다음과 같이 실제 SQL을 대입해서 사용이 가능하다.

 

 

JPQL 사용 시 주의해야 할 것들

  • 엔티티이름을 사용한다. 테이블 이름이  아니다.   ( @Entity에 name을 직접 지정할 수도 있지만 일반적으로는 클래스이름을 사용한다. )
  •  별칭은 필수다. ( as는 생략 가능 )

 

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");

 

 


JPQL에서 select 절에 조회할 대상을 지정하는 것을 의미하는 단어로 프로젝션이라는 단어를 사용한다.  

 

프로젝션 종류에는 엔티티프로젝션, 임베디드 타입 프로젝션, 스칼라 타입 프로젝션이 있다.

 

  • 엔티티 프로젝션

      1) Member에 있는 모든 항목(필드/속성) 조회하기 : select m from Member m
      2) Member 에 있는 항목 중 Team 만 조회하기  : select m.team from Member m

List<Member> result = em.createQuery("select m from Member m", Member.class)
    .getResultList();


List<Team> result = em.createQuery("select m.team from Member m", Team.class)
    .getResultList();

 

 

  위 코드에서  m.team을 가져올 때, 실제 sql 쿼리 조회 시 Team 이랑 Member 랑 조인이 발생하게 된다. 결과는 원하는 값이 출력되겠지만 사실 이러한 코드는 좋지 않은 코드다. 왜냐하면  내부적으로 조인이 발생한다는 게 직관적으로 보이지 않기 때문이다. ( 이런 조인을 묵시적 조인이라고 한다. 묵시적 조인과 반대되는 조인은 명시적 조인으로, 실무에선 명시적 조인을 사용하도록 하자 )

 

그렇다면 어떻게 하는 게 좋을까? 내부적으로 조인이 발생하니, 조인을 사용해서 작성해 주면 된다.

 

다음 코드를 보자.

 

List<Team> result = em.createQuery("select t from Member m join m.team t", Team.class)
.getResultList();

 

 

select m.team from Member m으로 조회하든 select t from Member m join m.team t으로 조회 하든 결과 값은 동일하지만 코드를 작성할 때 어떻게 쿼리가 나갈지 예측되게 구현해야 한다.

 

 

  • 임베디드 타입 프로젝션 

       1) Member의 address 항목 조회 하기 : select m.address from Member m
     
 이번에는 address를 조회해보자.  엔티티의 주인으로부터 조회해야 한다.
  Member 가 엔티티의 주인이 되므로 select m from Address m  은 사용 시 오류가 난다.   ( 여기를 누르면 임베디드 관련 게시글을 볼 수 있습니다.  ) 

 

실제로 select m from Address m을 하게 되면 다음과 같이 실행된다.

em.createQuery("select m from Address m", Address.class)
                    .getResultList();

 

  • 실제 오류 내용

    java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Address is not mapped [select m from Address m]

 

 

  • 스칼라 타입 프로젝션 

      1)  Member 항목 중 username과 age 조회하기 : select m.username, m.age from Member m1

 

 스칼라 타입 프로젝션은 일반 SQL과 가장 유사한 프로젝션이다.  참고로 데이터 조회 시  앞에 distinct를 넣으면 중복 제거를 사용할 수 있다.  (  select distinct m.username, m.age from Member m1  )

 

  • 여러 값을 조회하는 프로젝션

     username, age를 조회한 것처럼 프로젝션은 한 번에 값을 조회할 수 있다. 이때  각각 Query 타입,
     Object [] 타입, new 명령어를 통한 조회 방법이 있다.


      new 명령어를 사용하면, 엔티티 항목이 아니라 원하는 DTO 형태로 감싸서 조회할 수 있는데, 예시는 다음과 같다.

// dto 로  new 생성자를 써서 출력하는 방법
List<MemberDTO> resultList = em.createQuery(" select new jpql.MemberDTO(m.username, m.age) from Member m ").getResultList();
MemberDTO memberDTO = resultList.get(0);



 

 

 

3.  동적쿼리가 불편하다면? 대안으로 QueryDSL 사용을 사용해 보자.

 

 위에서 간단한 조건임에도 불구하고,  JPQL과 criteria를 사용하면 굉장히 불편하다는 것을 알 수 있다. 이럴 때 등장한 것이 QueryDSL이다.  QueryDSL의 장점은 다음과 같다.

 

 

장점

  • 문자가 아닌 자바코드로 jpql을 작성할 수 있다
  • JPQL에 대한 빌더 역할을 해준다
  • 컴파일 시점에 문법 오류를 발견할 수 있다
  • 동적 쿼리 작성이 편리하다

 

사실 실제 개발해 본 입장에서 동적 쿼리는 다른 것 보다 MyBatis 가 압도적으로 편리했다. 그럼에도 불구하고 내가 가장 큰 메리트를 느낀 부분은 컴파일 시점에 문법 오류를 발견할 수 있다는 점이었다. 

 

 

 

다음글은 여기로 

2024.02.17 - [개발/jpa] - JPQL 의 개념과 간단한 사용 방법 ( 2 / 2 )
 

JPQL 의 개념과 간단한 사용 방법 ( 2 / 2 )

이전 포스팅에서 이어집니다. 2024.02.17 - [개발/jpa] - JPQL의 개념과 간단한 사용 방법 ( 1 / 2 ) JPQL 의 개념과 간단한 사용 방법 ( 1 / 2 ) 게시글을 하나로 통합할까 하다가, 이론적인 부분과 실제 사

zigo-autumn.tistory.com