JPA Specification
JPA Specification은 Spring Data JPA에서 제공하는 도구로, 복잡한 데이터베이스 쿼리를 SQL 문자열로 작성하는 대신 Java 객체와 메서드로 표현하여 객체지향적으로 작성할 수 있게 도와줍니다. 이를 통해 코드의 가독성과 재사용성이 높아지고, 유지보수가 쉬워집니다.
특징
1. 객체지향적 쿼리 작성
JPA Specification은 SQL 대신 Java 객체를 사용하여 쿼리를 작성합니다.
- SQL: SELECT * FROM customers WHERE name = 'John' AND age > 30;
- 객체지향적 표현: Specification.where(nameEquals("John")).and(ageGreaterThan(30));
2. 재사용 가능한 조건
각 조건을 별도의 메서드로 정의하여 여러 곳에서 재사용할 수 있습니다. ex. "활성 고객"이라는 조건을 정의하고 필요할 때마다 쉽게 조합하여 사용
3. 동적 쿼리 생성
사용자 입력이나 상황에 따라 조건을 동적으로 추가하거나 제거할 수 있습니다. 이는 검색 기능이나 필터링 기능이 자주 사용되는 애플리케이션에서 매우 유용합니다.
public Specification<Question> search(String keyword, String author) {
return (root, query, cb) -> {
Predicate p1 = cb.like(root.get("title"), "%" + keyword + "%");
Predicate p2 = cb.equal(root.get("author"), author);
return cb.and(p1, p2);
};
}
- 사용자가 제목(keyword)과 작성자(author) 조건을 입력했을 때만 해당 조건으로 검색합니다.
- 조건이 없다면 기본 데이터를 반환하거나 조건 없이 전체를 조회할 수 있습니다.
동작원리
1. Specification 인터페이스
Specification<T>는 JPA 쿼리를 작성하기 위한 인터페이스로, toPredicate 메서드를 통해 조건을 정의합니다. 이 메서드는 다음과 같은 매개변수를 사용합니다.
- Root<T> : 쿼리의 루트 엔티티
- CriteriaQuery<?> : JPA Criteria API의 쿼리 객체
- CriteriaBuilder : 조건을 생성하는 빌더
2. Predicate
Predicate는 SQL 조건과 비슷한 역할을 하며, 필드 값 비교나 논리 연산(AND, OR)을 수행하는 데 사용됩니다.
예시
private Specification<Question> search(String kw) {
return new Specification<>() {
private static final long serialVersionUID = 1L;
@Override
public Predicate toPredicate(Root<Question> q, CriteriaQuery<?> query, CriteriaBuilder cb) {
query.distinct(true); // 중복을 제거
Join<Question, SiteUser> u1 = q.join("author", JoinType.LEFT);
Join<Question, Answer> a = q.join("answerList", JoinType.LEFT);
Join<Answer, SiteUser> u2 = a.join("author", JoinType.LEFT);
return cb.or(cb.like(q.get("subject"), "%" + kw + "%"), // 제목
cb.like(q.get("content"), "%" + kw + "%"), // 내용
cb.like(u1.get("username"), "%" + kw + "%"), // 질문 작성자
cb.like(a.get("content"), "%" + kw + "%"), // 답변 내용
cb.like(u2.get("username"), "%" + kw + "%")); // 답변 작성자
}
};
}
1. private Specification<Question> search(String kw)
- 검색 키워드(kw)를 사용하여 Question 엔티티를 대상으로 동적 검색을 수행하는 Specification을 생성합니다.
- Specification<Question> : Spring Data JPA에서 제공하는 인터페이스로, 엔티티를 대상으로 조건 기반 검색을 정의합니다.
2. new Specification<>()
- 익명 클래스를 사용하여 Specification 인터페이스를 구현합니다.
- toPredicate 메서드를 오버라이드하여 검색 조건을 정의합니다.
3. private static final long serialVersionUID = 1L;
- 직렬화(Serialization)와 관련된 필드입니다. 익명 클래스가 Serializable 인터페이스를 간접적으로 구현할 때 필요합니다.
- 직렬화는 객체를 저장하거나 전송할 때 사용되므로, 여기서는 코드 안정성을 위한 관례입니다.
4. public Predicate toPredicate(Root<Question> q, CriteriaQuery<?> query, CriteriaBuilder cb)
- toPredicate 메서드 : Specification의 핵심 메서드로, 조건을 생성합니다.
- Root<Question> q : 검색의 루트 엔티티를 나타냅니다. 여기서는 Question 엔티티입니다.
- CriteriaQuery<?> query : JPA Criteria API의 쿼리 객체입니다. select, where, distinct 등의 쿼리 동작을 정의할 수 있습니다.
- CriteriaBuilder cb : 조건(Predicate)을 생성하는 데 사용됩니다. and, or, like 등의 메서드를 제공합니다.
5. query.distinct(true);
- 중복 제거를 위해 쿼리 결과를 distinct 처리합니다.
- JPA에서 SQL의 SELECT DISTINCT와 동일하게 동작합니다.
6. Join<Question, SiteUser> u1 = q.join("author", JoinType.LEFT);
- Join 객체 : 엔티티 간의 관계를 정의합니다.
- q.join("author", JoinType.LEFT) : Question 엔티티의 author 필드를 기준으로 SiteUser와 LEFT JOIN합니다.
- LEFT JOIN은 author 필드가 없는 경우에도 결과에 포함되도록 보장합니다.
7. Join<Question, Answer> a = q.join("answerList", JoinType.LEFT);
- Question 엔티티와 answerList(질문에 달린 답변 리스트)를 LEFT JOIN하여 답변 데이터를 가져옵니다.
8. Join<Answer, SiteUser> u2 = a.join("author", JoinType.LEFT);
- 답변(Answer)의 작성자(author) 필드를 기준으로 SiteUser와 LEFT JOIN합니다.
9. cb.or(...)
- 조건을 OR 연산으로 결합합니다.
- ex. (조건1 OR 조건2 OR 조건3)의 SQL 쿼리를 생성합니다.
10. cb.like(...)
- SQL의 LIKE 연산자를 사용합니다.
- ex. cb.like(q.get("subject"), "%" + kw + "%")는 제목(subject) 필드에 키워드(kw)가 포함된 데이터를 검색합니다.
11. q.get("subject"), u1.get("username") 등
- Root와 Join 객체를 통해 필드를 지정합니다.
- ex. q.get("subject")는 Question 엔티티의 subject 필드를 나타냅니다.
출처
1. OpenAI의 ChatGPT (https://openai.com)
2. 점프 투 스프링 부트 책 3-13 검색 기능 추가하기 : https://wikidocs.net/162799
'프로그래머스 > Spring' 카테고리의 다른 글
| [Lombok] @Data (1) | 2025.04.13 |
|---|---|
| [Spring] 단위 테스트(Unit Test) vs 통합 테스트(Integration Test) (0) | 2025.03.28 |
| [Spring] Bean (1) | 2025.02.12 |
| [Spring] 페이징 (Paiging) 주요 메서드 (0) | 2025.02.10 |
| [Spring] 바인딩 (Binding) (0) | 2025.02.09 |