순수 JPA에서의 DTO 조회
JPA에서 프로젝션으로 여러 타입의 필드를 조회하기 위한 방법 중 하나이다.
- new 명령어 사용해야 한다.
- DTO의 package 이름을 다 적어야 한다.
- 생성자 방식만 지원한다.
List<MemberDTO> result = em.createQuery(
"select new jpql.MemberDTO(m.username, m.age) from Member m",
MemberDTO.class
).getResultList();
위와 같이 패키지명을 작성해야 하는 불편함은 QueryDSL을 사용하여 극복할 수 있다.
QueryDSL에서의 DTO 조회
기본적으로 QueryDSL에서는 프로젝션 대상으로 여러 필드를 선택하게 되면 Tuple이라는 타입을 리턴해준다.
List<Tuple> fetch = query.select(member.username, member.age)
.from(member)
.fetch();
for(Tuple tuple : fetch) {
System.out.println("Name = " + tuple.get(member.username) + " Age = " + tuple.get(member.age));
}
각각의 필드 값을 직접 하나하나 꺼내줘야 하는 번거로움이 있다.
QueryDSL은 아래 4가지 방법을 통해 DTO로 조회할 수 있도록 지원한다.
- 프로퍼티 접근 - setter
- 필드 직접 접근
- 생성자 사용
- @QueryProjection
1. 프로퍼티 접근 - setter
- Projections.bean(class, {field, ...}) 을 사용한다.
- 첫 번째 파라미터: 결과에 대한 클래스 (ex: MemberDTO.class)
- 두 번째 파라미터: 매핑될 필드 (ex: member.username, member,age)
- setter를 통해 값을 넣어주기 때문에 setter가 필요하다.
- dto와 entity에서 필드의 명칭이 일치해야 한다.
List<MemberDTO> result = queryFactory
.select(Projections.bean(MemberDTO.class,
member.username,
member.age))
.from(member)
.fetch();
DTO와 Entity의 필드명이 다른 경우
예를 들어 Member Entity의 필드가 username이고, MemberDTO의 필드가 name인 경우 해당 필드에 null 값이 들어간다. 따라서 alias(별칭)를 사용하여 매핑 문제를 해결한다.
- username.as(alias) : 필드에 별칭을 적용할 때 사용
- ExpressionUtils.as(source, alias) : 필드나 서브 쿼리에 별칭을 적용할 때 사용
아래는 username.as(alias)를 사용한 예시 코드이다.
List<MemberDTO> result = queryFactory
.select(Projections.bean(MemberDTO.class,
member.username.as("name"),
member.age))
.from(member)
.fetch();
2. 필드 직접 접근
- Projections.fields(class, {field, ...})을 사용한다.
- 필드에 직접 접근하여 값을 넣어주기 때문에 setter가 필요 없다.
- dto와 entity에서 필드의 명칭이 일치해야 한다.
List<MemberDTO> result = queryFactory
.select(Projections.fields(MemberDTO.class,
member.username,
member.age))
.from(member)
.fetch();
3. 생성자 사용
- Projections.constructor(class, {field, ...})를 사용한다.
- DTO의 생성자를 통해 DTO 객체를 생성한다.
- 생성자의 파라미터와 순서가 일치해야 한다.
List<Member> results = queryFactory
.select(Projections.constructor(Member.class,
member.username,
member.age))
.from(member)
.fetch();
4. @QueryProjection
1. 생성자에 @QueryProjection 어노테이션 추가
public class MemberDTO {
private String username;
private int age;
public MemberDTO() {
}
@QueryProjection
public MemberDTO(String username, int age) {
this.username = username;
this.age = age;
}
}
MemberDTO 클래스에서 매개변수가 있는 생성자에 @QueryProjection을 추가한다.
@QueryProjection은 QMemberDTO를 만들어 주는 어노테이션이다.
2. 컴파일 이후 생성된 QMemberDTO 파일 확인
@Generated("com.querydsl.codegen.ProjectionSerializer")
public class QMemberDTO extends ConstructorExpression<MemberDto> {
private static final long serialVersionUID = -857882546L;
public QMemberDTO(com.querydsl.core.types.Expression<String> username, com.querydsl.core.types.Expression<Integer> age) {
super(MemberDTO.class, new Class<?>[]{String.class, int.class}, username, age);
}
}
3. QMemberDTO 생성자 사용
List<MemberDto> results = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
- 컴파일러로 타입을 체크할 수 있으므로 컴파일 단계에서 오류를 찾을 수 있다.
- DTO까지 Q 파일을 생성해야 하며, Querydsl에 대한 의존성을 갖게 된다.
'Framework > JPA' 카테고리의 다른 글
[JPA] 페이징 처리 성능 최적화하기 (1) | 2024.02.11 |
---|---|
[JPA] 다대일 매핑에서 @JoinColumn의 역할 (0) | 2023.10.05 |
[JPA] @MappedSupperclass vs @Embedded & @Embeddable (0) | 2023.10.01 |
[JPA] 순한 참조 문제 해결하기 (0) | 2023.10.01 |