QueryDSL 중급 문법

강한친구·2022년 8월 22일
1

JPA

목록 보기
23/27

프로젝션 결과 반환

Tuple로 반환

가장 간단한 형태의 결과반환은 튜플형을 사용하면 된다.

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

String, int 형을 담은 두 형태가 알아서 들어가게 된다.

DTO 반환

package study.querydsl.domain;

import lombok.Data;

@Data
public class MemberDto {

    private String username;
    private int age;

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

이런 형태의 DTO를 반환하려고 한다.

순수 JPQL에서는 new 를 활용해서 이를 반환한다.

    @Test
    public void findDtoByJpql() {
        List<MemberDto> result = em.createQuery(
                        "select new study.querydsl.domain.MemberDto(m.username, m.age) " +
                                "from Member m", MemberDto.class)
                .getResultList();
    }

하지만 이 방식은 패키지명을 하나씩 다 넣어야해서 그렇게 편리한 방법은 아니다.

쿼리 DSL에서는 더 깔끔하게 처리할 수 있다.

Setter

    @Test
    public void findDtoBySetter() {
        List<MemberDto> result = queryFactory
                .select(Projections.bean(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
    }

Projections를 이용하면, memberDto안에 집어넣을 수 있다. 다만 이 방법은 MemberDto를 만들고 그 안에 조회한 값을 넣는 방식이기때문에, 기본생성자 혹은 NoArgsConstructor가 필요하다.

Field

    @Test
    public void findDtoByField() {
        List<MemberDto> result = queryFactory
       		.select(Projections.fields(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
    }

필드에 있는상태에서 바로 만든다. 따라서 생성자를 따로 사용하지 않는다.

constructor

    @Test
    public void findDtoByConstructor() {
        List<MemberDto> result = queryFactory
        	.select(Projections.constructor(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
    }

생성자를 통해 만드는 방식이다.

별칭이 다를때

DTO의 변수명과 필드명이 다를 수 있다.

package study.querydsl.domain;

import lombok.Data;

@Data
public class UserDto {
   private String name;
   private int age;
}
   @Test
   public void findDtoByDiffName() {
       QMember memberSub = new QMember("memberSub");
       List<UserDto> fetch = queryFactory
               .select(Projections.fields(UserDto.class,
                               member.username.as("name"),
                               ExpressionUtils.as(
                                       JPAExpressions
                                               .select(memberSub.age.max())
                                               .from(memberSub), "age")
                       )
               ).from(member)
               .fetch();
   }

이런 경우, as를 이용해서 이름을 맞춰줄 수 있다.

QueryProjection

Dto 생성자에 QueryProjection 어노테이션을 주면 쿼리 프로젝션으로 DTO를 만들 수 있다.

@Data
@NoArgsConstructor
public class MemberDto {

    private String username;
    private int age;

    @QueryProjection
    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}
@Test
    public void findByQueryProjection(){
        List<MemberDto> result = queryFactory
                .select(new QMemberDto(member.username, member.age))
                .from(member)
                .fetch();
    }

왜 이런 방식을 사용할까?
기존의 생성자방식을 사용하면, 오류가 발생하더라도 실행 후에 알 수 있다. 즉, 런타임 오류가 발생하는것이다.

하지만 QueryProjection Annotation을 사용하면, 실행전에 오류를 잡아낼 수 있다.

하지만 DTO에 QueryDSL 어노테이션을 유지해야 하는 점과 DTO까지 Q 파일을 생성해야 하는 단점이 있다. 즉, DSL 의존적 코딩이 되는 부분을 유의해야한다.

동적쿼리 - BooleanBuilder

동적쿼리를 처리하는 첫번째 방법이다.

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

따로 BooleanBuilder를 사용하는것이 아닌, where 절 안에서 한번에 처리하는 방법이다.

    @Test
    public void 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 값은 무시된다.
메서드를 다른 쿼리에서도 재활용 할 수 있다.
쿼리 자체의 가독성이 높아진다.

수정 삭제 벌크연산

쿼리 한번으로 대량 데이터 수정이다.

벌크연산후에는 가능하면 영속성 컨텍스트를 날려버려야 한다

한번에 바꾸기

멤버회원나이가 28 이하면, 전부 비회원으로 바꾸는 연산이다.

	@Test
    public void bulkChangeAll() {
        long count = queryFactory
                .update(member)
                .set(member.username, "비회원")
                .where(member.age.lt(28))
                .execute();
    }

기존값에 1씩 더하기

    @Test
    public void bulkAddOne() {
        long count = queryFactory .update(member)
                .set(member.age, member.age.add(1))
                .execute();
    }

전체 삭제

    @Test
    public void bulkDelete() {
        long count = queryFactory
                .delete(member)
                .where(member.age.gt(18))
                .execute();
    }

SQL function 호출

userName의 member부분을 M으로 바꿔서 호출한다.

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

혹은 소문자로도 처리가 가능하다.

    @Test
    public void sqlFunctionLower() {
        String result = queryFactory
                .select(member.username)
                .from(member)
                .where(member.username.eq(Expressions.stringTemplate("function('lower', {0})",
                        member.username)))
    }
    

0개의 댓글