@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");
}
기본 검색 쿼리이다. 검색 조건은 .and() , . or() 를 메서드 체인으로 연결할 수 있다.
참고 : select , from 을 selectFrom 으로 합칠 수 있음
1.jpql이 제공하는 모든 검색 조건
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.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%’ 검색
2.and조건을 파라미터 처리할 수 있다.
@Test
public void searchAndParam() {
List<Member> result1 = queryFactory
.selectFrom(member)
.where(member.username.eq("member1"),member.age.eq(10))
.fetch();
assertThat(result1.size()).isEqualTo(1);
}
where() 에 파라미터로 검색조건을 추가하면 AND 조건이 추가됨
fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
fetchOne() : 단 건 조회
- 결과가 없으면 : null
- 결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
fetchFirst() : limit(1).fetchOne()
fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행
fetchCount() : count 쿼리로 변경해서 count 수 조회
Querydsl 5.0 버전 이상 fetchResults(), fetchCount() 메서드가 지원되지 않는다. 그렇기 때문에 명확하게 카운트 쿼리를 별도로 작성하고, fetch()를 사용해서 해결해야 한다.
회원 정렬 순서
1.회원 나이 내림차순(sec)
2.회원 이름 올림차순(asc)
단, 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
@Test
public void sort(){
em.persist(new Member(null,100));
em.persist(new Member("member5",100));
em.persist(new Member("member6",100));
List<Member> result = queryFactory
.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();
}
1.조회 건수 제한
@Test
public void paging1(){
List<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2)
.fetch();
assertThat(result.size()).isEqualTo(2);
}
2.전체 조회 수가 필요하면?
@Test
public void paging2(){
QueryResults<Member> queryResults = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2)
.fetchResults();
assertThat(queryResults.getTotal()).isEqualTo(4);
assertThat(queryResults.getLimit()).isEqualTo(2);
assertThat(queryResults.getOffset()).isEqualTo(1);
assertThat(queryResults.getResults()).isEqualTo(2);
}
위 방식은 이제 지원하지 않기 때문에 아래 방식으로 따로 토탈 쿼리를 자바 Long 타입으로 생성해야한다. 페이징 쿼리는 반환타입을 fetch()타입으로 사용하자.
Long totalCount = queryFactory
.select(member.count())
.from(member)
.fetchOne();
assertThat(totalCount.longValue()).isEqualTo(4);
@Test
public void aggregation() throws Exception {
List<Tuple> result = queryFactory
.select(member.count(),
member.age.sum(),
member.age.avg(),
member.age.max(),
member.age.min())
.from(member)
.fetch();
Tuple tuple = result.get(0); //데이터 값은 1개라서
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);
}
팀의 이름과 각 팀의 평균 연령을 구해라.
@Test
public void group(){
List<Tuple> result = queryFactory
.select(team.name, member.age.avg())
.from(member)
.join(member.team, team) //member의 team과 team 조인
.groupBy(team.name) //team의 이름으로 그룹핑
.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);
}
groupBy , 그룹화된 결과를 제한하려면 having
having 예시
.groupBy(item.price)
.having(item.price.gt(1000))
팀 A에 소속된 모든 회원
@Test
public void join(){
List<Member> result = queryFactory
.selectFrom(member)
.leftJoin(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("member1","member2");
}
세타조인
회원의 이름이 팀 이름과 같은 회원 조회
연관관계가 없는 필드 조인
@Test
public void theta_join(){
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
List<Member> result = queryFactory
.select(member)
.from(member, team)
.where(member.username.eq(team.name))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("teamA","teamB");
}
조인 on절
예) 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
JPQL : select m,t from Member m left join m.team t on t.name = 'teamA'
@Test
public void join_on_filtering(){
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(member.team, team).on(team.name.eq("teamA"))
.fetch();
//일반 join으로 하게되면 null은 다 걸러져서 출력되기때문에
//where문으로 하는것이랑 같다
//.join(member.team,team)
//.where(team.name.eq("teamA")
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
연관관계가 없는 엔티티 외부 조인
회원의 이름이 팀 이름과 같은 대상 외부 조인
@Test
public void join_on_no_relation(){
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(team).on(member.username.eq(team.name))
.fetch();
//on 조인
//원래는 .leftjoin(member.team, team) 이렇게 조인하는데 id값끼리 매칭됨
//id 매칭이 아니고 이름으로만(member.username)=(team.name) 조인대상이 필터링된다.
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
@Test
public void fetchJoinNo(){
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 미적용").isFalse();
}
@Test
public void fetchJoinUse(){
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.join(member.team , team).fetchJoin()
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 적용").isTrue();
}
패치 조인은 쿼리를 조인된 연관관계 객체까지 한방에 읽어와서 즉시로딩으로 여러번 쿼리를 보내지않는다.
Q객체 이름이 겹치면 안되기 때문에 Q객체를 따로 생성해준다.
1.나이가 평균 이상
@Test
public void subQueryGoe(){
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.goe(
JPAExpressions //satic import 생략가능
.select(memberSub.age.avg())
.from(memberSub) // -> 30,40
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(30,40);
}
2.서브쿼리 여러 건 처리, in 사용 / 10살 보다 많은 사람
@Test
public void subQueryIn() {
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.in(
.select(memberSub.age)
.from(memberSub)
.where(memberSub.age.gt(10))
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(20, 30, 40);
}
3.select 절에 subquery
@Test
public void selectSubQuery(){
QMember memberSub = new QMember("memberSub");
List<Tuple> result = queryFactory
.select(member.username,
.select(memberSub.age.avg())
.from(memberSub))
.from(member)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
jpa 서브 쿼리의 한계-> from절의 서브쿼리가 안된다. querydsl도 안된다.
해결방안
@Test
public void basicCase(){
List<String> result = queryFactory
.select(member.age
.when(10).then("열살")
.when(20).then("스무살")
.otherwise("기타"))
.from(member)
.fetch();
}
@Test
public void complexCase(){
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();
}
@Test
public void constant(){
List<Tuple> result = queryFactory
.select(member.username, Expressions.constant("A"))
.from(member)
.fetch();
}
@Test
public void concat(){
//{username}_{age}
List<String> result = queryFactory
.select(member.username.concat("_").concat(member.age.stringValue()))
.from(member)
.where(member.username.eq("member1"))
.fetch();