본문 바로가기
Back-end/JPA 개념

22. JPQL - JPQL에 대하여

by devraphy 2022. 4. 20.

0. 개요

- 이전 포스팅까지 기본적인 JPA의 개념과 기능에 대해서 알아보았다.

- JPA는 객체 간의 관계를 RDB 형태로 변환 및 매핑을 돕는 기술이다.

- 그러므로 JPA는 객체 중심적으로 작성된 코드를 RDB에 적용할 수 있도록 해당 코드를 쿼리문으로 변환하는 역할을 한다. 

 

- 전반적인 과정을 보면 근본적인 개념은 달라지지 않았다.

- 개발자가 쿼리문을 직접 작성하지 않고 이를 객체 지향적 코드로 작성할 뿐, 내부적으로는 쿼리문이 발생하기 때문이다. 

- 즉, 객체 중심적 코드를 이용하여 자유자재로 쿼리를 구현할 수 있어야 한다.

 

- 지금까지 객체 간의 관계를 RDB의 형태로 전환하는 방법을 배웠다.

- 그러나 실무에서는 훨씬 복잡한 관계를 구현하는 경우가 대부분이다.

- 복잡한 관계를 JPA에서 제공하는 기본 메서드를 이용하여 표현하는 것은 한계가 있다.

 

- 이와 같은 이유로 JPQL을 사용한다.

- JPQL은 객체 중심적 문법으로 표현하는 SQL이라고 할 수 있다.

- 이를 어떻게 사용하는지, 어떤 문법을 기반으로 하는지 등 앞으로 차근차근 알아볼 예정이다.  

 

1. JPA가 지원하는 다양한 쿼리

- JPA는 JPQL을 기반한 다양한 쿼리 서비스를 지원한다. 

   ex) JPQL, JPA Criteria, QueryDSL, Native SQL(= DB 종속적인 쿼리를 작성) 등

 

- 이처럼 다양한 쿼리 서비스를 지원하는 이유는, JPA만으로 100% 모든 쿼리를 커버할 수 없기 때문이다.

- 그러나 그 중심에는 JPQL이 있다. 즉, 특정 상황을 제외하고는 대부분 JPQL을 이용하여 커버할 수 있다.

 

2. JPQL의 사용

a) 이미 JPQL을 사용하고 있다.

- 이전 포스팅에서 다음과 같은 메서드를 사용했다. 

EntityManager.find();

Player player1 = new Player();
player1.get();

 

- find() 또는 get()과 같은 메서드는 JPQL에서 지원하는 기본적인 메서드다. 

- 즉, 이미 JPQL을 사용하고 있는 것이다. 

 

b) JPQL이 필요한 이유

- JPA는 객체(= Entity) 중심적으로 코드를 작성하도록 도와준다.

- 그러므로 검색(= 조회, select)에서 한계에 부딪힌다. 

 

- 더불어, JPA를 사용하므로 테이블이 아닌 객체(= Entity)를 대상으로 검색을 수행한다. 

- 그러므로 모든 DB 테이블을 객체로 변환해서 검색해야 하는데, 사실상 이는 불가능하다. 

 

- 즉, SELECT 쿼리를 수행할 때 함께 쓰이는 조건문(= where 절)을 작성하는 것에 한계를 보인다.

- 즉, DB로부터 필요한 데이터만 필터링하기 위해서는 검색 조건(= where 절)이 포함된 쿼리를 작성해야 한다. 

 

- 이와 같은 이유로 SQL을 추상화한 JPQL(= 객체 지향 쿼리 언어)을 필요로 한다. 

 

c) JPQL의 특징

1. JPQL은 ANSI 표준에서 지원하는 쿼리 명령문을 모두 제공한다. 

   ex) SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 등

 

2. JPQL은 SQL을 추상화하여 객체 중심적, 객체 지향적 쿼리 언어다. 

   → 즉, 테이블이 아닌 객체를 대상으로 검색을 수행하는 쿼리 언어다. 

   → 이는 JPQL과 SQL을 구분할 수 있는 특성이다. 

 

3. JPQL은 SQL을 추상화한 것으로, 특정 DB에 의존적 또는 종속적이지 않다. 

  → 즉, DB 사용에 있어서 자유롭다. 

 

- 즉, JPA를 통해 작성된 JPQL은 SQL로 번역된다. 

 

3. JPQL Criteria

a) JPQL Criteria가 필요한 이유 - 동적 쿼리

- 일반적으로 JPQL을 사용할 때에는 다음과 같이 작성한다. 

String query = "select p from Player as p where p.playername like '%park%'";

 

- 여기서 중요한 점은, JPQL은 문자열이라는 것이다. 

- 문자열이기 때문에 동적 쿼리를 작성하는 것이 어렵다. 간단한 예시를 보자. 

 

  ex) 만약 where 절을 동적 쿼리로 작성한다고 해보자. 

     → where절 부분을 slicing 하여 다른 문자열과 조합하도록 해야 한다. 

     → 만약 where절 내부의 조건이 1개 이상인 경우에는 and 문자열을 추가한다.

     → 문자열 조합 시 띄어쓰기를 항상 신경 써야 한다.  

 

- 이처럼 동적 쿼리를 작성하기 위해 문자열을 다루는 부분이 굉장히 까다롭다. 

  → 동적 쿼리의 측면에서 보면 iBatis, MyBatis가 동적 쿼리를 작성하기 편리하다는 장점이 있다.

 

- 이와 같이, 동적 쿼리를 편리하게 작성하기 위해 JPQL Criteria를 사용한다. 

 

b) JPQL Criteria의 기본 기능 

- 앞서, JPQL Criteria는 동적인 JPQL을 작성하기 위해서 사용한다고 언급했다.

