동적 쿼리?
: 같은 기능을 하지만 동적으로 들어오는 파라미터에 의해 조건이 바뀌게 설계되는 쿼리
ex) 검색 기능 구현시 값이 없으면 전체 정보, 있으면 맞는 정보를 반환
방법
순수 JPQL
Criteria
QueryDSL
[ 순수 JPQL ]
jpql을 문자열로 만든 뒤 조건에 따라 jpql에 string을 추가하는 방식
--> jpql에 띄어쓰기 하나라도 틀리면 오류발생
@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);
[ Criteria ]
JPA Specification은 criteria API를 기반으로 만들어졌다.
JPA Criteria는 동적 쿼리를 사용하기 위한 JPA 라이브러리이다.
Criteria는 Spring의 표준 스펙이지만 실무에서 사용 X
사용성이 복잡해서 어떤 쿼리가 나가는지 도무지 파악 X
CriteriaBuilder criteraBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Lecture> criteriaQuery = criteriaBuilder.createQuery(Lecture.class);
// 제너릭 형식으로 criteria query 인스턴스를 생성한다.
Root<Lecture> root = criteriaQuery.from(Lecture.class);
// root : 영속적 엔티티 표시
Predicate predicates = criteriaBuilder.equal(root.get("year"), "2022");
// entity의 "year" 필드가 "2022"인 요소 선택
criteriaQuery.where(predicates);
// Predicate은 criteriaBuilder로부터 생성되며 SQL의 WHERE절 역할을 한다.
TypedQuery<Lecture> lectureListQuery = entityManager.createQuery(criteriaQuery).setFirstResult(startRow).setMaxResults(pageSize);
List<Lecture> lectureList = lectureListQuery.getResultList();
return lectureList;
And/Or Predicate
두 가지 조건을 And, 혹은 Or로 사용하고 싶을 경우, criteriaBuilder의 and/or predicates를 사용할 수 있다.
Predicate predicateForBlueColor = criteriaBuilder.equal(itemRoot.get("color"), "blue");
Predicate predicateForRedColor = criteriaBuilder.equal(itemRoot.get("color"), "red");
Predicate predicateForColor = criteriaBuilder.or(predicateForBlueColor, predicateForRedColor);
이렇게 작성하면 "color"가 "blue"인 엔티티와 "red"인 엔티티를 반환받을 수 있다.
Predicate predicateForGradeA = criteriaBuilder.equal(itemRoot.get("grade"), "A");
Predicate predicateForBlueColor = criteriaBuilder.equal(itemRoot.get("color"), "blue");
Predicate predicateForGrade = criteriaBuilder.and(predicateForGradeA, predicateForBlueColor);
검색 조건을 추상화하기 위해 사용된다.
이를 사용하기 위해 Repository에서 JpaSpecificationExecutor를 상속받아야 한다.
@Repository
public interface LectureRepository extends JpaRepository<Lecture, Long>, JpaSpecificationExecutor<Lecture> {
...
}
위의 메소드를 상속받아 사용할 수 있고, 매개변수로 Specification 객체를 넣어주면 된다.
List<T> findAll(@Nullable Specification<T> spec, Sort sort);
Specification
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}
Specification 인터페이스는 이렇게 구현되어 있다.
Specification 명세를 정의하고 조건쿼리를 생성하기 위해 Specification 인터페이스의 toPredicate() 메소드를 구현해야 한다.
public CustomerSpecifications {
public static Specification<Customer> customerHasBirthday() {
return new Specification<Customer> {
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.equal(root.get(Customer_.birthday), today);
}
};
}
}
메소드 안에 root, query, criteriabuilder를 매개변수로 받고, Predicate 객체를 반환하는 함수를 작성하면 된다.
이후 검색 비즈니스 로직을 실행하는 부분에서 Repository findAll 메소드의 파라미터로 리턴받는 Specification을 넣어주면 된다.
[ QueryDSL ]
DSL
DSL 은 도메인(Domain) + 특화(Specific) + 언어(Language) 입니다.
특정한 도메인에 초점을 맞춘 제한적인 표현력을 가진 컴퓨터 프로그래밍 언어입니다.
단순하고 간결하며 유창하다는 특징을 가집니다.
QueryDSL
그렇다면 QueryDSL 은 쿼리 + 도메인 + 특화 + 언어 입니다.
쿼리에 특화된 프로그래밍 언어이지요. 역시 단순, 간결, 유창합니다.
다양한 저장소 쿼리 기능을 통합하고 있습니다. JPA, MongoDB, SQL 같은 기술들을 위해 type-safe SQL을 만드는 프레임워크입니다.
List<Item> result = query
.select(item)
.from(item)
.where(builder)
.fetch();
Querydsl 을 사용하려면 JPAQueryFactory가 필요합니다. JPAQueryFactory는 JPA 쿼리인 JPQL을 만들기 때문에 EntityManager가 필요합니다.
설정 방식은 JdbcTemplate 을 설정하는 것과 유사합니다.
참고로 JPAQueryFactory 를 스프링 빈으로 등록해서 사용해도 됩니다.
@Configuration
public class QueryDslConfig {
private final EntityManager entityManager;
public QueryDslConfig(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
save(), update(), findById()
기본 기능들은 JPA가 제공하는 기본 기능을 사용합니다.