Querydsl
Querydsl 기본 문법
기본 코드
1
2
3
4
@PersistenceContext
EntityManager em;
// Querydsl 셋팅 - 엔티티매니저를 주입해 JPAQueryFactory 생성
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
Q-Type
- Querydsl은 @Entity 붙은 클래스들의 Q-Type이라는 클래스를 만들어줌
- 컴파일 시 생성되어 Querydsl 사용시 QMember와 같은 Q-Type을 사용해야한다.
결과 조회
fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
fetchOne() : 단 건 조회
결과가 없으면 : null
결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
fetchFirst() : limit(1).fetchOne()
페이징
1
2
3
4
5
6
List<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1) //0부터 시작(zero index)
.limit(2) //최대 2건 조회
.fetch();
위와 같이 offset, limit 메소드를 활용하면 된다.
Pageable를 사용해서도 가능하다.
1 2 3 4 5 6
List<Member> result = queryFactory .selectFrom(member) .orderBy(member.username.desc()) .offset(pageable.getOffset()) //0부터 시작(zero index) .limit(pageable.getPageSize()) //최대 2건 조회 .fetch();
기본 조인
1
join(조인 대상, 별칭으로 사용할 Q타입)
innerJoin(), leftJoin(), rightJoin() 모두 지원한다.
1 2 3 4 5
List<Member> result = queryFactory .selectFrom(member) .join(member.team, team) .where(team.name.eq("teamA")) .fetch();
세타 조인
연관관계가 없는 필드로 조인
1 2 3 4
queryFactory .select(member) .from(member, team) // 나머지 쿼리
- from 절에 여러 엔티티 선택해서 세타 조인
- 외부 조인 불가능 -> 조인 on 사용하면 외부 조인 가능
조인 - on
ON절을 활용한 조인
조인 대상 필터링
1 2 3 4 5 6
// 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회 List<Tuple> result = queryFactory .select(member, team) .from(member) .leftJoin(member.team, team).on(team.name.eq("teamA")) .fetch();
- on 절을 활용해 조인 대상을 필터링 할 때, 외부 조인이 아니라 내부 조인을 사용하면 where 절에서 필터링하는 것과 기능이 동일함
- 내부 조인은 where절을 이용하고, on 절은 외부조인이 필요한 경우에만 이 기능을 사용하자.
연관관계 없는 엔티티 외부 조인
1 2 3 4 5 6
//회원의 이름과 팀의 이름이 같은 대상 외부 조인 List<Tuple> result = queryFactory .select(member, team) .from(member) .leftJoin(team).on(member.username.eq(team.name)) .fetch();
- on을 사용해서 서로 관계가 없는 필드로 외부 조인하는 것이 가능
- 주의! leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어감
- 일반조인: leftJoin(member.team, team)
- on조인: from(member).leftJoin(team).on(xxx)
페치 조인
JPA에서 사용하는 페치 조인과 같은 기능을 제공한다.
1 2 3 4 5
Member findMember = queryFactory .selectFrom(member) .join(member.team, team).fetchJoin() .where(member.username.eq("member1")) .fetchOne();
서브 쿼리
com.querydsl.jpa.JPAExpressions 사용
1 2 3 4 5 6 7 8 9
// 나이가 가장 많은 회원 조회 List<Member> result = queryFactory .selectFrom(member) .where(member.age.eq( JPAExpressions .select(memberSub.age.max()) .from(memberSub) )) .fetch();
JPAExpressions.쿼리… 와 같이 서브 쿼리를 작성하면 된다.
Case 문
select, where, order by에서 사용 가능
단순 조건
1 2 3 4 5 6 7
List<String> result = queryFactory .select(member.age .when(10).then("열살") .when(20).then("스무살") .otherwise("기타")) .from(member) .fetch();
복잡한 조건
1 2 3 4 5 6 7
List<String> result = queryFactory .select(new CaseBuilder() .when(member.age.between(0, 20)).then("0~20살") .when(member.age.between(21, 30)).then("21~30살") .otherwise("기타")) .from(member) .fetch();
orderBy에서 Case 문 사용하기
1 2 3 4 5 6 7 8 9 10 11 12
// case 문 변수로 사용 NumberExpression<Integer> rankPath = new CaseBuilder() .when(member.age.between(0, 20)).then(2) .when(member.age.between(21, 30)).then(1) .otherwise(3); // order by 절에 적용 List<Tuple> result = queryFactory .select(member.username, member.age, rankPath) .from(member) .orderBy(rankPath.desc()) .fetch();
상수, 문자 더하기
상수가 필요하면 Expressions.constant(xxx)로 사용할 수 있음
1 2 3 4 5
Tuple result = queryFactory .select(member.username, Expressions.constant("A")) .from(member) .fetchFirst(); // 결과 : [이름, A]
문자 더하기 concat
1 2 3 4 5 6
String result = queryFactory .select(member.username.concat("_").concat(member.age.stringValue())) .from(member) .where(member.username.eq("member1")) .fetchOne(); // 결과 : member1_10
- 참고 : member.age.stringValue() 부분이 중요한데, 문자가 아닌 다른 타입들은 stringValue()로 문자로 변환 가능. 주로 ENUM 처리할 때 사용
중급 문법
결과를 DTO로 반환 (Bean population)
프로퍼티 접근(setter)
1 2 3
select(Projections.bean(MemberDto.class, member.username, member.age)
필드 직접 접근
1 2 3
Projections.fields(MemberDto.class, member.username, member.age)
생성자 접근
1 2 3
Projections.constructor(MemberDto.class, member.username, member.age)
중간에 서브쿼리 사용
1 2 3 4 5 6
Projections.constructor(MemberDto.class, member.username, ExpressionUtils.as( JPAExpressions .select(memberSub.age.max()) .from(memberSub), "age"))
ExpressionUtil.as(source, alias)
: 필드나, 서브쿼리에 별칭 적용username.as("memberName")
: 필드에 별칭 적용
@QueryProjection
: DTO 생성자에 어노테이션 붙여서 사용1 2 3 4 5 6 7 8 9 10 11 12
// DTO 생성자 @QueryProjection public MemberDto(String username, int age) { this.username = username; this.age = age; } // @QueryProjection 활용 List<MemberDto> result = queryFactory .select(new QMemberDto(member.username, member.age)) .from(member) .fetch();
- 이 방법은 컴파일러로 타입을 체크할 수 있는 가장 안전한 방법. 다만 DTO에 Querydsl 어노테이션을 유지해야하므로 DTO가 Querydsl을 의존한다는 단점이 있다.
동적쿼리
Querydsl은 where절에 조건을 추가함으로써 동적 쿼리를 쉽게 사용할 수 있다.
BooleanBuilder
1 2 3 4 5 6 7 8 9 10 11 12 13
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()에 .and로 조건 추가
where 다중 파라미터 사용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
private List<Member> searchMember2(String usernameCond, Integer ageCond) { return queryFactory .selectFrom(member) .where(usernameEq(usernameCond), ageEq(ageCond)) // null이면 통과 .fetch(); } // 조건 메소드 private BooleanExpression usernameEq(String usernameCond) { return StringUtils.hasText(usernameCond) ? member.username.eq(usernameCond) : null; //return usernameCond != null ? member.username.eq(usernameCond) : null; } private BooleanExpression ageEq(Integer ageCond) { return ageCond != null ? member.age.eq(ageCond) : null; }
- where 조건에 null은 무시
- 메소드를 이용하면 다른 곳에서 재사용 가능
- 가독성이 좋음
- String의 경우 null로 체크를 하면 ““과 같은 것을 처리하지 못한다. -> StringUtils.hasText(xxx)로 처리하는게 알맞다.
수정, 삭제 벌크 연산
벌크 쿼리
1 2 3 4 5
long count = queryFactory .update(member) .set(member.username, "비회원") .where(member.age.lt(28)) .execute();
.execute() 를 사용해 벌크 쿼리를 사용 가능
출처)
[실전! Querydsl 강의 | 대시보드 - 인프런 (inflearn.com)](https://www.inflearn.com/course/querydsl-실전/dashboard) |
This post is licensed under CC BY 4.0 by the author.