Querydsl? 동적쿼리?

eunhye_·2022년 12월 30일
2

Spring

목록 보기
2/5
post-thumbnail

QueryDsl이란

QueryDsl은 정적 타입을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해주는 프레임워크이다.

왜 사용하나?

쿼리를 문자가 아니라 진짜 자바 코드로 작성할 수 있게 도와준다. 때문에 자바 코드로 작성하지만 SQL, JPQL과 문법이 거의 같기 때문에 쉽게 학습할 수 있고, 또 쉽게 복잡한 쿼리도 작성할 수 있다.

JPA를 사용한다고 가정해보자. 간단한 쿼리라면 인터페이스에 메서드 명세만 잘 정의해 주면 별다른 문제 없이 사용할 수 있을 것이다. 예를 들면 아래처럼 “제목에 특정 문자열이 포함된 기사를 조회”하는 메서드처럼 말이다.

Post findByTitleContains(String title);

조금 더 복잡한 쿼리가 필요한 경우는 어떨까? 이런 경우에는 JPA 자체 제공 메서드만으로 해결하기 어렵기 때문에 네이티브 쿼리(Native Query)를 고려해볼 수 있다. 다음은 레벨이 특정 기준 이상인 사용자가 작성한 게시물을 조회하는 메서드다.

@Query(value = "SELECT id, title, user_id FROM post WHERE user_id IN (SELECT id FROM user WHERE level > :level)", nativeQuery = true)
List<Post> findByLevel(String level);

위에서 정의한 네이티브 쿼리를 보면 가독성은 감안하더라도 문자열을 이어 붙여가며 직접 작성하기 때문에 오타가 발생하기 아주 좋다.

위 코드를 Querydsl로 변경하면 이렇다.

public List<Post> findByUserLevel(String level) {
    QPost post = QPost.post;
    QUser user = QUser.user;

    return queryFactory.selectFrom(post)
        .where(
            post.userId.in(
                JPAExpressions
                    .select(user.id)
                    .from(user)
                    .where(user.level.gt(level))
            )
        )
        .fetch();
}

코드의 가독성이 좋아졌고, 메서드 타입에 맞지 않는 파라미터를 넘기는 경우 친절하게 컴파일 오류를 발생시켜 잠재적인 버그를 방지해준다.

즉, 실행 시점 이전에 잘못된 쿼리 파라미터 타입까지 확인할 수 있는 장점이 있다.

Querydsl은 동적 쿼리를 위해서 많이 사용된다. 그렇다면 동적쿼리란 무엇인가?

동적 쿼리란?

동적 쿼리란 상황에 따라 다른 문법의 SQL을 적용하는 것을 말한다.

예를 들면 DB에서 값을 조회할 때 조회 조건이 동적으로 바뀌어야 하는 경우가 많다. 이런 상황을 Querydsl을 사용하면 손쉽게 해결할 수 있다.

  • name 값이 들어오면 WHERE name = ${name}
  • age 값이 들어오면 WHERE age = ${age}
  • name과 age가 모두 들어오면 WHERE name = ${name} AND age=${age}
  • name과 age 모두 들어오지 않으면 WHERE 절을 사용하지 않는다.

1. BooleanBuilder

private List<Member> searchMember(String nameCond, Integer ageCond) {
    BooleanBuilder builder = new BooleanBuilder();
    
    if(nameCond != null) {
        builder.and(member.name.eq(nameCond));
    }
    if(ageCond != null) {
        builder.and(member.age.eq(ageCond));
    
    return queryFactory
            .selectFrom(member)
            .where(builder)
            .fetch();
}

BooleanBuilder를 추가해서 파라미터의 상태에 따라 다른 where절을 builder에 삽입한다.

BooleanBuilder의 문제점은 where절을 통째로 보기가 어렵다. 로직을 따라가면서 신경을 기울여야 쿼리문을 이해할 수 있다. 조건이 훨씬 까다로워지면 결과를 추측하기도 힘들어지는 쿼리가 될 수 있다.

2. BooleanExpression

Querydsl은 아래 2가지 기능을 제공한다.

  • where()에 null이 들어오면 무시한다.
  • where()에 , 을 and 조건으로 사용한다.

두 가지 기능을 사용하면 아래와 같이 코드를 작성하는 것이 가능하다.

private List<Member> searchMember(String nameCond, Integer ageCond) {
    return queryFactory
            .selectFrom(member)
            .where(nameEq(nameCond), ageEq(ageCond))
            .fetch();
}

private BooleanExpression nameEq(String nameCond) {
    if (nameCond == null) {
        return null;
    }
    return member.name.eq(nameCond);
}

private BooleanExpression ageEq(Integer ageCond) {
    if (ageCond == null) {
        return null;
    }
    return member.age.eq(ageCond);
}

유지보수를 할 때 searchMember()를 보게 되므로 queryFactory의 where() 메서드의 인자로 사용되는 메서드명을 보고 어렵지 않게 쿼리를 파악할 수 있다.

BooleanExpression의 큰 장점 중 하나가 BooleanExpression 객체들을 조립할 수 있다는 것이다. 복잡한 서비스의 경우 검색 조건이 여러가지 상태를 의존하는 경우가 많기 때문에 해당 기능을 사용하면 Composition(조립, 구성)을 깔끔하게 쿼리를 작성할 수 있다.

private BooleanExpression allEq(String usernameCond, Integer ageCond, String op) {
    if(op.equals("and")) {
        usernameEq(usernameCond).and(ageEq(ageCond));
    }
    if(op.equals("or")) {
        return usernameEq(usernameCond).or(ageEq(ageCond));
    }
    return  null;
}

이렇게 작성한 메서드는 다른 Repository 메서드에서 재사용도 할 수 있다.

그래서 QueryDSL의 BooleanExpression을 이용해서 동적 쿼리를 더 보기좋고 유지보수도 편리하게 해결할 수 있다.

다음에는 프로젝트에 적용시켜보자!

Refference
https://tecoble.techcourse.co.kr/post/2021-08-08-basic-querydsl/
https://jaehoney.tistory.com/185

0개의 댓글