ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA QueryDSL 프레임워크 - 2 중급 문법
    DBMS/JPA 2024. 8. 20. 04:27
    728x90
    반응형

    JPA QueryDSL 프레임워크 - 2 중급 문법

     

     

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

    프로젝션: select 대상 지정

     

    프로젝션 대상이 하나

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

     

    튜플 조회

    프로젝션 대상이 둘 이상일 때 사용

    com.querydsl.core.Tuple

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

     

     

     

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

    순수 JPA에서 DTO 조회

     

    MemberDto

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

     

     

    2 - 1. 순수 JPA에서 DTO 조회 코드

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

    순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야함

    DTO의 package이름을 다 적어줘야해서 지저분함

    생성자 방식만 지원함

     

     

    2 - 2. Querydsl 빈 생성(Bean population)

    결과를 DTO 반환할 때 사용

    다음 3가지 방법 지원

     

    프로퍼티 접근

    필드 직접 접근

    생성자 사용

     

     

    2 - 2 - 1. 프로퍼티 접근 - Setter

    List<MemberDto> result = queryFactory
            .select(Projections.bean(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

     

    2 - 2 - 2. 필드 직접 접근

    List<MemberDto> result = queryFactory
            .select(Projections.fields(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

     

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

    @Data
    public class UserDto {
        private String name;
        private int age;
    }
    
    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();

    프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 때 해결 방안

     

    ExpressionUtils.as(source,alias)

    필드나, 서브 쿼리에 별칭 적용

     

    username.as("memberName")

    필드에 별칭 적용

     

     

    2 - 2 - 4. 생성자 사용

    List<MemberDto> result = queryFactory
            .select(Projections.constructor(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();
    }

     

     

     

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

     

    생성자 + @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;
        }
    }

    ./gradlew compileQuerydsl

    QMemberDto 생성 확인

     

    @QueryProjection 활용

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

     

    이 방법은 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법이다.

    다만 DTO에 QueryDSL 어노테이션을 유지 해야 하는 점과 DTO까지 Q 파일을 생성해야 하는 단점이 있다.

    >> QueryDSL에 종속적으로 만들어진다.

    상황에 따라 판단하자.

     

    distinct

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

     

     

     

    4. 동적 쿼리

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

    BooleanBuilder

    Where 다중 파라미터 사용

     

     

    4 - 1. 동적 쿼리 - BooleanBuilder 사용

    @Test
    public void 동적쿼리_BooleanBuilder() throws Exception {
        String usernameParam = "member1";
        Integer ageParam = 10;
        List<Member> result = searchMember1(usernameParam, ageParam);
        Assertions.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();
    }

     

     

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

    @Test
    public void 동적쿼리_WhereParam() throws Exception {
        String usernameParam = "member1";
        Integer ageParam = 10;
        List<Member> result = searchMember2(usernameParam, ageParam);
        Assertions.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 값은 무시된다.

    메서드를 다른 쿼리에서도 재활용 할 수 있다.

    쿼리 자체의 가독성이 높아진다.

     

    조합 가능

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

    null 체크는 주의해서 처리해야함

     

     

     

    5. 수정, 삭제 벌크 연산

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

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

    곱하기: multiply(x)

     

    쿼리 한번으로 대량 데이터 삭제

    long count = queryFactory
            .delete(member)
            .where(member.age.gt(18))
            .execute();

     

    주의

    JPQL 배치와 마찬가지로,

    영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에

    배치 쿼리를 실행하고 나면 영속성 컨텍스트를 초기화 하는 것이 안전하다.

     

     

     

    6. SQL function 호출하기

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

     

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

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

     

    소문자로 변경해서 비교

    .select(member.username)
    .from(member)
    .where(member.username.eq(Expressions.stringTemplate("function('lower', {0})", member.username)))

     

    lower 같은 ansi 표준 함수들은 querydsl이 상당부분 내장하고 있다.

    따라서 다음과 같이 처리해도 결과는 같다.

    .where(member.username.eq(member.username.lower()))
    
    728x90
    반응형
Designed by Tistory.