QueryDsl

QueryDSL 중급문법

유휴 2023. 1. 9. 00:13
 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, - 강의 소개 | 인프런...

www.inflearn.com

문법 관련해서는 이미 정리가 잘된 강의자료가 있지만 SpringBoot + JPA + QueryDSL을 사용하여 토이프로젝트를 진행하기 전에 강의 내용을 한번 더 정리하고 진행하기 위해 해당 포스팅을 작성한다.

 

프로젝션과 결과 반환

  • 프로젝션 대상이 하나면 반환 타입을 명확하게 지정할 수 있다.
  • 프로젝션 대상이 둘 이상이면 반환 타입을 튜플 또는 DTO로 조회한다.

프로젝션 대상이 하나

자바 기본타입(String)으로 조회

List<String> result = queryFactory.select(member.username)
                                  .from(member)
                                  .fetch();

프로젝션 대상이 둘 이상

Tuple 타입으로 조회

List<Tuple> result = queryFactory.select(member.username, member.age)
                                 .from(member)
                                 .fetch();

DTO 타입으로 조회

DTO 타입으로 조회하는 3가지 방법

  • 프로퍼티 접근(Setter 사용) - Projections.bean
  • 필드 직접 접근 - Projections.fields
  • 생성자 사용 - Projections.constructor생성자를 사용한 DTO 조회 예제

별칭(alias)을 지정하는 방법

  • field.as("alias") 메서드를 통해 별칭을 지정하는 방법
  • ExpressionUtils.as(JPAExpressions, "alias") 메서드를 통해 별칭을 지정하는 방법
    첫 번째 인자로 JPAExpressions 객체를 이용하여 서브쿼리를 생성하고 두 번째 인자로 alias를 지정한다.
    * ExpressionUtils은 Querydsl 내부에서 새로운 Expression을 사용할 수 있도록 지원하는 객체이다.

필드 직접 접근을 통한 DTO 타입 조회

List<MemberDTO> fetch = queryFactory.select(Projections.fields(MemberDTO.class,
                                            member.username.as("name"),
                                            ExpressionUtils.as(
                                                            JPAExpressions.select(
                                                                           memberSub.age.max())
                                                            .from(memberSub), "age")))
                                    .from(member)
                                    .fetch();

 

@QueryProjection

DTO의 생성자에 @QueryProjection 어노테이션을 선언하여 프로젝션 결과를 반환 받을 수 있다.

 

장점

  • 컴파일러로 타입을 체크할 수 있다
  • 쿼리 실행코드가 간결해진다.

단점

  • DTO에 QueryDSL어노테이션을 사용해야한다.
  • DTO를 Q파일로 생성해야한다.
  • DTO가 QueryDSL에 종속된다.

@QueryProjection 선언 예제

@Data
public class MemberDTO {
      private String username;
      private int age;
      
      public MemberDTO() {}
      
      @QueryProjection
      public MemberDTO(String username, int age) {
          this.username = username;
          this.age = age;
      }
}

 

@QueryProjection 쿼리 실행 예제

List<MemberDto> result = queryFactory.select(new QMemberDTO(member.username, member.age))
                                     .from(member)
                                     .fetch();

중복제거

JPQL과 동일하게 select절 뒤에 .distinct() 메서드를 작성해주면 된다.

List<String> result = queryFactory.select(member.username).distinct()
                                  .from(member)
                                  .fetch();

동적 쿼리

동적 쿼리를 작성하는 두가지 방법

  • BooleanBuilder 사용
  • Where 다중 파라미터 사용 

BooleanBuilder 사용

String usernameParam = "member1";
Integer ageParam = 10;

BooleanBuilder builder = new BooleanBuilder();
if (usernameParam != null) builder.and(member.username.eq(usernameParam));
if (ageParam != null) builder.and(member.age.eq(ageParam));

List<Member> result = queryFactory.selectFrom(member).where(builder).fetch();

Where 다중 파라미터 사용

where절에서 다중 파라미터를 지정하게 되면 and조건으로 쿼리를 실행한다.

BooleanExpression을 반환 타입으로 하는 메서드를 선언하여 다중 파라미터로 지정하면 가독성 좋은 쿼리를 작성 가능하다.

 

특징

  • where 조건에 null 값은 무시된다.
  • 메서드를 다른 쿼리에서 재사용 할 수 있다.
private List<Member> searchMember(String usernameCond, Integer ageCond) {
    return queryFactory.selectFrom(member)
                       .where(usernameEq(usernameCond), ageEq(ageCond))
                       .fetch();
}

private BooleanExpression usernameEq(String usernameCond) {
    return usernameCond != null ? member.username.eq(usernameCond) : null;
}

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

수정, 삭제 벌크 연산

영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 벌크 연산 실행 후 영속성 컨텍스트를 초기화 하는 것이 안전하다.

long count = queryFactory.update(member)
                         .set(member.username, "비회원")
                         .where(member.age.lt(28))
                         .execute();
long count = queryFactory.delete(member)
                         .where(member.age.gt(18))
                         .execute();

SQL function 호출하기

SQL functionJPA와 같이 Dialect에 등록된 내용만 호출할 수 있다.

Expressions.stringTemplate(String, Object ...) 메서드를 사용하여 쿼리를 작성하면 된다.

String result = queryFactory.select(Expressions
                                      .stringTemplate("function('replace', {0}, {1}, {2})"
                                                      , member.username, "member", "M"))
                            .from(member)
                            .fetchFirst()

ansi 표준 함수들은 querydsl이 상당부분 내장하고 있어 아래와 같이 사용할 수 있다.

.where(member.username.eq(member.username.lower()))