프로젝션 결과 반환

프로젝션 대상이 하나

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

프로젝션 대상이 둘 이상

프로젝션 대상이 둘 이상일 때는 Tuple 이나 DTO로 조회한다.

튜플 조회

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

Tuple은 com.querydsl.core에 위치한다.
튜플은 가능하면 Repository 단 내에서만 사용하고, 외부로 보낼 때는 DTO 로 변환하여 보내자.

DTO 조회

순수 JPA 에서 DTO 조회 코드

순수 JPA에서 DTO를 조회할 때는 new 를 이용해야 한다. 패키지 이름을 다 적어줘야 해서 매우 지저분하다.

List<MemberDto> result = em.createQuery(
                    "select new study.querydsl.dto.MemberDto(m.username, m.age)" +
                            " from Member m",
                    MemberDto.class)
            .getResultList();

Querydsl 빈 생성

아래의 3가지 방법이 있다.
1. 프로퍼티 접근 - Setter

List<MemberDto> result = queryFactory
            .select(Projections.bean(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();
  1. 필드 직접 접근
    필드에 바로 값을 넣어주는 방법으로, getter, setter 모두 필요없다.
List<MemberDto> result = queryFactory
            .select(Projections.fields(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();
  • 만약, 별칭이 다를 경우 예시
    QMember subMember = new QMember("subMember");
      List<UserDto> result = queryFactory
              .select(Projections.fields(UserDto.class,
                      member.username.as("name"),
                      ExpressionUtils.as(
                              JPAExpressions.select(subMember.age.max())
                                      .from(subMember), "age"
                      )
              ))
              .from(member)
              .fetch();
  1. 생성자 사용
List<MemberDto> result = queryFactory
            .select(Projections.constructor(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

@QueryProjection

QueryProjection 을 하기 위해서는 DTO 의 생성자에 @QueryProjection 어노테이션을 달아주어야 한다.

@QueryProjection
public MemberDto(String username, int age){
    this.username = username;
    this.age = age;
}

그리고 엔티티에 대해 Q 타입 파일을 생성할 때처럼, compileQuerydsl 을 클릭하면 QMemberDto 파일이 생성되는 것을 확인할 수 있다.
그럼, 아래와 같이 사용할 수 있다.

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

장점

앞서 살펴본 생성자 방식과는 달리, 생성자의 인자를 잘못 주었을 때 런타임이 아닌 컴파일 타임에 에러를 확인할 수 있다.

단점

DTO 가 Querydsl 에 의존적이게 된다.
DTO 의 생성자에 @QueryProjection 을 해주고, DTO의 Q 타입 파일을 생성해주어야 한다.

동적 쿼리

동적 쿼리를 해결하는 방식은 2 가지가 있다.
1. BooleaBuilder 을 사용하는 방식
2. Where 다중 파라미터를 사용하는 방식

BooleanBuilder

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 다중 파라미터 사용

private List<Member> searchMember2(String usernameCond, Integer ageCond) {
    return queryFactory
            .selectFrom(member)
//                .where(usernameEq(usernameCond), ageEq(ageCond))
            .where(allEq(usernameCond, ageCond))
            .fetch();
}

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

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

private Predicate allEq(String usernameCond, Integer ageCond){
    return usernameEq(usernameCond).and(ageEq(ageCond));
}

장점

  1. where 조건 내의 null 값은 무시된다.
  2. 메소드를 재사용하여 다른 쿼리에서도 사용할 수 있다.
  3. 쿼리 가독성이 높아진다.
  4. 여러 조건들을 조합하여 사용하는 것도 가능하다. -> 이 경우 null 체크를 주의해야 한다!

수정, 삭제 벌크 연산

주의할 점

벌크 연산은 영속성 컨텍스트는 무시하고 DB 에 바로 쿼리가 나간다.
따라서 DB 와 영속성 컨텍스트의 상태가 달라져버린다.

벌크 연산을 실행하고 나면 영속성 컨텍스트를 초기화해주자!

em.flush();
em.clear();

데이터 수정

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();

SQL function 호출하기

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

member -> M 으로 변경하는 replace 함수 사용

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

소문자로 변경하기

List<String> result = queryFactory
          .select(member.username)
          .from(member)
		  .where(member.username.eq(
          		Expressions.stringTemplate("function('lower', {0})", member.username)))
          .fetch();

lower 같은 간단한 함수들은 이미 표준에서 제공하고 있다. 따라서 아래의 코드를 써도 똑같이 동작한다.

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

출처

  • 인프런 김영한 님의 실전! Querydsl 강의를 수강하며 정리한 내용입니다.
profile
컴공학부생입니다.

0개의 댓글

Powered by GraphCDN, the GraphQL CDN