QueryDSL 입문!

Minsu Kang·2021년 2월 28일
0

데이터베이스

목록 보기
4/4

QueryDSL 이란?

  • SQL, JPQL을 코드로 작성할 수 있도록 도와주는 빌더 API
  • JPA Criteria에 비해 편리하고 실용적이다.

SQL, JPQL의 문제점

  • SQL, JPQL은 문자열이기 때문에 type-check가 불가능하다.
  • 따라서 해당 로직의 실행 전까지 정상적으로 동작하는지 확인이 불가능하다.

QueryDSL의 장점

  • 문자가 아닌 코드로 작성한다. 따라서 컴파일 시점에 문법 오류를 발견할 수 있다.
  • IDE의 도움을 받을 수 있다.
  • 동적 쿼리 작성이 편리하다.
  • 제약조건을 조립할 수 있고 재사용할 수 있다.

Q-type class

Q-type class는 QueryDSL 설정을 성공적으로 마치면 @Entity가 붙은 클래스를 찾아 자동으로 생성된다.

예를들어 @Entity가 붙은 User.java 클래스가 있다면 QUser.java 파일이 자동으로 생성된다.

이러한 Q-type의 class들은 QueryDSL을 사용하여 메소드 기반으로 쿼리를 작성할 때 우리가 만든 도메인 클래스의 구조를 설명해주는 메타데이터 역할을 하며 쿼리의 조건을 설정할 때 사용된다.

간단한 사용법

JPAQueryFactory

QueryDSL을 사용하여 쿼리를 Build 하기 위해서는 JPAQueryFactory가 필요하다. JPAQueryFactory를 사용하면 EntityManager를 통해서 질의가 처리되고, JPQL을 사용한다.

참고로 SQLQueryFactory 라는것도 있는데 이 것을 사용하면 JDBC를 사용하여 질의가 처리되고, SQL을 사용한다.

다음과 같이 얻어올 수 있다.

EntityManagerFactory emf = 
  Persistence.createEntityManagerFactory("com.baeldung.querydsl.intro");
EntityManager em = entityManagerFactory.createEntityManager();
JPAQueryFactory queryFactory = new JPAQueryFactory(em);

필터링 쿼리

QUser user = QUser.user;

User c = queryFactory.selectFrom(user)
  .where(user.login.eq("David"))
  .fetchOne();

QueryFactory의 selectFrom() 메서드 (또는 select)를 호출하면 JPAQuery를 리턴하고 쿼리 build가 시작된다.

(참고로, QueryFactory는 select절부터 적을 수 있지만 JPAQuery는 그렇지 못하다.)

그리고 where()를 호출하여 쿼리의 조건절을 build 한다. 이때, Q-type의 인스턴스를 사용하여 적절한 조건을 설정할 수 있다.

마지막으로 fetchOne()를 호출하여 building chain을 끝내고 데이터베이스에서 데이터를 꺼내와 영속성 컨텍스트에 저장한다.

fetchOne()은 해당하는 데이터가 없으면 null을 반환하고, 데이터가 둘 이상 존재하면 NonUniqueResultException을 throws 한다.

Ordering, Grouping

orderBy()를 호출하여 정렬조건을 설정할 수 있다.

List<User> c = queryFactory.selectFrom(user)
  .orderBy(user.login.asc())
  .fetch();

위와 같이 작성하면 user.login을 기준으로 오름차순 정렬하여 데이터를 가져온다.

NumberPath<Long> count = Expressions.numberPath(Long.class, "c");

List<Tuple> userTitleCounts = queryFactory.select(
  blogPost.title, blogPost.id.count().as(count))
  .from(blogPost)
  .groupBy(blogPost.title)
  .orderBy(count.desc())
  .fetch();

위의 쿼리는 blog.title로 그룹핑하여 같은 title을 가진 데이터의 개수를 기준을 정렬한다.

count() 필드의 alias를 orderBy의 인자로 사용한 것에 주의하자.

Join과 Subquery

QBlogPost blogPost = QBlogPost.blogPost;

List<User> users = queryFactory.selectFrom(user)
  .innerJoin(user.blogPosts, blogPost)
  .on(blogPost.title.eq("Hello World!"))
  .fetch();

위 코드는 User와 BlogPost를 조인하여 blogPost가 "hello world"인 행을 가져온다.

직접 테스트 해보니 on으로 되어있지만 실제 쿼리의 where처럼 동작하는듯 하다.

innerJoin()하는순간 user.blogPosts.id = blogPost.id 인 것만 가져오는듯 하다.

아래 서브 쿼리를 활용한 코드이다.

List<User> users = queryFactory.selectFrom(user)
  .where(user.id.in(
    JPAExpressions.select(blogPost.user.id)
      .from(blogPost)
      .where(blogPost.title.eq("Hello World!"))))
  .fetch();

데이터 수정

where 조건에 맞는 레코드를 set에 설정한 데이터로 수정할 수 있다.

where 없이 사용하면 모든 레코드를 수정한다.

queryFactory.update(user)
  .where(user.login.eq("Ash"))
  .set(user.login, "Ash2")
  .set(user.disabled, true)
  .execute();

데이터 삭제

where 조건에 맞는 레코드를 삭제한다.

queryFactory.delete(user)
  .where(user.login.eq("David"))
  .execute();

데이터 삽입

JPAQueryFactory는 insert()를 가지고 있지 않다.

insert를 하려면 EntityManager를 사용하자! (SQLQueryFactory는 가능하다)

참고한 곳

https://www.youtube.com/watch?v=gRqyzi9VGYc ([토크ON세미나] JPA 프로그래밍 기본기 다지기 8강 - Spring Data JPA와 QueryDSL 이해 | T아카데미)
https://www.baeldung.com/intro-to-querydsl

profile
백엔드 개발자

1개의 댓글

comment-user-thumbnail
2022년 5월 10일

select pet_age, (total / t2.cnt ) * 100 as 퍼센트
from (
select pet_age, count() as total
from pet
group by pet_age
) as t1, (select count() as cnt
from pet) as t2
group by pet_age
order by pet_age asc;
위 코드를 QueryDsl로 사용하려면 어떻게 해야할까요..?
alias같은 걸 어떤식으로 처리해줘야 할지 모르겠습니다 ㅠㅠ

답글 달기