검색조건은 .and(),.or()를메서드체인으로연결할수있다.
📌참고: select , from 을 selectFrom 으로 합칠 수 있음
@Test
public void search() {
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1")
.and(member.age.eq(10)))
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
member.username.eq("member1") // username = 'member1'
member.username.ne("member1") //username != 'member1'
member.username.eq("member1").not() // username != 'member1'
member.username.isNotNull() //이름이 is not null
member.age.in(10, 20) // age in (10,20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10,30) //between 10, 30
member.age.goe(30) // age >= 30
member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30
member.username.like("member%") //like 검색
member.username.contains("member") // like ‘%member%’ 검색
member.username.startsWith("member") //like ‘member%’ 검색
and의 경우 연쇄체인이 아닌 , 로 비교하여 사용할 수 있다
Member findMember = jpaQueryFactory
.selectFrom(member)
.from(member)
.where(member.username.eq("member1")
.and(member.age.eq(10))
)
.fetchOne();
Member findMember = jpaQueryFactory
.selectFrom(member)
.from(member)
.where(
member.username.eq("member1")
,member.age.eq(10)
)
.fetchOne();
페이징 정보 포함
, total count 쿼리 추가 실행count 수만 조회
⭐️ 페이징은 성능 최적화를 고려해야하는 경우 content 조회와 count 쿼리를 따로 실행해줘야한다.
fetchResults(), fetchCount()의 경우 having절을 사용할 때
다음과 같은 에러 발생으로 querydsl 개발진 측에서 해당 함수를 deprecated 시켰다.
이유는 querydsl 내부에서 count용 쿼리르 만들어서 실행해야 하는데,
이때 작성한 select 쿼리를 기반으로 count를 만들어 낸다. 그런데 이 기능이 select 구문을 단순히 count 처리하는 것으로 바꾸는 정도여서 단순한 쿼리에서는 잘 동작하나 복잡한 쿼리에서는 동작하지 않을 수 있다.
⭐️ 명확하게 카운트 쿼리를 별로도 작성하고, fetch()를 사용해서 해결해야한다.
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: having near line 5, column 1
[select count(distinct gcm.memberId)
from com.neibus.admin.fcm.repository.Gcm gcm
inner join gcm.member as member1
where member1.dropDt is null and gcm.adId is not null and not length(gcm.adId) = 0
having count(gcm.adId) = ?1]
//리스트 조회
List<Member> fetch = jpaQueryFactory
.selectFrom(member)
.fetch();
//단건 조회
Member fetchOne = jpaQueryFactory
.selectFrom(member)
.where(member.age.eq(20))
.fetchOne();
//처음 한 건 조회
Member fetchFirst = jpaQueryFactory
.selectFrom(member)
//.limit(1).fetchOne() -> fetchFirst()와 동일한 것
.fetchFirst();
//페이징
QueryResults<Member> results = jpaQueryFactory
.selectFrom(member)
.fetchResults();
results.getTotal();
List<Member> content = results.getResults();
@DisplayName("회원 정렬")
@Test
void memberSort() {
em.persist(new Member(null, 100));
em.persist(new Member("member5", 100));
em.persist(new Member("member6", 100));
List<Member> result = jpaQueryFactory
.selectFrom(member)
.where(member.age.eq(100))
.orderBy(member.age.desc(), member.username.asc().nullsLast())
.fetch();
Member member5 = result.get(0);
Member member6 = result.get(1);
Member memberNull = result.get(2);
assertThat(member5.getUsername()).isEqualTo("member5");
assertThat(member6.getUsername()).isEqualTo("member6");
assertThat(memberNull.getUsername()).isNull();
}
/* select member1
from Member member1
where member1.age = 1001
order by member1.age desc, member1.username asc nulls last */
nullsLast() , nullsFirst() : null 데이터 순서 부여
쿼리가 복잡한 경우 count와 content 쿼리를 따로 실행한다
실무에서는 데이터를 조회하는 쿼리는 여러 테이블을 조인해야 하지만
count 쿼리는 조인이 필요 없는 경우도 있다
그런데 이렇게 자동화된 count 쿼리는 원본 쿼리와 같이 모두 조인을 해버리기 때문에 성능이 안나올 수 있다.
count 쿼리에 조인이 필요없는 성능 최적화가 필요하다면
count 전용 쿼리를 별도로 작성해야 한다
@DisplayName("페이징")
@Test
void paging1() {
List<Member> result = jpaQueryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)//0부터 시작(0을 스킵한 것)
.limit(2)
.fetch();
assertThat(result.size()).isEqualTo(2);
}
select를 특정 컬럼으로 지정해주는 경우 querydsl tuple을 조회한다
tuple은 여러개 타입이 복합적으로 있는 경우 조회
실무에서는 tuple 보다 DTO로 바로 조회하는 방법으로 사용한다
JPQL이 제공하는 모든 집합 함수를 제공한다.
@DisplayName("집합")
@Test
void aggregation() {
List<Tuple> result = jpaQueryFactory
.select(//데이터를 조회하면 tuple을 조회한다
member.count(),
member.age.sum(),
member.age.avg(),
member.age.max(),
member.age.min()
)
.from(member)
.fetch();
Tuple tuple = result.get(0);
assertThat(tuple.get(member.count())).isEqualTo(4);
assertThat(tuple.get(member.age.sum())).isEqualTo(100);
assertThat(tuple.get(member.age.avg())).isEqualTo(25);
assertThat(tuple.get(member.age.max())).isEqualTo(40);
assertThat(tuple.get(member.age.min())).isEqualTo(10);
}
groupBy 그룹화된 결과를 제한하려면 having절로 제한
@DisplayName("groupBy")
@Test
void groupBy() {
List<Tuple> result = jpaQueryFactory
.select(
team.name,
member.age.avg()
)
.from(member)
.join(member.team, team)
.groupBy(team.name)
.fetch();
Tuple teamA = result.get(0);
Tuple teamB = result.get(1);
assertThat(teamA.get(team.name)).isEqualTo("teamA");
assertThat(teamA.get(member.age.avg())).isEqualTo(15);
assertThat(teamB.get(team.name)).isEqualTo("teamB");
assertThat(teamB.get(member.age.avg())).isEqualTo(35);
}
...java
.groupBy(item.price)
.having(item.price.gt(1000))
...
조인의 기본 문법 : 첫 번째 파라미터에 조인 대상을 지정, 두 번째 파라미터에 별칭(alias)로 사용할 Q 타입을 지정하면 된다
/**
* 팀 A에 소속된 모든 회원
*/
@DisplayName("basicJoin")
@Test
void basicJoin() {
List<Member> result = jpaQueryFactory
.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("member1", "member2");
}
연관관계가 없는 필드로 조인
모든 회원을 가져오고, 모든 팀을 가져와서 조인을 한다
그런다음 where절로 필터링 하는것
(DB가 성능 최적화를 해준다)
@DisplayName("theta_join")
@Test
void theta_join() {
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
em.persist(new Member("teamC"));
List<Member> result = jpaQueryFactory
.select(member)
.from(member, team)
.where(member.username.eq(team.name))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("teamA", "teamB");
}
ON절을 활용한 조인(JPA 2.1 부터 지원)
- 1.조인 대상 필터링
- 2.연관관계 없는 엔티티 외부 조인
on 절을 활용해 조인 대상을 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링 하는 것과 기능이 동일하다.
따라서 on 절을 활용한 조인 대상 필터링을 사용할 때, 내부조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자.
List<Tuple> result = jpaQueryFactory
.select(member, team)
.from(member)
.leftJoin(member.team, team).on(team.name.eq("teamA"))
.fetch();
List<Tuple> result2 = jpaQueryFactory
.select(member, team)
.from(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
- 하이버네이트 5.1부터 on을 사용해서 서로 관계가 없는 필드로 외부 조인하는 기능이 추가 되었다.
물론 내부 조인도 가능하다.- 📌주의 ! 문법을 잘 봐야 한다.
leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어감
- 일반 조인: leftJoin(member.team, team)
- on 조인: from(member).leftJoin(team).on(XXX)
List<Tuple> result = jpaQueryFactory
.select(member, team)
.from(member)
.leftJoin(team).on(member.username.eq(team.name))
.where(member.username.eq(team.name))
.fetch();
List<Tuple> result = jpaQueryFactory
.select(member, team)
.from(member)
.leftJoin(member.username, team)
.where(member.username.eq(team.name))
.fetch();
페치 조인은 SQL에서 제공하는 기능은 아니다.
SQL 조인을 활용해서 연관된 엔티티를 SQL 한번에 조회하는 기능이다.
주로 성능 최적화에 사용하는 방법이다.
@PersistenceUnit//엔티티 메니저를 만드는 팩토리
EntityManagerFactory emf;
@DisplayName("fetchJoinNo")
@Test
void fetchJoinNo() {
em.flush();
em.clear();
Member findMember = jpaQueryFactory
.selectFrom(member)
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("패치 조인 미적용").isFalse();
}
/* select member1
from Member member1
where member1.username = 'member1'1 */
@PersistenceUnit//엔티티 메니저를 만드는 팩토리
EntityManagerFactory emf;
@DisplayName("fetchJoin")
@Test
void fetchJoin() {
em.flush();
em.clear();
Member findMember = jpaQueryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("패치 조인 미적용").isTrue();
}
/* select
member1
from
Member member1
inner join
fetch member1.team as team
where
member1.username = ?1 */
쿼리 안에 쿼리를 넣는것
com.querydsl.jpa.JPAExpressions
사용
import static com.querydsl.jpa.JPAExpressions.select;
from 절의 서브쿼리 한계
JPA JPQL 서브쿼리의 한계점으로 from 절의 서브쿼리(인라인 뷰)는 지원하지 않는다.
당연히 Querydsl 도 지원하지 않는다.
하이버네이트 구현체를 사용하면 select 절의 서브쿼리는 지원한다.
Querydsl도 하이버네이트 구현체를 사용하면 select 절의 서브쿼리를 지원한다.
🌈from절에서 서브쿼리를 사용하는 이유
DB 쿼리에서 기능을 많이 제공하니까 쿼리를 복잡하게 짜는데
SQL은 데이터를 가지고 오는 것에 집중하고
예를 들면 DATE 날짜 포맷 변경하고 하는 것은 쿼리가 아닌 view 또는 서버에서 처리한다
실시간 트래픽이 중요한 애플리케이션이면 쿼리 하나하나 날리는것이 중요함
그래서 한방 쿼리도 복잡하게 짜는데 오히려 쿼리를 나눠서 짜는것이 효율적일 수 있음
@DisplayName("나이가 가장 많은 회원 조회")
@Test
void subQuery() {
//별칭이 중복되는 경우에는 새로 생성해 줘야 한다
QMember memberSubQuery = new QMember("memberSubQuery");
List<Member> result = jpaQueryFactory
.selectFrom(member)
.where(member.age.eq(
JPAExpressions
.select(memberSubQuery.age.max())
.from(memberSubQuery)
))
.fetch();
assertThat(result)
.extracting("age")
.containsExactly(40);
}
select
member0_.member_id as member_i1_1_,
member0_.age as age2_1_,
member0_.team_id as team_id4_1_,
member0_.username as username3_1_
from
t_member member0_
where
member0_.age=(
select
max(member1_.age)
from
t_member member1_
)
@DisplayName("나이가 평균 이상인 회원 조회")
@Test
void subQueryGoe() {
//별칭이 중복되는 경우에는 새로 생성해 줘야 한다
QMember memberSubQuery = new QMember("memberSubQuery");
List<Member> result = jpaQueryFactory
.selectFrom(member)
.where(member.age.goe(
JPAExpressions
.select(memberSubQuery.age.avg())
.from(memberSubQuery)
))
.fetch();
assertThat(result)
.extracting("age")
.containsExactly(30, 40);
}
@DisplayName("나이가 10살 초과인 회원 조회")
@Test
void subQueryIn() {
//별칭이 중복되는 경우에는 새로 생성해 줘야 한다
QMember memberSubQuery = new QMember("memberSubQuery");
List<Member> result = jpaQueryFactory
.selectFrom(member)
.where(member.age.in(
JPAExpressions
.select(memberSubQuery.age)
.from(memberSubQuery)
.where(memberSubQuery.age.gt(10))//초과
))
.fetch();
assertThat(result)
.extracting("age")
.containsExactly(20, 30, 40);
}
책 추천