JPA

Querydsl 정리

쭈녁 2024. 3. 29. 22:43

 

기존에 활용해 보았던 Querydsl에 대하여 추가적으로 알게 된 기능들을 정리해 본다.

 

Dto를 Q타입으로 사용하는 방법(@QuertProjection)

@Data
public class MemberTeamDto {
    private Long memberId;
    private String username;
    private int age;
    private Long teamId;
    private String teamName;

    @QueryProjection
    public MemberTeamDto(Long memberId, String username, int age, Long teamId, String teamName) {
        this.memberId = memberId;
        this.username = username;
        this.age = age;
        this.teamId = teamId;
        this.teamName = teamName;
    }
}

 

위 방법은 데이터를 조회하여 바로 Dto 객체로 만들어 반환할 수 있게 해 준다. 기존 JPQL의 문법에서도 문자열의 생성자 호출을 통해 Dto로 변환이 가능하였지만 Q타입은 컴파일 시 해당 코드에 문제가 있는지 확인이 가능하여 사용하기 편하다.

만약 Dto와 Entity의 필드의 명칭이 다르다면 .as 를 통하여 어떤 필드에 값을 넣어 생성할 것인지 명시하여야 한다.

 

** 사용 시 주의점 **

위 방법으로 Dto 계층으로의 변환은 쉬워지지만 아키텍처상 Dto가 Querydsl이라는 라이브러리를 의존해야 한다는 단점이 존재한다. 만약 Querydsl을 사용하지 않도록 변경하거나 한다면 Dto계층에도 수정이 일어날 수 있다는 점이 단점이다.

 

만약 Dto와의 의존성이 꺼려진다면 아래와 같은 방법도 고려해 볼 수 있다.


//세터로 값 할당
@Test
public void findDtoBySetter() throws Exception {
    //given
    List<MemberDto> result = queryFactory
            .select(Projections.bean(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}
//필드에 직접 값 할당
@Test
public void findDtoByField() throws Exception {
    //given
    List<MemberDto> result = queryFactory
            .select(Projections.fields(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}
//필드와 세터는 엔티티와 Dto 의 필드가 매칭되어야 값이 할당됨, as로 Dto의 필드를 지정할 수 있음

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

    List<UserDto> UserResult = queryFactory
            .select(Projections.constructor(UserDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
    for (UserDto userDto : UserResult) {
        System.out.println("userDto = " + userDto);
    }
}

@Test
public void findUserDto() throws Exception {
    //given
    QMember sub = new QMember("sub");

    List<UserDto> userResult = queryFactory
            .select(Projections.fields(UserDto.class,
                    member.username.as("name"),
                    member.age))
            .from(member)
            .fetch();

    List<UserDto> userResult2 = queryFactory
            .select(Projections.fields(UserDto.class,
                    member.username.as("name"),

                    ExpressionUtils.as(JPAExpressions
                            .select(sub.age.max())
                            .from(sub), "age")))
            .from(member)
            .fetch();


    //when
    for (UserDto userDto : userResult) {
        System.out.println("userDto = " + userDto);
    }
    for (UserDto userDto : userResult2) {
        System.out.println("userDto2 = " + userDto);
    }
    //then
}

 

연관관계없는 테이블 간의 Join

/**
 * 연관관계 없는 엔티티 외부 조인
 * 회원 이름과 팀이름이 같은 대상 조인
 */
@Test
public void join_notRelation() throws Exception {

    //given
    em.persist(new Member("teamA"));
    em.persist(new Member("teamB"));
    em.persist(new Member("teamC"));


    //when
    List<Tuple> result = queryFactory
            .select(member, team)
            .from(member)
            .leftJoin(team) //조인하는 테이블의 연관 엔티티가 들어가는것이 아닌 관계 없는 엔티티 Q타입이 들어가고 on 절에서 조건 정의
            .on(member.username.eq(team.name)) // 연관관계가 없음으로 어떤 필드값끼리 매칭할지 정해준다.
            .fetch();


    //then

    for (Tuple tuple : result) {
        System.out.println("tuple = " + tuple);
    }
}

 

페이지네이션 시 사용법

  @Override
    public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition cond, Pageable pageable) {
        List<MemberTeamDto> result = queryFactory.
                select(new QMemberTeamDto(
                        member.id.as("memberId"),
                        member.username,
                        member.age,
                        team.id.as("teamId"),
                        team.name.as("teamName")))
                .from(member)
                .leftJoin(member.team, team)
                .where(usernameEq(cond.getUsername()), teamNameEq(cond.getTeamName()), ageGoe(cond.getAgeGoe()), ageLoe(cond.getAgeLoe()))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();

        JPAQuery<Long> count = queryFactory
                .select(member.count())
                .from(member)
                .leftJoin(member.team, team)
                .where(usernameEq(cond.getUsername()), teamNameEq(cond.getTeamName()), ageGoe(cond.getAgeGoe()), ageLoe(cond.getAgeLoe()));
        return PageableExecutionUtils.getPage(result, pageable, count::fetchFirst);
    }

 

강의에서는 fetchCount를 사용해 총데이터에 대한 카운트를 얻어왔는데 현재 기준으로 fetchResults와 fetchCount Deprecated 상태여서 위와 같이 작성하였다. 페이지 조회에 대한 쿼리와 카운트 쿼리가 두 번 나가지만 실제로 카운트 쿼리는 다르게 내보내야 할 경우가 많다고 하여 위와 같이 쓰는 게 가장 좋은 방법일 것 같다.

 

참고자료

김영한 : 실전! Querydsl

https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84/dashboard

 

실전! Querydsl | 김영한 - 인프런

김영한 | Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, 복잡한 쿼리, 동적 쿼리는 이제 안녕! Querydsl로 자바 백엔드 기술을 단단하게. 🚩 본 강의는 로드맵 과정입니다. 본 강의는 자

www.inflearn.com

 

 

'JPA' 카테고리의 다른 글

Querydsl 서브쿼리  (0) 2024.03.31
JPA , QueryDsl 수정 중 에러 해결  (0) 2024.03.17
영속성 컨텍스트  (0) 2024.01.28
JPQL문법 2 (페치 조인,벌크 연산)  (0) 2022.12.23
JPQL 문법 1  (1) 2022.12.22