[TIL] JPA에서 동적 쿼리를 어떻게 해결해야 하는가? Querydsl

주리링·2021년 8월 12일

Today I Learned

목록 보기
2/7
post-thumbnail

Querydsl란?

Querydsl은 타입에 안전한 방식으로 HQL 쿼리를 실행하기 위한 목적으로 만들어졌다.
HQL 쿼리를 작성하다보면 String 연결을 이용하게 되고, 이는 결과적으로 읽기 어려운 코드를 만드는 문제를 야기한다.
타입에 안전하도록 도메인 모델을 변경하면 소프트웨어 개발에서 큰 이득을 얻게 된다.
도메인의 변경이 직접적으로 쿼리에 반영되고, 쿼리 작성 과정에서 코드 자동완성 기능을 사용함으로써 쿼리를 더 빠르고 안전하게 만들 수 있게 된다.
Querydsl의 최초 쿼리 언어 대상은 Hibernate의 HQL이었으나, 현재는 JPA, JDO, JDBC, Lucene, Hibernate Search, MongoDB, 콜렉션 그리고 RDFBean을 지원한다.
Querydsl은 JPQL과 Criteria 쿼리를 모두 대체할 수 있다.
Querydsl은 Criteria 쿼리의 동적인 특징과 JPQL의 표현력을 타입에 안전한 방법으로 제공한다.
또한Querydsl JPA 모듈은 JPA와 Hibernate API를 모두 지원한다.

Querydsl 원칙

Querydsl의 핵심 원칙은 타입 안정성(Type safety)이다.
도메인 타입의 프로퍼티를 반영해서 생성한 쿼리 타입을 이용해서 쿼리를 작성하게 된다.
또한, 완전히 타입에 안전한 방법으로 함수/메서드 호출이 이루어진다.
또 다른 중요한 원칙은 일관성(consistency)이다.
기반 기술에 상관없이 쿼리 경로와 오퍼레이션은 모두 동일하며, Query 인터페이스는 공통의 상위 인터페이스를 갖는다.
모든 쿼리 인스턴스는 여러 차례 재사용 가능하다.
쿼리 실행 이후 페이징 데이터와 프로젝션 정의는 제거된다.

Querydsl 사용법

gradle 설정

plugins {
    // ...
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" // 추가
    // ...
}

// ...

dependencies {
    // ...
    implementation 'com.querydsl:querydsl-jpa' // 추가
    // ...
}

// ...

// queryDSL이 생성하는 QClass 경로 설정
def querydslDir = "$buildDir/generated/querydsl"

querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}

sourceSets {
    main.java.srcDir querydslDir
}

configurations {
    querydsl.extendsFrom compileClasspath
}

compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

Q CLASS 만들기

오른쪽에 Gradle을 누르고 Tasks-> other-> compileJava를 누르고 build한다.

build가 성공하면 project에 build -> generated -> querydsl폴더가 생성된 것을 확인 할 수 있다.

Config 설정하기

config패키지를 만든 후 QueryDSLConfig 클래스를 만들어서 JPAQueryFactory를 주입받아 QueryDSL을 사용한다.

쿼리 사용하기

아래와 같은 도메인이 있다고 하자

@Entity
public class Order {

    @Id @GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member; //주문 회원

    @OneToMany(mappedBy = "order",cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();

    @OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
    @JoinColumn(name = "delivery_id")
    private Delivery delivery; //배송정보

    private LocalDateTime orderDate; //주문시간

    @Enumerated(EnumType.STRING)
    private OrderStatus status; //주문상태 [ORDER, CANCEL]
}

Querydsl은 Order와 동일한 패키지에 QOrder라는 이름을 가진 쿼리 타입을 생성한다.
Querydsl 쿼리에서 Order 타입을 위한 정적 타입 변수로 QOrder를 사용한다.
QOrder는 기본 인스턴스 변수를 갖고 있으며, 다음과 같이 정적 필드로 접근할 수 있다.

QOrder order=QOrder.order;

JPA API를 사용하려면 다음과 같이 JPAQuery 인스턴스를 사용하면 된다.

JPAQuery query = new JPAQuery(entityManager); 

JPAQueryFactory를 사용하는 방법도 권장한다고 한다.

JPAQueryFactory queryFactory;

예시

OrderRepository.class

public List<Order> findAll(OrderSearch orderSearch){
        QOrder order=QOrder.order;
        QMember member=QMember.member;
        return queryFactory
                .select(order)//selectFrom으로 사용해도 된다.
                .from(order)
                .join(order.member,member)//order의 member와 member를 join한다.
                 //동적 쿼리를 위해 상황에 따른 메서드를 만든다.
                .where(statusEq(orderSearch.getOrderStatus(),order),
                        nameLike(orderSearch.getMemberName(),member))
				.limit(1000)
                .fetch();
    }
    private BooleanExpression statusEq(OrderStatus statusCond,QOrder order){
        if (statusCond==null){
            return null;
        }
        return order.status.eq(statusCond);//eq -> =과 같은 의미
    }
    private BooleanExpression nameLike(String nameCond,QMember member){
        if (!StringUtils.hasText(nameCond)){
            return null;
        }
        return member.name.like(nameCond);
    }

참고참고
https://querydsl.com/static/querydsl/3.4.3/reference/ko-KR/html_single/
https://jojoldu.tistory.com/372

profile
코딩하는 감자

0개의 댓글