중급 문법

LeeKyoungChang·2022년 5월 6일
0
post-thumbnail

Querydsl 수업을 듣고 정리한 내용입니다.

 

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

✏️ 프로젝션이란?
select 대상을 지정하는 것이다.

 

📖 A. 프로젝션 대상이 하나

@Test  
public void simpleProjection() {  
    List<String> result = queryFactory  
            .select(member.username)  
            .from(member)  
            .fetch();  
  
    for (String s : result) {  
        System.out.println("s = " + s);  
    }  
}
  • 프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있다.
  • 프로젝션 대상이 둘 이상이면 튜플이나 DTO로 조회한다.

 

실행 결과

스크린샷 2022-05-06 오후 12 21 10

 

📖 B. 튜플 조회

프로젝션 대상이 둘 이상일 때 사용한다.
com.querydsl.core.Tuple

@Test  
public void tupleProjection() {  
    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);  
  
    }  
}
  • Tuple은 리포지토리 계층 안에서 쓰는 정도는 괜찮지만, 그 밖의 계층에서는 DTO로 변환하는 것이 좋다.

 

실행 결과
스크린샷 2022-05-06 오후 12 23 24

 

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

📖 A. 순수 JPA에서 DTO 조회

MemberDto

package study.querydsl.dto;  
  
import lombok.Data;  
import lombok.NoArgsConstructor;  
  
@Data  
@NoArgsConstructor  
public class MemberDto {  
  
    private String username;  
    private int age;  
  
    public MemberDto(String username, int age) {  
        this.username = username;  
        this.age = age;  
    }  
      
}

 

순수 JPA에서 DTO 조회 코드

@Test  
public void findDtoByJPQL() {  
    List<MemberDto> result = em.createQuery("select new study.querydsl.dto.MemberDto(m.username, m.age) from Member m", MemberDto.class)  
            .getResultList();  
  
    for (MemberDto memberDto : result) {  
        System.out.println("memberDto = " + memberDto);  
    }  
}  
  
  • 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야 한다.
  • DTO의 package이름을 다 적어줘야 해서 지저분하다.
  • 생성자 방식만 지원한다.

실행 결과

스크린샷 2022-05-06 오후 12 38 31

 

📖 B. 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 memberDto : result) {  
        System.out.println("memberDto = " + memberDto);  
    }  
}  
  
  • Projections.bean(주입대상클래스, 프로퍼티1, 프로퍼티2) 방식으로 값을 주입한다.

 

실행 결과

스크린샷 2022-05-06 오후 12 37 41

 

(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);  
    }  
}
  • GetterSetter가 없어도 필드에 바로 값을 주입한다.
  • Projections.fields(주입대상클래스, 필드1, 필드2)

 

실행 결과
스크린샷 2022-05-06 오후 12 39 22

 

✔️ 별칭이 다를 때

UserDto

package study.querydsl.dto;  
  
import lombok.Data;  
  
@Data  
public class UserDto {  
  
    private String name;  
    private int age;  
  
  
    public UserDto() {  
    }  
  
    public UserDto(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
}

 

@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), "age")))  
            .from(member)  
            .fetch();  
    for (UserDto userDto : result) {  
        System.out.println("userDto = " + userDto);  
    }  
}
  • 프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 때 해결방안이다.
  • ExpressionUtils.as(source, alias): 필드나, 서브 쿼리에 별칭을 적용한다.
  • username.as("memberName"): 필드에 별칭 적용한다.

 

실행 결과

스크린샷 2022-05-06 오후 12 50 18

 

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

 

실행 결과

스크린샷 2022-05-06 오후 12 40 31

 

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

✔️ 생성자 + @QueryProjection

package study.querydsl.dto;  
  
import com.querydsl.core.annotations.QueryProjection;  
import lombok.Data;  
import lombok.NoArgsConstructor;  
  
@Data  
@NoArgsConstructor  
public class MemberDto {  
  
    private String username;  
    private int age;  
  
    @QueryProjection  
    public MemberDto(String username, int age) {  
        this.username = username;  
        this.age = age;  
    }  
  
}
  • DTO 프로젝션에 사용될 생성자에 @QueryProjection을 붙여준다.
  • 이를 통해 DTO로 Q타입을 생성해준다.

@QueryProject 추가후, compileQuerydsl 클릭

스크린샷 2022-05-06 오후 1 17 33 스크린샷 2022-05-06 오후 1 30 55

QMemberDto 생성되었다.

 

✔️ @QueryProjection 활용

@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);  
    }  
}
  • 이 방법은 컴파일러로 타입을 체크할 수 있으므로 가정 안전한 방법이다.
  • DTO에 QueryDSL 어노테이션을 유지해야 하고 DTO까지 Q파일을 생성해야 하는 단점이 있다.

 

실행 결과

스크린샷 2022-05-06 오후 1 21 55

 

✔️ distinct 사용

List<String> result = queryFactory
        .select(member.username).distinct()
        .from(member)
        .fetch();
  • JPQL의 distinct와 같다.

 

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글