Spring/강의

[📘 심화 Spring] 3-3. JPQL (Java Persistence Query Language)

가지코딩 2025. 6. 9. 21:49

📘 목차

  1. JPQL 등장 배경과 한계
  2. 기본 문법
  3. 반환 타입 및 결과
  4. 파라미터 바인딩
  5. Embedded Type
  6. 프로젝션 (Projection)
  7. 페이징 (Paging)
  8. JOIN
  9. CASE 식
  10. 함수

1. JPQL 등장 배경과 한계

객체 중심 설계와 JPA 의 한계

  • JPA는 객체(Entity) 중심의 데이터베이스 접근을 지향한다.
  • 단순 조회는 em.find() 또는 객체 간 참조 탐색으로 가능하다.
  • 모든 데이터를 객체로 불러올 수는 없음
  • 특정 조건 기반의 복잡한 검색은 불가능

조건 기반 조회가 필요한 상황에서는 SQL 같은 쿼리가 필요해짐

 

 

JPQL의 등장

  • JPQL(Java Persistence Query Language)은 객체(Entity)를 대상으로 SQL과 유사한 문법으로 작성된 객체지향 쿼리 언어이다.
  • 특징
    • Entity와 필드를 대상으로 질의 (테이블/컬럼 X)
    • SQL 추상화를 통해 다양한 DB에서 동일하게 사용 가능
    • 영속성 컨텍스트를 활용한 1차 캐시, 지연 로딩 가능
    • 타입 안정성 보장 (컴파일 시점 오류 확인)
String jpql = "SELECT p FROM Product p WHERE p.price > :minPrice";
List<Product> products = em.createQuery(jpql, Product.class)
                           .setParameter("minPrice", 1000)
                           .getResultList();

 

 

JPQL의 한계와 동적 쿼리

  • JPQL은 문자열로 작성된 정적 쿼리 → 조건이 달라질 경우 문자열 직접 조합 필요
String jpql = "SELECT p FROM Product p WHERE 1=1";
if (name != null) jpql += " AND p.name = :name";
if (price != null) jpql += " AND p.price = :price";

 

* 문제점

  • 가독성 저하
  • 유지보수 어려움
  • 문법 오류 컴파일 시 검출 불가
  • 조건이 많아질수록 복잡성 증가

→ 따라서 JPQL만으로 복잡한 동적 쿼리를 처리하는 것은 한계

 

 

JPA에서 지원하는 쿼리 방식

  • JPA는 JPQL 외에도 다양한 쿼리 방법을 제공하며, 각각의 목적과 사용처가 다르다.
방식 설명 특징
JPQL Entity 기반의 정적 쿼리 SQL 유사 문법, 객체 지향
QueryDSL Java 코드 기반 쿼리 빌더 동적 쿼리에 최적, 가독성 좋고 유지보수 쉬움
JPA Criteria API JPQL을 코드로 생성 타입 안정성은 높지만 가독성 낮고 복잡
Native SQL SQL 직접 사용 DB 종속적, 복잡 쿼리/튜닝에 유리

 

* 실무에서는 상황에 맞게 병행 사용

 

  • 단순 정적 쿼리 → JPQL
  • 복잡한 조건 쿼리 → QueryDSL
  • 성능 최적화 또는 특수 쿼리 → Native SQL

 


2. 기본 문법

  • Entity와 필드를 대상으로 쿼리 작성
  • SQL과 유사하지만 테이블이 아닌 객체(Entity) 단위로 질의
SELECT e FROM Employee e WHERE e.salary > 5000

 

  • SELECT 절: 반환할 엔티티 또는 필드 지정
  • FROM 절: 조회 대상 엔티티 지정 (테이블 X)
  • WHERE, GROUP BY, HAVING, ORDER BY 등 SQL 문법과 유사하게 사용

3. 반환 타입 및 결과

  • TypedQuery<T>: 명확한 반환 타입 지정
  • Query: 반환 타입 미지정
반환 대상 반환 타입
Entity Entity 객체 (영속 상태)
필드 1개 단일 값 (ex. String, Integer)
필드 여러 개 Object[] 또는 DTO

 

// Entity 반환
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);

// 단일 필드
TypedQuery<String> query = em.createQuery("SELECT m.name FROM Member m", String.class);

// 복수 필드
List<Object[]> result = em.createQuery("SELECT m.name, m.age FROM Member m").getResultList();

4. 파라미터 바인딩

  • 이름 기준 바인딩 권장 (:paramName)
/* 이름 기준 바인딩 */
String jpql = "SELECT m FROM Member m WHERE m.name = :name AND m.age >= :age";
TypedQuery<Member> query = em.createQuery(jpql, Member.class);
query.setParameter("name", "홍길동");
query.setParameter("age", 20);
List<Member> result = query.getResultList();
/* 위치 기준 바인딩 */
String jpql = "SELECT m FROM Member m WHERE m.name = ?1 AND m.age >= ?2";
TypedQuery<Member> query = em.createQuery(jpql, Member.class);
query.setParameter(1, "홍길동");
query.setParameter(2, 20);
List<Member> result = query.getResultList();

5. Embedded Type

  • @Embeddable로 선언된 값 타입도 JPQL에서 사용 가능
@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;
}

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @Embedded
    private Address address;
}
SELECT m FROM Member m WHERE m.address.city = 'Seoul'

6. 프로젝션 (Projection)

  • 전체 엔티티, 단일 필드, 여러 필드를 선택할 수 있음
  • DTO 프로젝션은 new 키워드로 가능
// Entity 전체
SELECT m FROM Member m

// 단일 필드
SELECT m.name FROM Member m

// DTO 생성
SELECT new com.example.dto.MemberDto(m.name, m.age) FROM Member m

 

* DTO 프로젝션은 new 패키지.클래스명(필드...) 형식 필수


7. 페이징 (Paging)

  • setFirstResult(), setMaxResults() 사용
  • JPQL 자체에는 LIMIT 문 없음 (JPA API에서 처리)
em.createQuery("SELECT m FROM Member m ORDER BY m.name", Member.class)
  .setFirstResult(0)	// 조회 시작 위치 (offset)
  .setMaxResults(10)	// 조회할 데이터 수 (limit)
  .getResultList();

 


8. JOIN

  • 엔티티 간 연관관계를 바탕으로 조인 수행
SELECT m FROM Member m JOIN m.team t WHERE t.name = 'TeamA'

 

 

JOIN 종류

JOIN 종류 키워드 설명
내부 조인 JOIN 또는 INNER JOIN 연관된 엔티티만 조회 (기본)
외부 조인 LEFT [OUTER] JOIN 연관된 엔티티가 없어도 조회
외부 조인 RIGHT [OUTER] JOIN 거의 사용되지 않음
조인 조건 사용 JOIN ... ON 조인 조건을 명시적으로 지정 (JPA 2.1+)
페치 조인 JOIN FETCH 연관 엔티티를 즉시 로딩 (N+1 문제 해결용)

9. CASE 식

  • 조건 분기에 따라 결과를 다르게 반환
SELECT 
  CASE 
    WHEN m.age >= 60 THEN '시니어'
    WHEN m.age >= 30 THEN '중년'
    ELSE '청년'
  END
FROM Member m

10. 함수

  • JPQL 표준 함수 및 DB 함수 (dialect 의존) 사용 가능
함수 설명
CONCAT(a, b) 문자열 연결
SUBSTRING(str, start, len) 문자열 자르기
LOCATE(str, substr) 위치 반환
SIZE(c) 컬렉션 크기
UPPER(str) / LOWER(str) 대소문자 변환
LENGTH(str) 문자열 길이

 

SELECT CONCAT(m.name, '님') FROM Member m