JPQL (Java Persistence Query Language) 완벽 가이드
JPQL(Java Persistence Query Language)은 JPA(Java Persistence API)에서 엔티티 객체를 대상으로 하는 쿼리 언어로, SQL과 유사하지만 데이터베이스 테이블이 아닌 엔티티 객체를 대상으로 작동한다는 점에서 차이가 있다. JPQL은 데이터베이스에 의존하지 않는 객체 지향 쿼리로, JPA를 사용하는 애플리케이션에서 데이터베이스와 독립적으로 유지보수가 용이한 코드를 작성하는 데 필수적이다.
JPQL 기본 문법
JPQL은 기본적으로 SQL과 문법이 유사하지만, 테이블 대신 엔티티 객체를 대상으로 조회를 수행한다.
// JPQL 예시
String jpql = "SELECT m FROM Member m WHERE m.age > :age";
TypedQuery<Member> query = em.createQuery(jpql, Member.class);
query.setParameter("age", 20);
List<Member> resultList = query.getResultList();
위 예제에서 JPQL 쿼리 문자열은 SELECT m FROM Member m WHERE m.age > :age이다. 이 쿼리는 Member 엔티티를 대상으로 age가 20을 초과하는 모든 회원을 조회한다. 중요한 점은 SQL의 테이블과 컬럼이 아닌, 엔티티와 필드를 사용한다는 것이다.
JPQL과 SQL의 차이점
JPQL SQL
엔티티 객체를 대상으로 조회 | 테이블을 대상으로 조회 |
필드명을 사용 | 컬럼명을 사용 |
데이터베이스 독립적 | 특정 데이터베이스 종속 |
예를 들어, SQL에서 다음과 같이 테이블을 조회한다고 가정해보자:
SELECT * FROM member WHERE age > 20;
이를 JPQL로 변환하면 다음과 같다:
SELECT m FROM Member m WHERE m.age > 20;
JPQL은 엔티티 객체에 의존하므로, SQL과 비교하여 데이터베이스의 변경 사항에 덜 민감하다.
JPQL의 주요 기능
1. SELECT 쿼리
JPQL의 SELECT 쿼리는 SQL의 SELECT와 유사하지만, 엔티티 객체를 대상으로 한다.
String jpql = "SELECT m FROM Member m";
List<Member> members = em.createQuery(jpql, Member.class).getResultList();
2. WHERE 절
조건을 추가하려면 WHERE 절을 사용한다.
String jpql = "SELECT m FROM Member m WHERE m.username = :username";
TypedQuery<Member> query = em.createQuery(jpql, Member.class);
query.setParameter("username", "john_doe");
Member member = query.getSingleResult();
3. JOIN
JPQL은 엔티티 간 연관 관계를 탐색할 때 JOIN을 사용한다.
String jpql = "SELECT o FROM Order o JOIN o.member m WHERE m.username = :username";
TypedQuery<Order> query = em.createQuery(jpql, Order.class);
query.setParameter("username", "john_doe");
List<Order> orders = query.getResultList();
4. GROUP BY와 HAVING
JPQL에서도 GROUP BY와 HAVING을 사용할 수 있다.
String jpql = "SELECT c.name, COUNT(p) FROM Category c JOIN c.products p GROUP BY c.name HAVING COUNT(p) > 10";
List<Object[]> results = em.createQuery(jpql).getResultList();
5. 서브 쿼리
JPQL은 WHERE, HAVING, FROM 절에 서브 쿼리를 사용할 수 있다.
String jpql = "SELECT m FROM Member m WHERE m.age > (SELECT AVG(m2.age) FROM Member m2)";
List<Member> members = em.createQuery(jpql, Member.class).getResultList();
JPQL 사용 시 주의사항
- 지연 로딩과 N+1 문제: JPQL로 연관된 엔티티를 조회할 때 LAZY 로딩 설정을 주의해야 한다. 특히 컬렉션을 조회할 때 N+1 문제가 발생할 수 있으므로, FETCH JOIN을 고려해야 한다.
String jpql = "SELECT m FROM Member m JOIN FETCH m.orders";
List<Member> members = em.createQuery(jpql, Member.class).getResultList();
- 엔티티 필드명 변경 시 영향: JPQL은 엔티티의 필드명을 사용하므로, 엔티티 필드명이 변경되면 JPQL 쿼리도 수정해야 한다. 이를 방지하려면 IDE의 리팩토링 기능을 활용하는 것이 좋다.
- 네이티브 쿼리와의 차이점: 네이티브 쿼리는 데이터베이스에 종속되므로, JPQL로 작성할 수 없는 복잡한 쿼리나 데이터베이스 고유 기능을 사용할 때만 사용해야 한다.
String nativeQuery = "SELECT * FROM member WHERE age > 20";
List<Member> resultList = em.createNativeQuery(nativeQuery, Member.class).getResultList();
JPQL의 장점과 단점
장점
- 데이터베이스 독립적인 코드 작성 가능
- 객체 지향적인 접근으로 유지보수가 용이
- 엔티티를 직접 대상으로 하므로 생산성 향상
단점
- 복잡한 쿼리 작성 시 한계점
- 엔티티 필드명 변경 시 쿼리 수정 필요
- 네이티브 쿼리 대비 성능 최적화에 제약
실무에서의 JPQL 활용 팁
- 동적 쿼리 작성 시 QueryDSL 사용 권장: JPQL로 동적 쿼리를 작성하는 것은 코드가 복잡해질 수 있으므로, QueryDSL과 같은 라이브러리를 활용하면 보다 직관적이고 안전하게 동적 쿼리를 생성할 수 있다.
- 복잡한 쿼리는 네이티브 쿼리로 전환: JPQL로 작성이 어려운 복잡한 쿼리는 네이티브 쿼리로 전환하는 것이 효율적이다.
- 페치 전략 설정: JPQL로 데이터를 조회할 때 페치 전략을 적절히 설정하여 성능 문제를 방지해야 한다. 특히 대량 데이터 조회 시 페이징 처리를 반드시 고려해야 한다.
String jpql = "SELECT m FROM Member m ORDER BY m.id ASC";
List<Member> members = em.createQuery(jpql, Member.class).setFirstResult(0).setMaxResults(10).getResultList();
결론
JPQL은 JPA 애플리케이션에서 가장 중요한 데이터 조회 수단 중 하나로, SQL과 유사하지만 객체 지향적인 접근 방식을 통해 유지보수성과 확장성을 제공한다. JPQL을 효율적으로 사용하기 위해서는 기본 문법뿐만 아니라, 성능 최적화 기법과 실무 경험을 바탕으로 한 활용 팁을 반드시 숙지해야 한다.
'백엔드 프레임워크 > JPA & Mybatis' 카테고리의 다른 글
[JPA] 엔티티 매핑 (Entity Mapping) (0) | 2025.01.11 |
---|---|
[JPA] JPA 기본 사용법 (0) | 2025.01.11 |