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의 기본 문법에 대해서 차근차근 알아가보자.
'Back-end > JPA 개념' 카테고리의 다른 글
24. JPQL - 프로젝션(Projection, Select) (0) | 2022.04.22 |
---|---|
23. JPQL - 기본 기능(파라미터 바인딩, 결과 조회, 쿼리의 종류) (0) | 2022.04.21 |
21. 값 타입(4) - 값 타입과 컬렉션 (0) | 2022.04.19 |
20. 값 타입(3) - 객체 비교와 equals() 재정의 (0) | 2022.04.18 |
19. 값 타입(2) - 문제점과 불변 객체 (0) | 2022.04.15 |
댓글