[스프링(spring) JPA 활용-동적쿼리(JPQL vs Criteria vs QueryDSL)

allnight5·2023년 2월 27일
0

스프링

목록 보기
54/62

참조사이트 1

동적 쿼리?
: 같은 기능을 하지만 동적으로 들어오는 파라미터에 의해 조건이 바뀌게 설계되는 쿼리
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);

JPA Specification

검색 조건을 추상화하기 위해 사용된다.

이를 사용하기 위해 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가 제공하는 기본 기능을 사용합니다.

profile
공부기록하기

0개의 댓글