[JPA] QueryDSL는 왜 사용할까?

wujin·2024년 7월 5일

QueryDSL이 뭔데?

  • QueryDSL은 HQL(Hibernate Query Language), SQL, JPQL 등을 타입에 안전하게 생성하고 관리해주는 프레임워크이다. 이를 통해 Java와 Kotlin에서 정적 타입을 이용해 쿼리를 작성할 수 있다.
  • 문자열 기반의 쿼리가 아닌, Java 코드로 쿼리를 작성하여 컴파일 시점에 오류를 검출할 수 있는 장점이 있다.
  • JPA 뿐 아니라 SQL, MongoDB, Lucenece 등 다양한 어너에 대해서 서비스를 제공한다.

필요성 및 배경

전통적으로 JPQL, MyBatis, Criteria API 등은 쿼리를 문자열 형태로 작성해야했다. 이로 인해 오타나 구문 오류를 컴파일 시점에 잡아낼 수 없고, 런타임에서만 오류를 확인할 수 있었다. QueryDSL은 이러한 문제를 해결하기 위해 등장했으며, 타입 안전한 쿼리 작성을 통해 컴파일 시점에 오류를 검출하고 읽기 쉽게 유지보수하기 쉬운 코드를 작성할 수 있게 해준다.


주요 기능 및 장점

1. 타입 안정성 (Type Safety)

  • 일반적으로 SQL은 type check가 불가능하고 실행되기 전까지 작도 여부를 확인하기 어렵다.
  • QueryDSL은 Java나 Kotlin으로 정적 타입을 사용하여 type safety하게 개발할 수 있다. 그래서 컴파일 시점에 오류를 검출할 수 있다.
  • 이는 쿼리 작성시 오타나 잘못된 데이터 타입으로 인한 오류를 방지한다.

2. 자동 완성 기능

  • IDE의 자동 완성 기능을 통해 쿼리를 쉽게 작성할 수 있어 개발 생산성을 향상시킨다.

3. 복잡한 쿼리 작성

  • 다양한 조건을 결합하거나 동적으로 추가할 수 있어 복작한 쿼리나 동적 쿼리 작성이 용이하다.

4. QClass 생성

  • 컴파일 시 엔티티를 기반으로 QClass를 생성한다.
  • QClass는 APT(Annotation Processor Tool)를 사용하여 생성된다.
  • QClass는 엔티티 메타 정보를 담고 있는 클래스로, QClass 기반으로 type safety하게 쿼리를 작성할 수 있게 된다.
  • QClass는 엔티티 클래스의 속성을 static 방식으로 표현한다.

APT(Annotation Processor Tool)

  • 어노테이션이 있는 기존 코드를 바탕으로 새로운 파일들을 만들 수 있고, 이들을 이용한 클래스에서 컴파일하는 기능을 지원.
  • Lombok의 @Getter@Setter를 보면, APT가 컴파일 시점에 해당 어노테이션 기준으로 getter와 setter의 메서드를 만들어 주는 것.

5. 쿼리 재사용성

  • 쿼리 작성 시 제약 조건 등을 메서드로 추출하여 재사용할 수 있다.
  • 이는 코드의 중복을 줄이고, 가독성과 유지보수성을 향상시킨다.

6. 높은 가독성

  • JPQL 문법과 유사한 형태로 작성할 수 있어 쉽게 적응할 수 있다.

JPQL과 QueryDSL 비교

JPQL

String username = "java";
String jpql = "select m from Member m where m.username = :username";

List<Member> result = em.createQuery(jpql, Member.class)
                        .setParameter("username", username)
                        .getResultList();
  • JPQL에서는 문자열로 쿼리를 작성하므로 type-check가 어렵고 오타나 잘못된 구문으로 인한 오류를 컴파일 시점에 발견 할 수 없다.
  • 테스트 코드를 작성하면 어느정도 문제를 해결할 수 있지만, 실제 운영환경에서 오류가 발생할 수 있다는 부담이 크다.

QueryDSL

String username = "java";
List<Member> result = queryFactory
        .selectFrom(member)
        .where(member.username.eq(username))
        .fetch();

QueryDSL은 코드로 쿼리를 작성하여 오타가 발생할 확률이 적고 컴파일 시점에 오류를 검출할 수 있으며, 객체지향적으로 개발이 가능하다. 또한, IDE의 자동 완성 기능을 통해 더 쉽게 쿼리 작성이 가능하다.


QueryDSL 사용 예시

동적 쿼리 작성

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private int age;
    // getters and setters
}

public List<User> searchUsers(String name, Integer minAge, Integer maxAge) {
    JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
    QUser qUser = QUser.user;

    BooleanBuilder builder = new BooleanBuilder();
    if (name != null) {
        builder.and(qUser.name.containsIgnoreCase(name));
    }
    if (minAge != null) {
        builder.and(qUser.age.goe(minAge));
    }
    if (maxAge != null) {
        builder.and(qUser.age.loe(maxAge));
    }

    return queryFactory.selectFrom(qUser)
                       .where(builder)
                       .fetch();
}

쿼리 재사용성 예시

  • QueryDSL을 사용하면 자주 사용되는 쿼리 조건을 메서드로 추출하여 재사용할 수 있다.
public BooleanExpression usernameEq(String username) {
    return username == null ? null : QUser.user.username.eq(username);
}

public BooleanExpression ageBetween(Integer minAge, Integer maxAge) {
    BooleanBuilder builder = new BooleanBuilder();
    if (minAge != null) {
        builder.and(QUser.user.age.goe(minAge));
    }
    if (maxAge != null) {
        builder.and(QUser.user.age.loe(maxAge));
    }
    return builder;
}

public List<User> searchUsers(String username, Integer minAge, Integer maxAge) {
    return queryFactory.selectFrom(QUser.user)
                       .where(usernameEq(username), ageBetween(minAge, maxAge))
                       .fetch();
}

이처럼 자주 사용하는 조건을 메서드로 추출하여 쿼리 작성 시 재사용할 수 있어 코드의 중복을 줄이고 유지보수성을 높일 수 있다.


참고자료
https://velog.io/@jmjmjmz732002/SpringBoot-QueryDSL-JPA-1-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0-%EC%83%9D%EA%B0%81%ED%95%B4%EB%B3%B4%EA%B8%B0

0개의 댓글