HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🛁
공부기록
/
🍀
Spring
/
🧳
JPA 순수 Criteria
🧳

JPA 순수 Criteria

태그
Criteria Criteria 위치Criteria 기본 사용법Criteria 쿼리 검색조건 추가하기Criteria 서브쿼리 사용하기커스텀 레포지토리 정의하는 방법커스텀 레포지토리 네임규칙
 

Criteria

  • Criteria 쿼리는 JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API다.
  • Criteria를 사용하면 문자가 아닌 코드로 JPQL을 작성하므로 문법 오류를 컴파일 단계에서 잡을 수 있고 문자 기반의 JPQL보다 동적 쿼리를 안전하게 생성할 수 있다는 장점이 있다.
  • 하지만 실제 Criteria를 사용해서 개발해보면 코드가 복잡하고 장황해서 직관적으로 이해하기 힘들다는 단점도 존재한다.
  • 즉 Criteria는 JPQL의 생성을 돕는 클래스의 모음이다.
 

Criteria 위치

  • Criteria API는 javax.persistence.criteria 패키지에 존재한다.
 

Criteria 기본 사용법

notion image
  1. Criteria 쿼리를 생성하려면 먼저 Criteria 빌더를 얻어야 한다. Criteria 빌더는 EntityManager나 EntityManagerFactory에서 얻을 수 있다.
  1. Criteria 쿼리 빌더에서 Criteria 쿼리를 생성한다. 이때 반환 타입을 지정할 수 있다.
  1. FROM 절을 생성한다. 반환된 값 m은 Criteria에서 사용하는 특별한 별칭이다. m을 조회의 시작점이라는 의미로 쿼리 루트라한다.
  1. SELECT 절을 생성한다.
  • 이렇게 Criteria 쿼리를 완성하고 나면 다음 순서는 JPQL과 같다. em.createQuery(cq)에 완성된 Criteria 쿼리를 넣어주기만 하면 된다.
 

Criteria 쿼리 검색조건 추가하기

  1. 검색 조건을 정의한 부분을 보면 m.get(”username”) 으로 되어 있는데 m은 회원 엔티티의 별칭이다. 이것은 JPQL에서 m.username과 같은 표현이다. 그리고 cb.equal(A,B)는 이름 그대로 A = B 라는 뜻이다. 따라서 cb.equal(m.get(”username”), “김형욱")는 JPQL에서 m.username = “김형욱" 과 같은 표현이다.
  1. 정렬 조건을 정의하는 코드인 cb.desc(m.get(”age”))는 JPQL의 m.age desc와 같은 표현이다.
  1. 만들어준 조건을 where, orderBy 에 넣어서 원하는 쿼리를 생성한다.
  • Criteria는 검색 조건부터 정렬까지 Criteria 빌더를 사용해서 코드를 완성한다.
🔥
추가적으로 IN, GroupBy 등 다양한 Query를 작성할 수 있다.
 

Criteria 서브쿼리 사용하기

  • subQuery는 mainQuery를 이용해서 생성할 수 있다.
  • 추라적으로 여기서 하나 알 수 있는 점은 m.get(”age”)에 <Integer>로 타입을 명시해 주었는데 가지고 올 때 “age”의 타입 정보를 알지 못한다. 그렇기 때문에 반환 타입 정보를 명시해주어야 한다. (String같은 문자열은 지정하지 않아도 된다.)
 

커스텀 레포지토리 정의하는 방법

  1. 커스텀 레포지토리 인터페이스를 아래의 예시처럼 만든다.
  1. 인터페이스의 구현체를 아래의 예시처럼 만들어준다
  • 사실 가장 중요한건 구현체 클래스 이름 뒤에 Impl 이라는 키워드이다.
    • 구현 자체는 Spring Data에 의존하지 않고 일반적인 Bean으로 관리된다. 즉 일반적인 의존성 주입을 통해(ex : jdbc Template) 주입하고 작업을 수행할 수 있다.
  1. 구현체를 적용하기 위해서는 아래의 예시처럼 인터페이스를 추가해주면 된다.
  • 기본 레포지토리 인터페이스에 함께 확장하여 사용하면 기능이 결합되여 클라이언트가 사용할 수 있게 된다.
  • 레포지토리 인터페이스에 인터페이스를 추가할 때마다 레포지토리 조각들을 추가하여 구성하게 된다.
    • 기본 레포지토리 추가된 레포지토리 인터페이스는 스프링 데이터 모듈에서 제공하게 된다.
  • 사용자 지정으로 구현된 레포지토리 기본 리포지토리보다 우선순위가 높다. 이 우선순위를 사용하면 동일한 메서드 시그니처를 제공하는 경우 모호한 부분을 해결할 수 있게 된다.
  • 또한 레포지토리 하나의 기본 레포지토리에만 사용할 수 있는 것은 아니고 여러 레포지토리에서 사용할 수 있다.
 

커스텀 레포지토리 네임규칙

  • 네임스페이스 요소는 위의 repository-impl-postfix 속성을 지키면서 만들어야 하며 이 postfix의 기본값은 Impl이다. 직접 정의하고 싶은 경우는 위의 예시처럼 사용자가 직접 정의해서 설정할 수 있다.
// JPQL : select m from Member m CriteriaBuilder cb = em.getCriteriaBuilder(); // 타입을 지정해주지 않으면 Object 타입이 나온다. CriteriaQuery<Member> cq = cb.createQuery(Member.class); Root<Member> m = cq.from(Member.class); // FROM 절 cq.select(m); // SELECT 절 TypedQuery<Member> query = em.createQuery(cq); List<Member> members = query.getResultList();
// JPQL // SELECT m FROM Member m // WHERE m.username = "김형욱" CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Member> cq = cb.createQuery(Member.class); Root<Member> m = cq.from(Member.class); // FROM 절 생성 // 검색 조건 정의 Predicate usernameEqual = cb.equal(m.get("username"),"김형욱"); // 정렬 조건 정의 javax.persistence.criteria.Order ageDesc = cb.desc(m.get("age")); // 쿼리 생성하기 cq.select(m) .where(usernameEqual) .orderBy(ageDesc) List<Member> resultList = em.createQuery(cq).getResultList();
/* SELECT m FROM Member m WHERE m.age >= (SELECT AVG(m2.age) from Member m2) */ CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class); // 서브쿼리 생성하기 SubQuery<Double> subQuery = mainQuery.subQuery(Double.class); Root<Member> m2 = subQuery.from(Member.class); subQuery.select(m2.<Integer>get("age")); // 메인쿼리 만들기 Root<Member> m = mainQuery.from(Member.class) mainQuery.select(m) .where(cb.ge(m.<Integer>get("age"), subQuery));
interface CustomizedPostRepository { List<Post> findAllSearch(); }
class CustomizedPostRepositoryImpl implements CustomizedPostRepository { public List<Post> findAllSearch() { ~~~ } }
interface PostRepository extends JpaRepository<Post, Long>, CustomizedPostRepository { }
interface CustomizedSave<T> { <S extends T> S save(S entity); } // MemberRepository, PostRepository 모두 사용 가능하다.
<repositories base-package="com.acme.repository" /> <repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />