ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • QueryDSL 중급 문법
    이커머스 devops/국비교육 2025. 11. 19. 13:11

     프로젝션과 결과 반환 - 기본

    - 프로젝션: select 대상 지정

    // 프로젝션 대상이 하나
    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 조회
    @Data
    public class MemberDto {
        private String username;
        private int age;
    
        public MemberDto() {}
        
        public MemberDto(String username, int age) {
            this.username = username;
            this.age = age;
        }
    }
    
    // 순수 JPA에서 DTO 조회 코드
    List<MemberDto> result = em.createQuery(
        "select new study.querydsl.dto.MemberDto(m.username, m.age) " +
        "from Member m", MemberDto.class)
        .getResultList();
    • 튜플
      • 튜플은 Querydsl 종속적이기 때문에 밖으로 나갈 때는 DTO 변환해서 전달 권장
    • 순수 JPA에서 DTO 조회 코드
      • 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야 한다
      • DTO의 pacakge 이름을 다 적어줘야 해서 코드가 지저분하다
      • 생성자 방식만 지원한다

     

     

    Querydsl 빈 생성 (Bean population)

    결과 DTO 반환 3가지 방법

    - 프로터피 접근

    - 필드 직접 접근

    - 생성자 사용

     

     

    1. 프로퍼티 접근 - Setter

    @Test
    public void findDtoBySetter() {
        List<MemberDto> result = queryFactory
                .select(Projections.bean(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
        for (MemberDto dto : result) {
            System.out.println("dto = " + dto);
         }
    }

     

     

    2. 필드 직접 접근

    @Test
    public void findDtoByField() {
        List<MemberDto> result = queryFactory
                .select(Projections.fields(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
        for (MemberDto dto : result) {
            System.out.println("dto = " + dto);
         }
    }

     

     

    cf. 필드 직접 접근 - 별칭이 다를 때

    @Data
    public class UserDto {
        private String name;
        private int age;
    }
    
    @Test
    public void findDUserDtoByField() {
        QMember memberSub = new QMember("memberSub");
        
        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();
        for (UserDto dto : result) {
            System.out.println("dto = " + dto);
        }
    }
    • 프로퍼티나 필드 접근 생성 방식이 이름이랑 다를 때
    • ExpressionUtils.as(source, alias) : 필드나 서브쿼리에 별칭 적용
    • username.as("memberName") : 필드에 별칭 적용

     

     

    3. 생성자 사용

    @Test
    public void findDtoByConstructor() {
        List<MemberDto> result = queryFactory
                .select(Projections.constructor(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
        for (MemberDto dto : result) {
            System.out.println("dto = " + dto);
         }
    }

     

     

    프로젝션과 결과 반환 - @QueryProjection

    생성자 + @QueryProjection

    QMemberDto 생성 확인

    @Test
    public void findDtoByQueryProjection() {
        List<MemberDto> result = queryFactory
                .select(new QMemberDto(member.username, member.age))
                .from(member)
                .fetch();
    
        for (MemberDto dto : result) {
            System.out.println("dto = " + dto);
        }
    }
    • 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법
    • DTO에 Querydsl 어노테이션을 유지해야 하며 Q파일을 생성해야 하는 단점이 있다

     

     

    cf) distinct

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

     

     

    동적 쿼리를 해결하는 두 가지 방식

    - BooleanBuilder

    - where 다중 파라미터 사용

     

     

     동적 쿼리 - BooleanBuilder 사용

    @Test
    public void dynamicQuery_BooleanBuilder() {
        String usernameParam = "memberA";
        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() {
        String usernameParam = "memberA";
        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 Predicate usernameEq(String usernameCond) {
        if (usernameCond == null) {
            return null;
        }
        return member.username.eq(usernameCond);
    }
    
    private Predicate ageEq(Integer ageCond) {
        return ageCond == null ? null : member.age.eq(ageCond);
    }
    • where 조건에 null은 무시된다
    • 메서드를 다른 쿼리에서도 재활용할 수 있다
    • 쿼리 자체 가독성이 높아진다

     

     

    private List<Member> searchMember2(String usernameCond, Integer ageCond) {
        return queryFactory
                .selectFrom(member)
                .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 BooleanExpression allEq(String usernameCond, Integer ageCond) {
        return usernameEq(usernameCond).and(ageEq(ageCond));
     }
    • 조합이 가능하다 (큰 장점)

     

     

    수정, 삭제 벌크 연산

    • 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를 실행하고 나면 영속성 컨텍스트를 초기화하는 것이 안전하다
    • 영속성 컨텍스트가 DB에서 가져온 값 보다 우선권을 갖는다 

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

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

     

     

    - 기존 숫자에 1 더하기

    long count = queryFactory
            .update(member)
            .set(member.age, member.age.add(1))
            .execute();

    - 곱하기 : multiply(x)

     

     

    SQL function 호출하기

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

    // member -> M 으로 변경하는 replace 함수
    @Test
    public void sqlFunction() {
        List<String> result = queryFactory
                .select(Expressions.stringTemplate("function('replace', {0}, {1}, {2})",
                        member.username, "member", "M"))
                .from(member)
                .fetch();
    
        for (String s : result) {
            System.out.println("s : " + s);
        }
    }
    
    // 소문자로 변경해서 비교
    @Test
    public void sqlFunction2() {
        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();
    
        for (String s : result) {
            System.out.println("s : " + s);
        }
    }

    • lower 같은 ansi 표준 함수들은 querydsl이 상당 부분 내장하고 있다
    728x90
Designed by Tistory.