개발공부/JPA

Spring Data JPA의 JpaSpecificationExecutor란? 동적 검색을 깔끔하게 구현하기

기억지기 개발자 2026. 7. 14. 12:54

 

관리자 목록 조회 기능을 구현하면서 여러 검색 조건을 조합해야 하는 상황이 있었다.

  • 이름으로 검색
  • 이메일로 검색
  • 상태(PENDING, ACTIVE)로 검색
  • 이름 + 상태 검색
  • 이메일 + 상태 검색
  • 이름 + 이메일 + 상태 검색

처럼 검색 조건이 계속 늘어났다.

처음에는 Repository에 메서드를 하나씩 추가하면 되지 않을까 생각했다.

findByName(String name)

findByEmail(String email)

findByStatus(AdminStatus status)

findByNameAndStatus(String name, AdminStatus status)

findByEmailAndStatus(String email, AdminStatus status)

findByNameAndEmailAndStatus(...)

하지만 조건이 하나씩 늘어날 때마다 메서드도 함께 증가했다.

조건이 3~4개만 되어도 Repository가 금방 복잡해지는 문제가 발생했다.

이런 문제를 해결하기 위해 Spring Data JPA에서는 JpaSpecificationExecutor를 제공한다.


JpaSpecificationExecutor란?

JpaSpecificationExecutor검색 조건을 동적으로 조합하여 조회할 수 있도록 지원하는 인터페이스이다.

Repository에서 다음과 같이 함께 상속받아 사용한다.

public interface AdminRepository extends
        JpaRepository<Admin, Long>,
        JpaSpecificationExecutor<Admin> {
}

이 한 줄만 추가하면 Specification을 이용한 동적 검색 기능을 사용할 수 있다.


왜 필요한 걸까?

예를 들어 관리자 조회 화면에서

  • 이름은 입력하지 않을 수도 있다.
  • 이메일은 입력할 수도 있다.
  • 상태는 선택하지 않을 수도 있다.

즉,

이름 O
이메일 X
상태 O

또는

이름 X
이메일 O
상태 X

처럼 사용자가 원하는 조건만 검색해야 한다.

만약 Repository 메서드만 사용한다면

findByName(...)

findByStatus(...)

findByNameAndStatus(...)

findByEmailAndStatus(...)

findByNameAndEmailAndStatus(...)

처럼 가능한 모든 조합을 만들어야 한다.

검색 조건이 많아질수록 Repository는 점점 비대해진다.


Specification이란?

Specification하나의 검색 조건을 객체로 표현한 것이다.

예를 들어 이름 검색 조건은 다음처럼 만들 수 있다.

public static Specification<Admin> nameContains(String name) {

    return (root, query, cb) ->
            cb.like(root.get("name"), "%" + name + "%");
}

상태 검색도 하나의 Specification으로 만들 수 있다.

public static Specification<Admin> hasStatus(AdminStatus status) {

    return (root, query, cb) ->
            cb.equal(root.get("status"), status);
}

즉,

이름 조건

상태 조건

이메일 조건

을 각각 하나의 객체로 분리하는 것이다.


필요한 조건만 조합할 수 있다.

예를 들어

Specification<Admin> spec = Specification.where(null);

if (name != null) {
    spec = spec.and(nameContains(name));
}

if (status != null) {
    spec = spec.and(hasStatus(status));
}

처럼 필요한 조건만 추가하면 된다.

마지막에는

adminRepository.findAll(spec);

한 줄만 호출하면 된다.

Repository 메서드를 계속 추가할 필요가 없다.


내부적으로는 어떻게 동작할까?

JpaSpecificationExecutor는 내부적으로 JPA Criteria API를 사용한다.

즉,

Specification

↓

Criteria API

↓

SQL

형태로 변환된다.

예를 들어

name = "kim"

status = ACTIVE

라면

최종적으로는

SELECT *
FROM admin
WHERE name LIKE '%kim%'
AND status = 'ACTIVE';

와 같은 SQL이 생성된다.


장점

1. Repository가 깔끔해진다.

검색 조건마다 메서드를 만들 필요가 없다.

findByNameAndStatusAndEmail(...)

같은 메서드가 계속 늘어나지 않는다.


2. 조건을 자유롭게 조합할 수 있다.

필요한 조건만 추가하면 된다.

이름만 검색

상태만 검색

이름 + 상태

이메일 + 상태

모두 하나의 로직으로 처리할 수 있다.


3. 유지보수가 쉽다.

새로운 검색 조건이 생겨도 Repository를 수정할 필요가 없다.

예를 들어

전화번호 검색

이 추가되면

Specification 하나만 만들면 된다.


단점

Specification을 많이 사용하면 코드가 길어질 수 있다.

특히 JOIN이 많아지고 조건이 복잡해질수록 가독성이 떨어질 수 있다.

또한 Criteria API 기반으로 동작하기 때문에 처음에는 문법이 다소 낯설게 느껴질 수 있다.


정리

JpaSpecificationExecutor동적 검색을 위해 제공되는 Spring Data JPA의 기능이다.

검색 조건이 늘어날수록 Repository 메서드를 계속 추가하는 대신, 각각의 조건을 Specification으로 분리하여 필요한 조건만 조합할 수 있다.

덕분에 Repository는 훨씬 깔끔해지고, 새로운 검색 조건이 추가되더라도 기존 코드를 크게 수정하지 않아도 된다.

간단한 검색에서는 Repository 메서드만으로도 충분하지만, 검색 조건이 다양해지는 순간부터는 JpaSpecificationExecutor가 유지보수성과 확장성을 크게 높여주는 도구라는 것을 알게 되었다.