[QueryDSL] 중급 문법

황인찬·2024년 7월 28일
post-thumbnail

프로젝션 반환 결과 - 기본

  • 프로젝션 반환 결과가 하나일 때
    • 타입을 명확하게 지정할 수 있음
List<String> result = queryFactory
		.select(member.username)
        .from(member)
        .fetch();
  • 프로젝션 반환 결과가 둘 이상일 때
    • 튜플이나 DTO로 조회
List<Tuple> result = queryFactory
		.select(member.username, member.age)
        .from(member)
        .fetch();

for (Tuple tuple : result) {
		String username = tuple.get(member.username);
        Integer age = tuple.get(member.age);
        System.out.println("username = " + username);
        System.out.println("age = " + age);
}

프로젝션 반환 결과 - DTO 조회

  • 순수 JPA에서 DTO 조회
    • new 명령어를 사용해야함
    • DTO의 패키지명을 다 적어야해서 지저분함
    • 생성자 방식만 지원함
List<MemberDto> result = em.createQuery("select new study.querydsl.dto.MemberDto(m.username, m.age) " +
                        "from Member m", MemberDto.class)
                .getResultList();
  • QueryDSL 빈 생성(Bean population)

    • 3가지 방법 지원

      • 프로퍼티 접근
      • 필드 직접 접근
      • 생성자 사용
    • 프로퍼티 접근 - Setter

    List<MemberDto> result = queryFactory
                  .select(Projections.bean(MemberDto.class,
                          member.username,
                          member.age))
                  .from(member)
                  .fetch();
    • 필드 직접 접근
    List<MemberDto> result = queryFactory
                  .select(Projections.fields(MemberDto.class,
                          member.username,
                          member.age))
                  .from(member)
                  .fetch();
    • 별칭이 다를 때
      • 프로퍼티, 필드 접근 생성 방식에서 이름이 다를 때 해결 방안
      • ExpressionsUtils.as(source, alias) : 필드나 서브쿼리에 별칭 적용
      • username.as("name") : 필드에 별칭 적용
    List<UserDto> result = queryFactory
                  .select(Projections.fields(UserDto.class,
                          member.username.as("name"),
                          ExpressionUtils.as(JPAExpressions
                                  .select(memberSub.age.max())
                                  .from(memberSub), "age")
                  ))
                  .from(member)
                  .fetch();
    • 생성자 사용
    List<MemberDto> result = queryFactory
                  .select(Projections.constructor(MemberDto.class,
                          member.username,
                          member.age))
                  .from(member)
                  .fetch();

    프로젝션 반환 결과 - @QueryProjection

    • 생성자 + @QueryProjection
      • QMemberDto 파일이 생성됨
     @QueryProjection
      public MemberDto(String username, int age) {
          this.username = username;
          this.age = age;
      }
    • @QueryProjection 활용
      • 컴파일러로 타입을 체크할 수 있어서 가장 안정적임
      • DTO에 QueryDSL 어노테이션을 유지해야 하는 점과 DTO까지 Q파일을 생성해야 하는 단점이 있음
    List<MemberDto> result = queryFactory
                  .select(new QMemberDto(member.username, member.age))
                  .from(member)
                  .fetch();

동적 쿼리 - BooleanBuilder 사용

 @Test
  public void dynamicQuery_BooleanBuilder() throws Exception{
      String usernameParam = "member1";
      Integer ageParam = 10;

      List<Member> result = searchMember1(usernameParam, ageParam);
      assertThat(result.size()).isEqualTo(1);
  }

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

동적 쿼리 - Where 다중 파라미터 사용

 @Test
  public void dynamicQuery_WhereParam() throws Exception{
      String usernameParam = "member1";
      Integer ageParam = 10;

      List<Member> result = searchMember2(usernameParam, ageParam);
      assertThat(result.size()).isEqualTo(1);
  }

  private List<Member> searchMember2(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;
  }
  • where 조건에 null 값은 무시됨
    • 메소드를 다른 쿼리에서도 재사용 가능
    • 쿼리의 가독성이 높아짐
    • 조합 가능
      • null체크는 주의해야함🚫
      private BooleanExpression allEq(String usernameCond, Integer ageCond) {
          return usernameEq(usernameCond).and(ageEq(ageCond));
      }

수정, 삭제 벌크 연산

  • 쿼리 한 번으로 대량의 데이터 수정
    long count = queryFactory
                   .update(member)
                   .set(member.username, "비회원")
                   .where(member.age.lt(28))
                   .execute();
  • 기존 숫자에 1더하기
    long count = queryFactory
                   .update(member)
                   .set(member.age, member.age.add(1))
                   .execute();
  • 쿼리 한 번으로 대량의 데이터 삭제
    long count = queryFactory
                   .delete(member)
                   .where(member.age.gt(18))
                   .execute();

    주의🚫: JPQL 배치와 마찬가지로, 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를 실행 후 영속성 컨텍스트를 초기화 하는 것이 안전함

SQL function 호출하기

  • member -> M으로 변경하는 replace 함수 사용
List<String> result = queryFactory
                .select(
                        Expressions.stringTemplate(
                                "function('replace', {0}, {1}, {2})",
                                member.username, "member", "M"))
                .from(member)
                .fetch();
  • 소문자로 변경하여 비교
    • lower 같은 ansi 표준 함수들은 querydsl이 상당 부분 내장하고 있음
 List<String> result = queryFactory
                .select(member.username)
                .from(member)
//                .where(member.username.eq(
//                        Expressions.stringTemplate("function('lower', {0})", member.username)))
                .where(member.username.eq(member.username.lower()))
                .fetch();
profile
찬이's 개발로그

0개의 댓글