본문 바로가기

개인적으로 공부한 것을 정리해 놓은 블로그입니다 틀린 것이 있으면 댓글 부탁 드립니다!


JPA/QueryDSL

QueryDSL 공부 5 - 프로젝션과 결과 반환

반응형

프로젝션이란 select 절에 대상을 지정하는 것을 말한다 . 

 

프로젝션 대상이 하나일 경우 반환 타입이 명확하게 지정되지만 

 

프로젝션 대상이 둘 이상이면 튜플이나 DTO로 조회해야한다 . 

 

 

단일 프로젝션 조회

 

    @Test
    void projection_one(){

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

        for (String s : fetch) {
            System.out.println("s = " + s);
        }
    }

 

단일 타입이기 떄문에 반환타입이 명확하다 . 

 

Tuple 조회

 

    @Test
    void tuple_projection(){
        List<Tuple> fetch = qf
                .select(member.username, member.age)
                .from(member)
                .fetch();

        for (Tuple tuple : fetch) {
            String name = tuple.get(member.username);
            Integer age = tuple.get(member.age);
            System.out.println("name = " + name);
            System.out.println("age = " + age);
        }
    }

 

프로젝션 타입이 2개 이상일 경우에 Tuple 이 반환된다.  Tuple은 여러 타입을 한번에 받을 수 있게 해주고 사용할때는 

Tuple에서 찾을려는 대상을 get()하여 꺼내서 쓰면된다.  

  주의할 점은 여기서 Tuple은 QueryDsl의 Tuple 객체이기 때문에 리파지토리 안에서만 사용하는 것이 좋다 

만약 Service 계층으로 반환하고 싶다면 Tuple의 값을 꺼내 Dto에 넣어서 반환하는 것이 좋다.

 

 

DTO로 조회

 

QueryDsl 에서 Dto로 프로젝션 조회를 하기 위해선 4가지 방법이  있다. 

 

일단 순수 JPA에서 Dto 조회하는 것을 보자 

 

//순수 JPA에서 DTO 조회
    //생성자로만 가능
    //지저분함
    @Test
    public void findDtoByJPQL(){
        List<MemberDto> resultList = em.createQuery(
                "select new com.querydsl.study.dto.MemberDto(m.username,m.age)" +
                        " from Member m"
                , MemberDto.class).getResultList();

        for (MemberDto memberDto : resultList) {
            System.out.println("memberDto = " + memberDto);
        }
    }

 

JPQL에서 DTO로 프로젝션 조회를 하기 위해선 DTO의 패키지 경로를 모두 적어 줘야한다 . 코드가 지저분해진다 .

한가지 더 단점은 생성자로만 값을  넣을 수 있다는 점이다 . 

 

 

QueryDsl 에서의 Dto 조회를 알아보자 

 

1.setter를 통해 값을 주입

   //1. setter 를 통해서 값이 들어 감
    @Test
    public void findDtoByQuerydsl(){
        List<MemberDto> fetch = qf.select(Projections.bean(MemberDto.class,
                member.username,
                member.age))
                .from(member)
                .fetch();

        for (MemberDto memberDto : fetch) {
            System.out.println("memberDto = " + memberDto);
        }
    }

 

select 절에서 Projections.bean() 을 사용하면 Dto에 setter를 통해서 값이 들어간다 .  첫번째 인자로는 조회할 Dto의 타입 , 

그 다음 부터는 조회할 컬럼명을 적어주면 된다 .

 

2.필드에 직접 주입

 

    //필드에 직접 값이 들어감
    @Test
    public void findDtoByField(){
        List<MemberDto> fetch = qf.select(Projections.fields(MemberDto.class,
                member.username,
                member.age))
                .from(member)
                .fetch();

        for (MemberDto memberDto : fetch) {
            System.out.println("memberDto = " + memberDto);
        }
    }

 

dto의 필드로 값이 바로 들어간다. 사용법은 1번과 똑같고 Projections.fields()를 사용하면 된다. 

 

 

3.생성자를 통한 주입

 

    //생성자로 들어감
    //생성자에서 필드의 타입을 보고 들어가기 떄문에 
    @Test
    public void findDtoByConstructor(){
        List<UserDto> fetch = qf.select(Projections.constructor(UserDto.class,
                member.username,
                member.age))
                .from(member)
                .fetch();

        for (UserDto userDto : fetch) {
            System.out.println("userDto = " + userDto);
        }
    }

Projections.construtor()로 생성자를 통해 값이 들어 간다 . 생성자에서 타입을 보고 주입되기 때문에 조회하는 컬럼과 생성자의 타입이 일치하지 않으면 에러가 발생한다  , 런타임시에 에러가 발생할 수 있기 때문에 사용하지 않는 것이 좋다 .

 

4.@QueryProjection 를 통한 주입

 

MemberDto

 

@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
    void queryProjection(){
        List<MemberDto> result = qf.select(new QMemberDto(member.username, member.age))
                .from(member)
                .fetch();
        for (MemberDto memberDto : result) {
            System.out.println("memberDto = " + memberDto);
        }
    }

 

Dto의 생성자에 @QueryProjection을 달아주고 , gradle에서 compileQueryDsl을 하면 Dto를 Q-Type으로 만들어준다 . Dto를 기반으로 Q타입 Dto가 생성되기 때문에 Dto의 정보를 그대로 갖고 사용할 수 있다.  생성자에 값이 잘못들어 갔을 경우 컴파일 시점에서 에러를 잡아낼 수 있는 장점이 있다 Q타입임에도 반환은 원래 Dto로 반환해주는 것도 신기하다 . 

 치명적인 단점은 Dto내에서 쿼리Dsl에대한 어노테이션을 달아야 하기 때문에  여러 레이어에서 사용될 수 있는 Dto에서 QueryDsl에 대해 의존성을 갖게 된다는 단점이 있다.  

 

 아키텍쳐 구성시 Dto에 대한 정책을 유연하게 정한다면 사용할 수 있지만 각 계층간의 의존관계를 명확히 하고싶다면 setter나 field를 통한 주입을 사용하는 것이 좋다 . 

 

반응형