해당 포스팅에서는 QueryDSL 문법에 대해 정리해 보고자 한다.
프로젝션이란 select 대상을 지정하는 것 이다.
// 프로젝션 대상이 하나일 때
List<String> result = queryFactory
.select(member.username)
.from(member)
.fetch();
// 프로젝션 대상이 둘 이상일 때 (튜플 조회)
List<Tuple> r esult = 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);
}
순수 JPA에서 DTO 타입으로 조회하려면 생성자 방식만 지원하기 때문에 new 명령어를 사용해서 DTO의 package 경로를 일일이 다 적어줘야 한다.
List<MemberDto> result = em.createQuery(
"select new study.querydsl.dto.MemberDto(m.username, m.age) " +
"from Member m", MemberDto.class)
.getResultList();
QueryDsl 빈 생성을 통해 결과를 DTO로 반환할 수 있다. 다음 3가지 방법을 지원한다.
queryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
queryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 때 해결 방안
ExpressionUtils.as(source,alias) : 필드나, 서브 쿼리에 별칭 적용
username.as("memberName") : 필드에 별칭 적용
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();
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
생성자에 @QueryProjection 어노테이션을 적용하면 MemberDto용 Q파일이 생성된다. 이 방법은 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법이다. 다만 DTO에 QueryDsl 어노테이션을 유지해야 하는 점과 DTO까지 Q 파일을 생성해야 하는 단점이 있다
@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;
}
}
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
distinct는 JPQL의 distinct와 같다.
List<String> result = queryFactory
.select(member.username).distinct()
.from(member)
.fetch();
QuaryDsl 에서 동적 쿼리를 해결하기 위한 방법으로는 2가지가 있다.
@Test
public void 동적쿼리_BooleanBuilder() throws Exception {
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember1(usernameParam, ageParam);
Assertions.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();
}
where 다중 파라미터를 사용하면 메서드를 다른 쿼리에서도 재활용 할 수 있고, 쿼리 자체의 가독성이 높아진다. 참고로 where 절에 null이 오면 해당 조건은 무시된다.
@Test
public void 동적쿼리_WhereParam() throws Exception {
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember2(usernameParam, ageParam);
Assertions.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;
}
조합 가능
BooleanExpression을 통해 단일 조건 메소드들을 합쳐서 하나의 새로운 조건 메소드로 뽑을 수 있다.
private BooleanExpression allEq(String usernameCond, Integer ageCond) {
return usernameEq(usernameCond).and(ageEq(ageCond));
}
벌크 연산이란 대량의 데이터를 한 번에 수정하거나 삭제하는 방법이다. 참고로 QueryDsl에서의 모든 벌크 연산은 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에, 배치 쿼리를 실행하고 나면 영속성 컨텍스트를 초기화 하는 것이 안전하다.
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();
update member
set age = age + 1
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)
.fetchFirst();
List<String> result = queryFactory
.select(member.username)
.from(member)
.where(member.username.eq(
Expressions.stringTemplate("function('lower', {0})", member.username)))
.fetch();
lower 같은 ansi 표준 함수들은 querydsl이 상당부분 내장하고 있다. 따라서 다음과 같이 처리해도 결과는 같다.
.where(member.username.eq(member.username.lower()))
참조
https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84/dashboard