List<String> result = queryFactory
.select(member.username)
.from(member)
.fetch();
프로젝션 대상이 둘 이상일 때는 Tuple 이나 DTO로 조회한다.
List<Tuple> result = queryFactory
.select(member.username, member.age)
.from(member)
.fetch();
Tuple은 com.querydsl.core에 위치한다.
튜플은 가능하면 Repository 단 내에서만 사용하고, 외부로 보낼 때는 DTO 로 변환하여 보내자.
순수 JPA에서 DTO를 조회할 때는 new 를 이용해야 한다. 패키지 이름을 다 적어줘야 해서 매우 지저분하다.
List<MemberDto> result = em.createQuery(
"select new study.querydsl.dto.MemberDto(m.username, m.age)" +
" from Member m",
MemberDto.class)
.getResultList();
아래의 3가지 방법이 있다.
1. 프로퍼티 접근 - Setter
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
QMember subMember = new QMember("subMember");
List<UserDto> result = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
ExpressionUtils.as(
JPAExpressions.select(subMember.age.max())
.from(subMember), "age"
)
))
.from(member)
.fetch();
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
QueryProjection 을 하기 위해서는 DTO 의 생성자에 @QueryProjection 어노테이션을 달아주어야 한다.
@QueryProjection
public MemberDto(String username, int age){
this.username = username;
this.age = age;
}
그리고 엔티티에 대해 Q 타입 파일을 생성할 때처럼, compileQuerydsl 을 클릭하면 QMemberDto 파일이 생성되는 것을 확인할 수 있다.
그럼, 아래와 같이 사용할 수 있다.
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
앞서 살펴본 생성자 방식과는 달리, 생성자의 인자를 잘못 주었을 때 런타임이 아닌 컴파일 타임에 에러를 확인할 수 있다.
DTO 가 Querydsl 에 의존적이게 된다.
DTO 의 생성자에 @QueryProjection 을 해주고, DTO의 Q 타입 파일을 생성해주어야 한다.
동적 쿼리를 해결하는 방식은 2 가지가 있다.
1. BooleaBuilder 을 사용하는 방식
2. Where 다중 파라미터를 사용하는 방식
BooleanBuilder builder = new BooleanBuilder();
if (usernameCond != null){
builder.and(member.username.eq(usernameCond));
}
if(ageCond != null){
builder.and(member.age.eq(ageCond));
}
return queryFactory
.selectFrom(member)
.where(builder)
.fetch();
private List<Member> searchMember2(String usernameCond, Integer ageCond) {
return queryFactory
.selectFrom(member)
// .where(usernameEq(usernameCond), ageEq(ageCond))
.where(allEq(usernameCond, ageCond))
.fetch();
}
private BooleanExpression usernameEq(String usernameCond) {
return usernameCond == null ? null : member.username.eq(usernameCond);
}
private BooleanExpression ageEq(Integer ageCond) {
return ageCond == null ? null : member.age.eq(ageCond);
}
private Predicate allEq(String usernameCond, Integer ageCond){
return usernameEq(usernameCond).and(ageEq(ageCond));
}
벌크 연산은 영속성 컨텍스트는 무시하고 DB 에 바로 쿼리가 나간다.
따라서 DB 와 영속성 컨텍스트의 상태가 달라져버린다.
벌크 연산을 실행하고 나면 영속성 컨텍스트를 초기화해주자!
em.flush();
em.clear();
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28))
.execute();
long count = queryFactory
.update(member)
.set(member.age, member.age.add(1))
.execute();
long count = queryFactory
.delete(member)
.where(member.age.gt(18))
.execute();
SQL function은 JPA와 같이 Dialect에 등록된 내용만 호출할 수 있다.
List<String> result = queryFactory
.select(Expressions.stringTemplate(
"function('replace', {0}, {1}, {2})",
member.username, "member", "M"))
.from(member)
.fetch();
List<String> result = queryFactory
.select(member.username)
.from(member)
.where(member.username.eq(
Expressions.stringTemplate("function('lower', {0})", member.username)))
.fetch();
lower 같은 간단한 함수들은 이미 표준에서 제공하고 있다. 따라서 아래의 코드를 써도 똑같이 동작한다.
.where(member.username.eq(member.username.lower()))