JPAQueryFactory queryFactory = new JPAQueryFactory(em);
Querydsl을 사용하려면 JPAQueryFactory 필요
👉 JPAQueryFactory는 JPA 쿼리인 JPQL을 만들기 때문에 EntityManager 필요
JPAQueryFactory 를 스프링 빈으로 등록하여 사용할 수도 있다
필드로 제공해도 동시성 문제 X
👉 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도, 트랜잭션 마다 별도의 영속성 컨텍스트를 제공하기 때문
//빈 등록 후
@Bean
JPAQueryFactory jpaQueryFactory(EntityManager em) {
return new JPAQueryFactory(em);
}
//주입 받아 사용
public MemberJpaRepository(EntityManager em, JPAQueryFactory queryFactory) {
this.em = em;
this.queryFactory = queryFactory;
}
Q클래스 인스턴스 사용 방법 2가지
QMember qMember = new QMember("m"); //별칭 직접 지정
QMember qMember = QMember.member; //기본 인스턴스 사용
ex)
@Test
public void querydsl() {
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1")
.and(member.age.eq(10)))
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
select()
+ from()
👉 selectFrom()
으로 사용 가능
.and()
.or()
.eq()
.ne()
.goe()
.gt()
.loe()
.lt()
.not()
.in()
.notIn()
.between()
.like() // like 검색
.contains() // like '%member%' 검색
.startWith() // like 'member%' 검색
.where(member.age.eq(20), member.gender.eq("F"), ... )
fetch()
fetchOne()
fetchFirst()
fetchResults()
- 페이징 정보 포함
- total count 쿼리 추가 실행 👉 총 쿼리 2번
- 조인이 붙으며 쿼리가 복잡해지면, count 쿼리와 데이터 조회 쿼리가 다를 수 있음
(count 쿼리는 조인이 필요 없는 경우) 👉 성능 최적화 필요시, 쿼리 별도로 실행
results.getTotal(); //토탈 카운트
results.getResults(); //데이터
fetchCount()
- count 쿼리로 변경해서 count 수 조회
더 이상 fetchResults() , fetchCount() 를 지원하지 않는다고 함
fetchOne
사용
desc()
asc()
nullsLast()
nullsFirst()
null 데이터 순서 부여
List<Member> result = queryFactory
.selectFrom(member)
.where(member.city.eq("서울"))
.orderBy(member.username.asc().nullsLast)
.fetch();
.distinct()
.select(member.username).distinct()
.offset()
(index 0부터 시작)
.limit()
fetch()
조회 건수 제한
fetchResults()
전체 조회 (count 쿼리 성능 주의) , 카운트 쿼리시 필요없는 order by 제거
더 이상 fetchResults() , fetchCount() 를 지원하지 않는다고 함
fetchOne
사용
.count()
.sum()
.avg()
.max()
.min()
count(*)
대신 Wildcard.count
사용member.count()
를 사용하면 count(member.id)
로 처리List<Tuple> result = queryFactory
.select(
member.count(), //-> select count(member.id)
member.age.sum(),
member.age.avg(),
member.age.max(),
member.age.min()
)
.from(member)
.fetch();
Tuple tuple = result.get(0);
💡 조회하는 대상이 여러개이면 Tuple 사용
but, 실무에서는 이것보다 DTO로 변환하는 방법 사용
.groupBy
.having
그룹화된 결과 제한
join(조인 대상, 별칭으로 사용할 Q타입)
join()
innerJoin()
leftJoin()
rightJoin()
: 연관관계가 없는 필드 조인
.on()
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(team).on(member.username.eq(team.name))
.fetch();
.leftJoin(member.team, team) //일반 조인
from(member).leftJoin(team).on(xxx) // on 조인
.fetchJoin()
.join(member.team, team).fetchJoin()
@Test
public void subQuery() throws Exception {
QMember memberSub = new QMember("memberSub"); //다른 별칭
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(
JPAExpressions //static import 가능
.select(memberSub.age.max())
.from(memberSub)
))
.fetch();
assertThat(result).extracting("age").containsExactly(20);
}
.selectFrom(member)
.where(member.age.in(
select(memberSub.age)
.from(memberSub)
.where(memberSub.age.gt(10))
))
💡참고
화면에 쿼리를 그대로 맞추려고 하다 보니 계속 서브쿼리가 생겨나고 복잡해진다.
데이터를 필터링, 그룹핑하여 가져오는 용도로만 쓰고 그 외에는 애플리케이션, 프레젠테이션 로직에서 끝내야 한다.
한방쿼리가 정말 좋은지 고민해보자.
한 번에 가져오기 위해 복잡해지는 쿼리보다, 단순한 여러 개의 쿼리가 더 좋을 수도 있다. 서브쿼리를 줄이기 위해서는 생각의 전환이 필요 (case문 등..)
- 킹영한님
.when().then()
.otherwise()
.select(member.age
.when(10).then("A")
.when(20).then("B")
.otherwise("C"))
.from()
CaseBuilder()
.select(new CaseBuilder()
.when(member.age.between(0, 10)).then("A")
.when(member.age.between(11, 20)).then("B")
.otherwise("C"))
.from()
NumberExpression<Integer> rankPath = new CaseBuilder()
.when(member.age.between(0, 20)).then(1)
.when(member.age.between(21, 30)).then(2)
.otherwise(3);
List<Tuple> result = queryFactory
.select(member.username, member.age, rankPath)
.from(member)
.orderBy(rankPath.desc())
.fetch();
Expressions.constant()
.select(member.username, Expressions.constant("A"))
.concat()
.select(member.city.concat("_").concat(member.age.stringValue()))
데이터 수정
long count = queryFactory
.update(member)
.set(member.username, "회원")
.where(member.group.eq("A"))
.execute();
long count = queryFactory
.update(member)
.set(member.age, member.age.add(1))
.execute();
//곱셈
long count = queryFactory
.update(member)
.set(member.age, member.age.multiply(5))
.execute();
데이터 삭제
long count = queryFactory
.delete(member)
.where(member.group.eq("A"))
.execute();
🔎 JPQL 과 마찬가지로 영속성 컨텍스트를 무시하고 실행된다.
따라서 이후 영속성 컨테스트를 초기화 하는 것이 안전
ex. replace (member -> M)
String result = queryFactory
.select(Expressions.stringTemplate(
"function('replace', {0}, {1}, {2})", member.username, "member", "M"))
.from(member)
.fetchFirst();
ex. 소문자로 변경
.select(member.username)
.from(member)
.where(member.username.eq(Expressions.stringTemplate(
"function('lower', {0})", member.username)))
.where(member.username.eq(member.username.lower()))