- 이를 위해서 JPQL Criteria는 from 또는 where와 같은 문법을 메서드의 형태로 제공한다. 

 

- 다음 예시 코드를 살펴보자. 이해할 필요는 없다. 다만, 어떤 형태를 갖는지 간단하게 이해해보자. 

//Criteria 사용 준비
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Player> query = cb.createQuery(Player.class);

//루트 클래스 (조회를 시작할 클래스)
Root<Player> player = query.from(Player.class);

//쿼리 생성 
CriteriaQuery<Player> cq = 
      query.select(player).where(cb.equal(player.get("playername"), “park”));
      
List<Player> resultList = em.createQuery(cq).getResultList();

 

- 위의 코드에서 알 수 있듯이, JPQL Criteria는 where() 또는 from()과 같은 메서드를 제공한다.

 

- 이처럼 쿼리문을 자바로 작성하므로 몇 가지 장점이 있다.

  → 문법적 오류를 컴파일 레벨에서 잡아낼 수 있다. 

  → 조건문 등을 사용하여 동적 쿼리를 쉽게 작성할 수 있다. 

 

- 그러나, 가독성이 매우 떨어지므로 유지보수 시 이해하기 어렵다는 단점이 있다.

- 가독성이 떨어지는 이유는 코드 자체가 너무 객체 중심적이다 보니 쿼리문스럽지 않기 때문이다. 

- 즉, 실용성이 떨어진다. 

 

- JPQL Criteria는 JPA 표준 스펙 중 하나이지만, 이와 같은 이유로 현업에서 잘 사용하지 않는다. 

 

4. QueryDSL 

a) QueryDSL 이란?

- QueryDSL은 JPQL의 작성을 도와주는 오픈소스 라이브러리다. 

- QueryDSL은 JPQL을 사용하기에 편리한 라이브러리고 인식된다. 

 

b) QueryDSL의 기본 형태

- QueryDSL은 다음과 같은 기본 구조를 가진다. 

private final JPAQueryFactory queryFactory;

public List<Player> findAllPlayer(Player player) {
   
   return queryFactory
               .select(player)
               .from(player)
               .where(player.name.like("park");
}

 

- 위의 예시 코드는 QueryDSL을 사용하기 위해 온전한 구성을 갖춘 코드는 아니다.

- 단순히 QueryDSL의 형태를 보여주기 위한 코드라는 것을 염두에 두자.

 

c) QueryDSL의 특징 

- 자바 코드로 작성되므로 컴파일 시점에서 문법 오류를 발견할 수 있다.

- 자바 코드로 작성되므로 동적 쿼리를 작성이 편리하다.

- 작성된 자바 코드가 쿼리스럽고 직관적인 형태이므로 사용하기 쉽다.

- 유지보수에 용이하다. 

 

- 이와 같은 이유로 현업에서 QueryDSL을 사용하는 것을 권장한다. 

 

5. Native SQL

a) Native SQL을 사용하는 이유

- Native SQL은 이름 그대로 SQL을 직접 작성하는 방식을 말한다.

 

- 앞서 모든 테이블을 객체로 변환하여 검색하는 것이 이상적이지만 불가능하다고 언급하였다.

- 이처럼 JPQL로 해결할 수 없는 상황에서 사용하는 방법이 Native SQL이다.

   ex) 특정 DB에 종속적인 쿼리문을 작성해야 하는 경우

   ex) SQL 방언으로 커버가 불가능한 SQL을 작성해야 하는 경우

 

- 즉, raw SQL을 개발자가 직접 작성하는 것을 의미한다. 

- 다음 예시코드를 보면서 Natvie SQL을 사용하는 기본적인 형태에 대해서 알아보자. 

String sql = "SELECT * FROM PLAYER WHERE NAME = 'park'";

List<Player> result = em.createNativeQuery(sql, Player.class).getResultList();

 

- 이처럼 Native SQL은 문자열로 구성된다. 

 

6. JDBC 직접 사용

a) JDBC를 이용한 쿼리 생성

- 사실 현업에서는 Native SQL 방식을 잘 사용하지 않는다.

- Native SQL의 경우, 쿼리문을 문자열로 작성하기 때문에 발생하는 문제를 안고 가기 때문이다.

 

- 더불어, 개발자는 항상 기술을 선택함에 있어서 확장 가능성을 염두에 두어야 한다. 

- 이를 기반으로 생각했을 때, 문자열로 쿼리문을 작성하는 방식은 효율적이지 않다.

 

- 그러므로 JDBC Connection, Spring의 JdbcTemplate, MyBatis 등을 함께 사용하는 것을 권장한다.

 

b) 주의 사항

- JPA에서는 flush()를 실행해야 DB에 데이터가 들어간다.

- 앞서 라이프 사이클에서 배웠지만, flush()는 commit 시점 또는 쿼리가 실행되는 시점에서 수행된다. 

- 그러나 JDBC를 이용하는 경우, JPA의 기능을 사용하는 것이 아니므로 flush()가 실행되지 않는다.

- 그러므로 적절한 시점에서 강제로 flush()를 실행해야 한다는 것을 주의하자. 

 

5. 마무리하며

- 이번 포스팅에서는 JPQL이 왜 필요한지, 어떤 식으로 운용하는지에 대해서 알아보았다.

- QueryDSL을 소개하면서, 아마도 배울 것이 또 하나 늘었다고 생각할 수 있다.

 

- QueryDSL의 근본은 JPQL이다.

- 그러므로 JPQL의 기본적인 구조와 개념을 이해하고 있다면, 사용하는 것은 어렵지 않다.

- 앞으로 JPQL의 기본 문법에 대해서 차근차근 알아가보자.

댓글