Java JPA JPQL과 Criteria API 완벽 가이드, 쿼리 작성부터 실전 활용까지
📌 객체 지향 쿼리 언어 JPQL과 타입 안전한 Criteria API 사용법을 한 번에 익히세요
안녕하세요.
Java로 데이터베이스를 다루면서 JPA를 사용하신다면, 데이터를 조회하고 조작하기 위해 JPQL(Java Persistence Query Language)과 Criteria API를 반드시 이해해야 합니다.
JPQL은 SQL과 유사하지만 엔티티 객체를 대상으로 동작하며, Criteria API는 타입 안전성과 동적 쿼리 작성에 강점을 갖고 있죠.
이 두 가지는 서로 보완적인 관계에 있기 때문에, 상황에 따라 적절히 선택하여 사용하는 것이 중요합니다.
이번 글에서는 JPQL의 기본 문법과 실전 예제, 그리고 Criteria API를 활용한 동적 쿼리 작성 방법까지 단계별로 살펴보겠습니다.
또한 두 방법의 장단점을 비교하여 프로젝트에서 어떤 방식이 더 적합한지도 안내드릴 예정입니다.
글을 끝까지 읽으시면, 복잡한 조회 로직도 객체 지향적으로 작성할 수 있는 자신감을 얻게 되실 거예요.
📋 목차
🔗 JPQL 기본 개념과 특징
JPQL(Java Persistence Query Language)은 JPA에서 제공하는 객체 지향 쿼리 언어로, SQL과 문법이 유사하지만 테이블이 아닌 엔티티 객체를 대상으로 동작합니다.
즉, JPQL은 데이터베이스의 테이블과 컬럼이 아닌 엔티티 클래스와 그 속성을 사용하여 쿼리를 작성합니다.
JPQL을 사용하면 데이터베이스 종류에 독립적인 코드를 작성할 수 있으며, JPA가 이를 SQL로 변환하여 실행합니다.
따라서 동일한 JPQL 쿼리가 MySQL, Oracle, PostgreSQL 등 다양한 데이터베이스에서 동일하게 동작할 수 있습니다.
📌 JPQL의 주요 특징
- 🗂️엔티티와 속성을 기준으로 쿼리 작성
- 🌐데이터베이스 벤더에 독립적
- 🔍객체 지향 탐색을 통한 조인 및 조건 지정 가능
- 📈JPQL을 SQL로 변환하여 실행하므로 실행 계획 확인 가능
📌 JPQL과 SQL의 차이
| 구분 | JPQL | SQL |
|---|---|---|
| 대상 | 엔티티 객체와 속성 | 테이블과 컬럼 |
| DB 의존성 | 독립적 | DBMS에 종속적 |
| 조인 방식 | 객체 관계 탐색 기반 | 관계형 조인 기반 |
💡 TIP: JPQL은 SQL에 비해 추상화 수준이 높지만, 복잡한 네이티브 쿼리가 필요한 경우에는 NativeQuery를 함께 활용할 수 있습니다.
🛠️ JPQL 문법과 실전 예제
JPQL의 문법은 SQL과 비슷하지만, 엔티티와 그 속성을 대상으로 작성한다는 점이 다릅니다.
SELECT, UPDATE, DELETE와 같은 키워드를 사용하며, FROM 절에는 반드시 엔티티 이름이 들어갑니다.
엔티티 이름은 클래스명이 기본이며, @Entity(name=”별칭”)을 통해 변경할 수도 있습니다.
📌 기본 SELECT 예제
String jpql = "SELECT m FROM Member m WHERE m.age > :age";
List<Member> members = em.createQuery(jpql, Member.class)
.setParameter("age", 20)
.getResultList();
위 예제에서는 Member 엔티티를 대상으로 20세 초과인 회원 목록을 조회합니다.
여기서 m.age는 데이터베이스 컬럼이 아니라 Member 클래스의 필드를 의미합니다.
📌 JOIN 사용 예제
String jpql = "SELECT o FROM Order o JOIN o.member m WHERE m.name = :name";
List<Order> orders = em.createQuery(jpql, Order.class)
.setParameter("name", "홍길동")
.getResultList();
여기서는 Order 엔티티에서 Member 엔티티를 조인하여, 특정 회원이 주문한 주문 목록을 조회합니다.
조인 대상은 테이블이 아닌 엔티티의 연관관계 필드입니다.
📌 페이징 처리
List<Member> members = em.createQuery("SELECT m FROM Member m ORDER BY m.name", Member.class)
.setFirstResult(0) // 시작 위치
.setMaxResults(10) // 조회 개수
.getResultList();
JPA는 페이징을 데이터베이스 방언에 맞게 자동으로 변환하므로, 개발자가 직접 LIMIT나 OFFSET을 작성할 필요가 없습니다.
⚠️ 주의: SELECT 절에는 엔티티 객체나 스칼라 타입을 명시해야 하며, 조인 시 반드시 관계 필드를 사용해야 합니다.
⚙️ Criteria API 개념과 장점
Criteria API는 JPA에서 제공하는 타입 안전(Type-safe)한 쿼리 작성 방법입니다.
문자열로 작성하는 JPQL과 달리, 메서드 체인과 빌더 패턴을 사용하여 컴파일 시점에 오류를 잡을 수 있다는 장점이 있습니다.
또한, 동적 쿼리를 작성하기에 적합해 복잡한 조건이 많은 검색 기능 구현 시 유용합니다.
Criteria API는 javax.persistence.criteria 패키지에 포함되어 있으며, 엔티티 매니저에서 CriteriaBuilder를 꺼내어 쿼리를 구성합니다.
SQL이나 JPQL처럼 문자열을 다루는 것이 아니라, 객체를 조립하듯 쿼리를 생성하는 방식이 특징입니다.
📌 Criteria API의 주요 장점
- ✅컴파일 시 문법 오류를 사전에 방지
- 🔄동적 쿼리 작성이 간편
- 🛠️IDE 자동완성을 활용한 개발 효율성 향상
- 🔍복잡한 조건과 조인을 안전하게 구성 가능
📌 Criteria API 기본 구조
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
cq.select(m).where(cb.greaterThan(m.get("age"), 20));
List<Member> members = em.createQuery(cq).getResultList();
이 예제에서는 Member 엔티티에서 나이가 20세 이상인 회원 목록을 조회하는 Criteria API 기본 구조를 보여줍니다.
쿼리 조건을 객체로 구성하므로, 조건 추가나 변경이 쉽고 안전합니다.
💎 핵심 포인트:
Criteria API는 특히 검색 조건이 많은 화면이나 필터 기능 구현 시 강력한 도구가 됩니다.
JPQL보다 장황할 수 있지만, 유지보수성과 안정성 측면에서 장점이 큽니다.
🔌 Criteria API로 동적 쿼리 작성
Criteria API의 가장 큰 장점 중 하나는 동적 쿼리 작성에 있습니다.
동적 쿼리는 검색 조건이 사용자 입력이나 상황에 따라 달라지는 경우 유용합니다.
JPQL로 작성하면 문자열을 조합해야 하므로 오류가 발생하기 쉽지만, Criteria API는 객체 지향적으로 조건을 조립할 수 있습니다.
📌 동적 조건 추가 예제
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
List<Predicate> predicates = new ArrayList<>();
if (name != null) {
predicates.add(cb.equal(m.get("name"), name));
}
if (minAge != null) {
predicates.add(cb.greaterThanOrEqualTo(m.get("age"), minAge));
}
cq.select(m).where(cb.and(predicates.toArray(new Predicate[0])));
List<Member> results = em.createQuery(cq).getResultList();
위 예제에서는 사용자가 입력한 이름과 최소 나이를 조건으로 필터링하는 동적 쿼리를 작성했습니다.
조건이 존재할 때만 Predicate를 추가하므로, 조건 유무에 따라 쿼리가 유연하게 변합니다.
📌 복잡한 조인과 그룹핑
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
Root<Order> o = cq.from(Order.class);
Join<Order, Member> m = o.join("member", JoinType.INNER);
cq.multiselect(m.get("name"), cb.count(o))
.groupBy(m.get("name"))
.having(cb.greaterThan(cb.count(o), 5));
List<Object[]> results = em.createQuery(cq).getResultList();
이 예제는 회원별 주문 건수를 집계하고, 주문이 5건 이상인 회원만 필터링하는 복잡한 그룹핑 쿼리를 Criteria API로 구현한 것입니다.
이처럼 조인, 그룹핑, 조건 절을 모두 객체 방식으로 작성할 수 있습니다.
⚠️ 주의: Criteria API는 코드가 길어질 수 있으므로, 재사용 가능한 메서드나 Builder 패턴을 활용해 가독성을 높이는 것이 좋습니다.
💡 JPQL과 Criteria API 비교
JPQL과 Criteria API는 모두 JPA에서 강력한 조회 기능을 제공하지만, 사용 목적과 장단점이 다릅니다.
JPQL은 직관적이고 간단한 쿼리를 작성할 때 적합하며, Criteria API는 동적 쿼리나 타입 안전성이 중요한 경우 유리합니다.
상황에 따라 두 방식을 적절히 혼합해서 사용하는 것이 실무에서의 최적 전략입니다.
📌 장단점 비교 표
| 구분 | JPQL | Criteria API |
|---|---|---|
| 문법 | SQL 유사, 직관적 | 메서드 체인 기반, 복잡 |
| 가독성 | 짧고 명확 | 길지만 구조적 |
| 동적 쿼리 | 문자열 조합 필요 | 객체 조립 방식, 안전함 |
| 타입 안전성 | 낮음 (런타임 오류 가능) | 높음 (컴파일 시점 검증) |
📌 선택 가이드
- ✅간단한 조회 → JPQL 추천
- 🔄검색 조건 다양/변동 → Criteria API 추천
- ⚠️복잡한 쿼리는 가독성을 위해 별도 메서드나 QueryDSL 고려
💬 실무에서는 JPQL과 Criteria API를 상황에 맞게 혼합 사용하는 것이 가장 유연한 접근 방식입니다. 예를 들어, 단순 조회는 JPQL로, 필터 조건이 많은 화면은 Criteria API로 처리하는 식입니다.
❓ 자주 묻는 질문 (FAQ)
JPQL과 SQL의 가장 큰 차이는 무엇인가요?
Criteria API가 JPQL보다 나은 경우는 언제인가요?
Criteria API 코드가 너무 길어질 때는 어떻게 하나요?
JPQL에서 페이징 처리는 어떻게 하나요?
Criteria API에서 조인은 어떻게 하나요?
JPQL에서 엔티티 이름 대신 테이블 이름을 사용할 수 있나요?
JPQL에서 파라미터는 어떻게 바인딩하나요?
Criteria API가 모든 JPQL 기능을 지원하나요?
📌 JPQL과 Criteria API 활용 핵심 정리
이번 글에서는 JPA에서 제공하는 두 가지 강력한 조회 방식, JPQL과 Criteria API를 비교하며 살펴봤습니다.
JPQL은 SQL과 유사한 문법으로 직관적이며 간단한 쿼리 작성에 유리하고, Criteria API는 동적 쿼리와 타입 안전성이 필요한 경우에 적합합니다.
각 방식의 장단점을 이해하고, 상황에 맞게 선택하는 것이 중요합니다.
실무에서는 단순 조회나 정적인 쿼리는 JPQL로 처리하고, 필터 조건이 많거나 변동이 잦은 화면에서는 Criteria API를 사용하는 전략이 효과적입니다.
또한, Criteria API는 코드가 길어지는 단점이 있으므로, 재사용 가능한 빌더 메서드나 QueryDSL 같은 대안을 고려하는 것이 좋습니다.
이렇게 하면 유지보수성과 가독성을 모두 확보할 수 있습니다.
🏷️ 관련 태그 : Java, JPA, JPQL, CriteriaAPI, ORM, 동적쿼리, 타입안전성, 엔티티조회, 데이터베이스, QueryDSL