JPA Specification이란?
: JPA에서 동적 쿼리를 조합할 수 있게 도와주는 WHERE 절 조건 빌더
조건을 메서드 단위로 나누고, 조합할 수 있도록 함
→ where 절의 조건 하나하나를 Specification 객체로 만들고, 이를 .and(), .or() 등으로 조립한다 !
기본 사용법
1. Repository에 JPASpecificationExecutor 확장
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}
2. Specification 정의
public class UserSpecification {
public static Specification<User> hasName(String name) {
return (root, query, cb) -> cb.equal(root.get("name"), name);
}
public static Specification<User> createdAfter(LocalDateTime time) {
return (root, query, cb) -> cb.greaterThan(root.get("createdAt"), time);
}
public static Specification<User> hasStatus(String status) {
return (root, query, cb) -> cb.equal(root.get("status"), status);
}
}
3. 사용 예시
Specification<User> spec = Specification
.where(UserSpecification.hasName("name"))
.and(UserSpecification.hasStatus("ACTIVE"))
.and(UserSpecification.createdAfter(LocalDateTime.now().minusDays(30)));
List<User> result = userRepository.findAll(spec);
파라미터 소개
파라미터 | 설명 |
root | 쿼리의 루트 (FROM 절의 엔티티) |
query | CriteriaQuery 객체 (보통은 잘 안 씀) |
cb | CriteriaBuilder (조건 생성기) |
CriteriaBuilder란?
JPA의 Criteria API에서 조건을 생성하는 빌더 객체
CriteriaBuilder의 메서드
메서드 | 예시 |
equal | cb.equal(root.get("name), "kim") |
notEqual | cb.notEqual(root.get("age"), 20) |
graterThan, lessThan | cb.greaterThan(root.get("age"), 18) |
like | cb.like(root.get("name"), "%kim%") |
in | cb.in(root.get("status")).value("ACTIVE") |
and, or | cb.and(cond1, cond2) |
not | cb.not(cb.equal(...)) |
isNull, isNotNull | cb.isNull(root.get("email")) |
between | cb.between(root.get("age"), 20, 30) |
optional filtering (조건 null 체크 처리)
public static Specification<User> hasNameIfPresent(String name) {
return (root, query, cb) ->
name == null ? null : cb.equal(root.get("name"), name);
}
- null을 반환하면 해당 조건은 무시됨
JOIN 처리
public static Specification<Post> hasAuthorName(String name) {
return (root, query, cb) -> {
Join<Post, User> author = root.join("author");
return cb.equal(author.get("name"), name);
};
}
페이징 + 정렬과 함께 사용
Pageable pageable = PageRequest.of(0, 10, Sort.by("createdAt").descending());
Page<User> page = userRepository.findAll(spec, pageable);
- repository 메서드에 spec과 pageable 전달
❗ JpaSpecificationExecutor 실무 사용이 권장되지 않는 이유
1. 내부적으로 JPA Criteria API 기반
- Criteria API는 문법이 복잡하고 가독성이 매우 떨어지는 단점이 있음
- 조건이 복잡해질수록 코드가 길고 비직관적 → 유지보수성이 낮아짐
2. 타입 안정성이 없음
- 필드명을 문자열로 입력 → 오타가 있어도 컴파일 타임 오류가 발생하지 않아 버그 유발 위험 증가
cb.equal(root.get("name"), "deang"); // "name" 오타 나도 컴파일 통과
Specification vs QueryDSL
Specification은 JPA의 공식적인 방법으로, 추가적인 라이브러리나 의존성 없이 바로 사용 가능
QueryDSL은 추가적인 의존성이 필요하지만, 가독성이 좋고 유지보수가 용이
간단한 동적 조건 조합 → Specification으로 구현해볼 수 있음 !
복잡한 쿼리, 실무 서비스 레벨 → QueryDSL을 사용하는 것이 안정적, 유지보수에 유리
복잡한 조건 조립은 QueryDSL로 깔끔하게 구현하자 ~
'spring' 카테고리의 다른 글
더티 체킹(Dirty Checking) - 개념 및 주의사항 / @DynamicUpdate (0) | 2025.05.05 |
---|---|
@Transactional 이해하기 - 내부 동작 및 주의사항 포함 (0) | 2025.05.05 |
웹 어플리케이션 보안 취약성 (0) | 2024.11.02 |
HTTPS vs HTTP (0) | 2024.11.02 |
로깅(Logging) (0) | 2024.09.20 |