JpaSpecificationExecutor를 사용하면 데이터베이스에서 데이터를 조회할 때 동적인 쿼리를 작성할 수 있습니다. 일반적인 쿼리 메소드와는 달리, 메소드 선언에 기술적인 조건을 추가할 수 있습니다. 이렇게 작성된 동적인 조건은 실행 시점에 쿼리로 변환되어 데이터베이스에서 실행됩니다.
JpaSpecificationExecutor의 핵심 메소드는 다음과 같습니다:
- findAll(Specification spec): 지정된 Specification에 해당하는 모든 엔티티를 검색합니다.
- findOne(Specification spec): 지정된 Specification에 해당하는 첫 번째 엔티티를 검색합니다.
- count(Specification spec): 지정된 Specification에 해당하는 엔티티의 수를 계산합니다.
Specification은 JPA Criteria API를 사용하여 동적인 쿼리를 작성하기 위한 인터페이스입니다. Specification을 구현하여 필요한 동적인 조건을 정의할 수 있습니다. 예를 들어, 특정 필드가 특정 값보다 큰지 확인하거나, 여러 필드를 조합하여 복잡한 조건을 만족하는 엔티티를 조회하는 등의 작업을 할 수 있습니다.
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
// 사용자 엔티티(User)를 처리하기 위한 UserRepository 인터페이스 정의
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}
// 사용자 서비스(UserService) 클래스
public class UserService {
private final UserRepository userRepository;
// UserRepository 주입
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 이름과 나이를 조건으로 받아 동적인 쿼리를 작성하고 실행하는 메소드
public List<User> findUsersByCriteria(String name, Integer age) {
// Specification을 조합하여 동적인 쿼리 작성
Specification<User> spec = Specification.where(UserSpecifications.withName(name))
.and(UserSpecifications.withAgeGreaterThan(age));
// 생성된 Specification을 사용하여 사용자 조회
return userRepository.findAll(spec);
}
}
// Specification 생성을 위한 유틸리티 클래스
public class UserSpecifications {
// 주어진 이름과 일치하는 사용자를 찾기 위한 Specification 생성
public static Specification<User> withName(String name) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("name"), name);
}
// 주어진 나이보다 큰 나이를 가진 사용자를 찾기 위한 Specification 생성
public static Specification<User> withAgeGreaterThan(Integer age) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.greaterThan(root.get("age"), age);
}
}
우선, UserRepository 인터페이스는 JpaRepository<User, Long>와 JpaSpecificationExecutor를 상속받고 있습니다. 이렇게 함으로써 User 엔티티에 대한 기본적인 CRUD(Create, Read, Update, Delete) 작업은 JpaRepository가 제공하고, 동적인 쿼리 작성과 실행은 JpaSpecificationExecutor가 제공하는 기능을 사용할 수 있습니다.
UserService 클래스는 UserRepository를 주입받아 사용합니다. findUsersByCriteria 메소드는 이름과 나이를 조건으로 받아 동적인 쿼리를 작성하고 실행합니다. 이 메소드는 name과 age를 매개변수로 받으며, 해당 조건을 만족하는 사용자를 조회하여 반환합니다.
UserSpecifications 클래스는 Specification을 생성하는 유틸리티 메소드를 제공합니다. withName 메소드는 주어진 이름과 일치하는 사용자를 찾기 위한 Specification을 생성하고, withAgeGreaterThan 메소드는 주어진 나이보다 큰 나이를 가진 사용자를 찾기 위한 Specification을 생성합니다. 이러한 Specification은 JPA Criteria API를 사용하여 작성됩니다.
위의 예제 코드에서 UserService 클래스의 findUsersByCriteria 메소드는 이름과 나이를 조건으로 받아 동적인 쿼리를 작성하고 UserRepository에서 findAll 메소드를 호출하여 해당 조건을 만족하는 사용자를 조회합니다.
findUsersByCriteria ( 'James', 20 ) 을 실행시키면 이름이 James 면서 나이가 20세 이상인 사람들을 찾아준다는거지?
네, 맞습니다. findUsersByCriteria('James', 20)를 실행하면 이름이 'James'이고 나이가 20세 이상인 사용자를 찾아줍니다.
UserSpecifications.withName('James')는 이름이 'James'인 사용자를 찾기 위한 Specification을 생성하고, UserSpecifications.withAgeGreaterThan(20)는 나이가 20세보다 큰 사용자를 찾기 위한 Specification을 생성합니다.
Specification.where(UserSpecifications.withName('James')).and(UserSpecifications.withAgeGreaterThan(20))는 두 개의 Specification을 조합하여 이름이 'James'이고 나이가 20세 이상인 사용자를 찾기 위한 최종적인 Specification을 생성합니다.
userRepository.findAll(spec)에서 spec는 위에서 생성한 Specification입니다. 이를 사용하여 해당 조건을 만족하는 사용자를 조회하게 됩니다.
따라서 findUsersByCriteria('James', 20)을 실행하면 이름이 'James'이면서 나이가 20세 이상인 사용자들의 리스트가 반환될 것입니다.
UserSpecifications 클래스에서 root, query, criteriaBuilder 은 뭐야? 이것도 사전에 사용자가 정의를 해둬야 하는 거야? 아니면 spring이나 java에서 제공해주는거야?
UserSpecifications 클래스에서 root, query, criteriaBuilder는 JPA Criteria API에서 사용되는 객체입니다. 이 객체들은 Spring Data JPA나 Java에서 제공하는 것이며, 사용자가 직접 정의할 필요는 없습니다.
여기서 간단히 설명드리겠습니다:
root: 엔티티의 속성에 접근하기 위한 엔티티 루트 객체입니다. 엔티티의 특정 필드를 참조하거나 엔티티 간의 관계를 탐색하는 데 사용됩니다.
query: JPA 쿼리 작성을 위한 쿼리 메타데이터를 나타내는 객체입니다. 주로 쿼리 결과의 정렬이나 그룹화, 조인 등과 관련된 작업에 사용됩니다.
criteriaBuilder: JPA Criteria API에서 제공하는 빌더 객체입니다. 쿼리를 작성하는데 사용되며, 다양한 조건과 연산자를 활용하여 동적인 쿼리를 생성할 수 있습니다. 예를 들어 criteriaBuilder.equal(), criteriaBuilder.greaterThan() 등을 사용하여 비교 조건을 작성할 수 있습니다.
Specification 인터페이스의 toPredicate 메소드에서 root, query, criteriaBuilder 객체를 활용하여 동적인 쿼리를 작성합니다. 이 객체들은 Spring Data JPA에서 자동으로 주입되므로 별도로 정의할 필요가 없습니다.
따라서 UserSpecifications 클래스에서는 root, query, criteriaBuilder 객체를 활용하여 사용자 정의 Specification을 작성하고, 이를 JpaSpecificationExecutor를 통해 실행할 수 있습니다.