백엔드 개발을 하다 보면, 데이터베이스에서 원하는 데이터를 찾아오는 과정이 꼭 필요합니다. 예를 들어, 쇼핑몰에서 특정 브랜드의 상품만 찾거나, 가격이 특정 범위 내에 있는 상품을 검색하는 경우가 있죠. 이런 작업을 쉽게 해주는 것이 JPA입니다.
JPA(Java Persistence API) 는 데이터베이스와 자바 애플리케이션을 연결해주는 도구입니다. 우리가 SQL을 직접 작성하지 않아도, 자바 코드만으로 데이터를 쉽게 저장하고 불러올 수 있도록 도와줍니다. 하지만 실무에서는 단순한 조회가 아니라, 사용자의 입력에 따라 검색 조건이 달라지는 동적 쿼리가 필요할 때가 많습니다.
동적 쿼리란?
동적 쿼리는 고정된 SQL 문이 아니라, 실행 시점에서 조건에 따라 달라지는 SQL을 생성하여 원하는 데이터를 조회하는 방식입니다.
예를 들어, 사용자가 다음과 같은 조건으로 데이터를 검색할 수 있다고 가정해 봅시다.
- 이름이 “김비즈”인 사용자 조회
- 나이가 20~30세 사이인 사용자 조회
- 특정 지역(예: “서울”)에 거주하는 사용자만 필터링
이 경우, 사용자가 입력한 조건에 따라 검색 조건이 달라질 수 있습니다.
- 이름만 입력한 경우 → 이름 = ‘김비즈’
- 나이 범위만 입력한 경우 → 나이 BETWEEN 20 AND 30
- 지역만 입력한 경우 → 지역 = ‘서울’
- 위 조건들을 조합해서 입력한 경우
→ 이름 = ‘김비즈’ AND 나이 BETWEEN 20 AND 30
→ 이름 = ‘김비즈’ AND 지역 = ‘서울’
이처럼 검색 조건이 유동적으로 변할 때, JPA의 기본적인 findByXxx 메서드나 JPQL로는 모든 경우를 처리하기 어렵습니다. JPA의 정적 쿼리는 미리 정의된 조건만 사용할 수 있기 때문입니다.
따라서, 이러한 상황에서는 동적 쿼리를 활용하여 필요한 조건만 추가하는 방식으로 SQL을 생성해야 합니다. 이를 위해 JPA에서는 Criteria API, QueryDSL, 또는 Native Query와 같은 방법을 사용할 수 있습니다. 이번 포스팅에서는 이러한 방법들을 이용하여 어떻게 동적 쿼리를 구현할 수 있는지 살펴보도록 하겠습니다.
다음 조건에 대하여 각 방법으로 동적 쿼리를 구현해보겠습니다.
✔ 특정 이름을 가진 사용자 찾기
✔ 특정 나이 범위(예: 20~30세)에 속하는 사용자 찾기
✔ 검색 조건을 조합하여 사용자 목록 조회하기
Criteria API
Criteria API는 JPA에서 제공하는 기능으로, SQL을 코드로 작성할 수 있게 해줍니다.
📌 특징
- 객체지향적인 방식으로 쿼리를 작성할 수 있습니다.
- 동적으로 조건을 추가할 수 있어 유연한 검색 기능을 구현하기 좋습니다.
- 하지만 코드가 복잡하고 가독성이 떨어질 수 있습니다.
public List<User> findUsers(String name, Integer minAge, Integer maxAge) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
List<Predicate> predicates = new ArrayList<>();
if (name != null) {
predicates.add(cb.equal(user.get("name"), name));
}
if (minAge != null && maxAge != null) {
predicates.add(cb.between(user.get("age"), minAge, maxAge));
}
query.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(query).getResultList();
}
Specification
Specification은 Criteria API를 더 쉽게 사용할 수 있도록 도와주는 기능입니다. Spring Data JPA에서 제공하며, 객체지향적인 방식으로 동적 쿼리를 작성할 수 있도록 돕습니다.
📌 특징
- Criteria API를 기반으로 만들어졌지만, 더 간결하게 표현할 수 있습니다.
- 조건을 메서드 단위로 분리하여 여러 조건을 쉽게 조합할 수 있습니다.
public class UserSpecification {
public static Specification<User> hasName(String name) {
return (root, query, cb) -> name == null ? null : cb.equal(root.get("name"), name);
}
public static Specification<User> ageBetween(Integer minAge, Integer maxAge) {
return (root, query, cb) ->
(minAge == null || maxAge == null) ? null : cb.between(root.get("age"), minAge, maxAge);
}
}
public List<User> findUsers(String name, Integer minAge, Integer maxAge) {
Specification<User> spec = Specification.where(UserSpecification.hasName(name))
.and(UserSpecification.ageBetween(minAge, maxAge));
return userRepository.findAll(spec);
}
QueryDSL
QueryDSL은 SQL을 코드로 쉽게 작성할 수 있도록 도와주는 강력한 라이브러리입니다.
📌 특징
- SQL과 유사한 문법을 사용하여 가독성이 높습니다.
- 코드 자동 완성 기능을 활용할 수 있어 개발이 편리합니다.
- 하지만 별도의 추가 설정이 필요합니다. (Gradle/Maven 의존성 추가 등)
public List<User> findUsers(String name, Integer minAge, Integer maxAge) {
QUser user = QUser.user;
BooleanBuilder builder = new BooleanBuilder();
if (name != null) {
builder.and(user.name.eq(name));
}
if (minAge != null && maxAge != null) {
builder.and(user.age.between(minAge, maxAge));
}
return queryFactory.selectFrom(user)
.where(builder)
.fetch();
}
동적 쿼리는 JPA를 사용할 때 필수적으로 알아야 할 기능 중 하나이며, 이처럼 다양한 방법을 통해 구현할 수 있습니다. Criteria API, Specification, QueryDSL 등 각 방식의 특징을 이해하고 프로젝트의 요구사항에 맞춰 적절한 방법을 선택한다면 보다 효율적인 코드를 작성할 수 있을 것입니다.
이번 포스팅이 JPA 동적 쿼리에 대한 이해를 높이는 데 도움이 되었길 바라며, 여기에서 마치겠습니다. 감사합니다😊
최신 마케팅/고객 데이터 활용 사례를 받아보실 수 있습니다.