프로젝션이란?
-> select
할 대상을 찾는 것
class Projection {
void example() {
List<String> result = queryFactory
.select(member.username)
.from(member)
.fetch();
}
}
-> String
과 같이 타입을 명확하게 지정할 수 있다.
class Projection {
void example() {
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);
}
}
}
-> 결과가 여러 개일 때 담을 수 있도록 만든 Querydsl 객체인 Tuple
을 반환한다.
-> Tuple
또는 Dto
를 반환하도록 한다.
먼저 반환할 Dto 엔티티를 생성해준다.
MemberDto
@Data
public class MemberDto {
private String username;
private int age;
public MemberDto() {
}
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
이때 기본 생성자의 접근제한자는
public
으로 선언하도록 한다.
Entity
의 기본 생성자를 만들 때는,Proxy
의 특징으로 인해Protected
까지 생성이 가능하지만
<엔티티(Entity) 기본 생성자 - Reflection API 참고>
Dto
의 경우 다른 패키지에서 이용할 수 있기 때문에 반드시public
으로 설정한다.
그렇지 않으면IllegalAccessException
런타임에러가 발생한다.
순수 JPA에서 Dto
를 반환할 때는 패키지명까지 전부 기술해야하고 생성자 방식
만 지원한다.
@Test
void findDtoByJPQL() {
List<MemberDto> result = em.createQuery(
"select new study.querydsl.dto.MemberDto(m.username, m.age) " +
"from Member m", MemberDto.class)
.getResultList();
}
}
bean
이 setter
로 필드에 정보를 주입해주는 방식이다.
@Test
void findDtoBySetter() {
List<MemberDto> result = queryFactory
// bean이 setter로 주입해준다.
.select(Projections.bean(
MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
fields
메소드를 사용한다.
querydsl이 Dto
의 필드에 변수를 바로 꽂아버린다.
@Test
void findDtoByField() {
List<MemberDto> result = queryFactory
// 필드에 바로 넣는다.
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
constructor
메소드를 사용한다.
생성자의 파라미터 변수로 Qclass
의 필드를 이용한다.
@Test
void findDtoByConstructor() {
List<MemberDto> result = queryFactory
// 생성자를 사용한다.
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
QMember
클래스의 이름 필드는 username
이다.
하지만 아래의 UserDto
의 이름 필드는 name
이다
그리고 나이는 member
들의 최대치로 고정시키고 싶다... 어떻게 해야 좋을까?
@Data
public class UserDto {
private String name;
private int age;
}
먼저 필드의 이름이 다르다면 alias
를 이용할 수 있다.
member.username.as("name")
코드처럼 alias
를 이용해 문제를 해결할 수 있다.
나이의 경우 ExpressionUtils
를 이용해 서브쿼리로 작성을 해주고 alias
를 이용해 이름을 맞춰주면 된다.
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();
생성자에 @QueryProjection
어노테이션을 붙인뒤 compile
을 해주면 별도의 QClass
가 생성된다.
@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;
}
}
컴파일러로 타입을 체크할 수 있기 때문에 런타임에러를 체크해야했던 다른 방법과 달리 가장 안전한 방법이다.
하지만, DTO에 QueryDSL 어노테이션을 유지해야 하는 점 & DTO까지 Q 파일을 생성해야 하는 단점이 있다.
-> 엔티티가 Querydsl
에 의존적이게 되는 단점도 존재한다.
@Test
void findDtoByQueryProjection() {
List<MemberDto> result = queryFactory
// DTO 클래스를 new로 바로 넣으면 된다.
// 생성자로 만들기 때문에 타입과 파라미터를 다 맞춰줘서 안정적으로 만들 수 있다.
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
}
참고 :