Programming/Querydsl

Querydsl - 중급 문법 (1) (프로젝션)

잇(IT) 2023. 7. 28. 00:55
- 프로젝션과 결과 반환 - 기본

- 프로젝션 : select 대상 지정

 

- 프로젝션 대상이 하나

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

- 프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있다.

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


- 튜플 조회

 

- 프로젝션 대상이 둘 이상일 때 사용한다.

@Test
    public void 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);
        }
    }

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

- Querydsl 빈 생셩(Bean population)

 

- 결과를 DTO 반환할 때 사용

1. 프로퍼티 접근

2. 필드 직접 접근

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 memberDto : result) {
            System.out.println("memberDto = " + memberDto);
        }
    }

- Projections.bean을 이용하여 DTO 클래스를 작성해주고, 해당 클래스에서 원하는 필드를 가져다가 사용하는 방법이다.


2. 필드 직접 접근

@Test
    public void findDtoByField() {
        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);
        }
    }

- 필드 직접 접근 방식은 말 그대로 필드에 직접 접근하는 방식이다. Member 엔티티에 있는 값을 DTO 필드에 직접 주입하는 방식이다.


- 별칭이 다를 때

 

- MemberDTO처럼 Member 엔티티의 속성 명을 그대로 사용하는 DTO는 상관없지만 새롭게 생성되는 DTO에 필드명이 다르게 되면 추가적으로 작업을 해주어야 한다.

 

- UserDto

@Data
public class UserDto {

    private String name;
    private int userage;

    public UserDto() {
    }

    public UserDto(String name, int userage) {
        this.name = name;
        this.userage = userage;
    }
}

- 위와 같이 UserDto에 필드명이 username이 아닌 name에 값을 age가 아닌 userage에 값을 넣으려 한다.

 

- 일반적인 별칭에 바로 넣는 방식

@Test
    public void findUserDto2() {

        QMember memberSub = new QMember("memberSub");

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

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

 

 

- 서브 쿼리를 이용하는 방식

@Test
    public void findUserDto() {

        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), "userage")

                ))
                .from(member)
                .fetch();

- as를 통해 별칭을 지정하여 값을 넣어준다.


3. 생성자 사용

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

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

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

- 생성자를 사용하는 방식은 원하는 필드가 포함되어 있는 생성자만 생성해 준다면 별칭을 이용하지 않고 엔티티의 값만 이용해서 DTO에 값을 넣어 반환 할 수 있다.

- UserDto의 필드명이 member 엔티티의 필드명과 다르지만 생성자 위치에 의해 별도의 별칭을 부여하지 않아도 정상적으로 값이 들어가는 것을 확인 할 수 있다.


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

 

- MemberDto

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

- 생성자 위에 @QueryProjection을 부여해주게 되면 해당 필드에 맞게 QMemberDto를 Querydsl에서 생성한다.

 

- Test 코드

@Test
    public void findDtoByQueryProjection() {
        List<MemberDto> result = queryFactory
                .select(new QMemberDto(member.username, member.age))
                .from(member)
                .fetch();

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

- @QueryProjection 방식 또한 (new QMemberDto)를 통해 새롭게 객체를 생성하는 약간의 차이 빼고는 기존의 필드, 생성자 방식과 유사하고 쿼리, 결과물 또한 동일하다.

 

- 하지만 다른 방식과 다른 @QueryProjection의 장점이 있다.

 

- 생성자 방식

- @QueryProjection 방식

 

 

- 생성자 방식의 경우 해당 생성자 필드에 없는 값이 들어가도 컴파일러로 타입을 체크할 수 없기 때문에 런타임에서 오류가 발생한다.

- 하지만 @QueryProjection의 경우 컴파일러가 타입을 체크하기 때문에 실행전에 오류를 발견할 수 있다는 장점이 있다.

- 하지만 @QueryProjection의 경우 Querydsl 어노테이션을 유지해야 하기 때문에 종속적이고, DTO까지 Q 파일을 생성해야 하는 단점이 존재한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 : 인프런 - 김영한(실전! Querydsl)

728x90