가장 간단한 형태의 결과반환은 튜플형을 사용하면 된다.
List<Tuple> result = queryFactory
.select(member.username, member.age)
.from(member)
.fetch();
String, int 형을 담은 두 형태가 알아서 들어가게 된다.
package study.querydsl.domain;
import lombok.Data;
@Data
public class MemberDto {
private String username;
private int age;
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
이런 형태의 DTO를 반환하려고 한다.
순수 JPQL에서는 new 를 활용해서 이를 반환한다.
@Test
public void findDtoByJpql() {
List<MemberDto> result = em.createQuery(
"select new study.querydsl.domain.MemberDto(m.username, m.age) " +
"from Member m", MemberDto.class)
.getResultList();
}
하지만 이 방식은 패키지명을 하나씩 다 넣어야해서 그렇게 편리한 방법은 아니다.
쿼리 DSL에서는 더 깔끔하게 처리할 수 있다.
@Test
public void findDtoBySetter() {
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
Projections를 이용하면, memberDto안에 집어넣을 수 있다. 다만 이 방법은 MemberDto를 만들고 그 안에 조회한 값을 넣는 방식이기때문에, 기본생성자 혹은 NoArgsConstructor가 필요하다.
@Test
public void findDtoByField() {
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
필드에 있는상태에서 바로 만든다. 따라서 생성자를 따로 사용하지 않는다.
@Test
public void findDtoByConstructor() {
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
생성자를 통해 만드는 방식이다.
DTO의 변수명과 필드명이 다를 수 있다.
package study.querydsl.domain;
import lombok.Data;
@Data
public class UserDto {
private String name;
private int age;
}
@Test
public void findDtoByDiffName() {
QMember memberSub = new QMember("memberSub");
List<UserDto> fetch = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
ExpressionUtils.as(
JPAExpressions
.select(memberSub.age.max())
.from(memberSub), "age")
)
).from(member)
.fetch();
}
이런 경우, as를 이용해서 이름을 맞춰줄 수 있다.
Dto 생성자에 QueryProjection 어노테이션을 주면 쿼리 프로젝션으로 DTO를 만들 수 있다.
@Data
@NoArgsConstructor
public class MemberDto {
private String username;
private int age;
@QueryProjection
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
@Test
public void findByQueryProjection(){
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
}
왜 이런 방식을 사용할까?
기존의 생성자방식을 사용하면, 오류가 발생하더라도 실행 후에 알 수 있다. 즉, 런타임 오류가 발생하는것이다.
하지만 QueryProjection Annotation을 사용하면, 실행전에 오류를 잡아낼 수 있다.
하지만 DTO에 QueryDSL 어노테이션을 유지해야 하는 점과 DTO까지 Q 파일을 생성해야 하는 단점이 있다. 즉, DSL 의존적 코딩이 되는 부분을 유의해야한다.
동적쿼리를 처리하는 첫번째 방법이다.
@Test
public void BooleanBuilder() throws Exception {
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember1(usernameParam, ageParam);
assertThat(result.size()).isEqualTo(1);
}
private List<Member> searchMember1(String usernameCond, Integer ageCond) {
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();
}
따로 BooleanBuilder를 사용하는것이 아닌, where 절 안에서 한번에 처리하는 방법이다.
@Test
public void WhereParam() throws Exception {
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember2(usernameParam, ageParam);
assertThat(result.size()).isEqualTo(1);
}
private List<Member> searchMember2(String usernameCond, Integer ageCond) {
return queryFactory
.selectFrom(member)
.where(usernameEq(usernameCond), ageEq(ageCond))
.fetch();
}
private BooleanExpression usernameEq(String usernameCond) {
return usernameCond != null ? member.username.eq(usernameCond) : null;
}
private BooleanExpression ageEq(Integer ageCond) {
return ageCond != null ? member.age.eq(ageCond) : null;
}
where 조건에 null 값은 무시된다.
메서드를 다른 쿼리에서도 재활용 할 수 있다.
쿼리 자체의 가독성이 높아진다.
쿼리 한번으로 대량 데이터 수정이다.
벌크연산후에는 가능하면 영속성 컨텍스트를 날려버려야 한다
멤버회원나이가 28 이하면, 전부 비회원으로 바꾸는 연산이다.
@Test
public void bulkChangeAll() {
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28))
.execute();
}
@Test
public void bulkAddOne() {
long count = queryFactory .update(member)
.set(member.age, member.age.add(1))
.execute();
}
@Test
public void bulkDelete() {
long count = queryFactory
.delete(member)
.where(member.age.gt(18))
.execute();
}
userName의 member부분을 M으로 바꿔서 호출한다.
@Test
public void sqlFunction() {
String result = queryFactory
.select(Expressions.stringTemplate("function('replace', {0}, {1}, {2})",
member.username, "member", "M"))
.from(member)
.fetchFirst();
}
혹은 소문자로도 처리가 가능하다.
@Test
public void sqlFunctionLower() {
String result = queryFactory
.select(member.username)
.from(member)
.where(member.username.eq(Expressions.stringTemplate("function('lower', {0})",
member.username)))
}