기본 Q-Type 활용
•
Q클래스 인스턴스를 사용하는 2가지 방법
QMember m = new QMember("m"); //별칭 직접 지정
QMember m = QMember.member; //기본 인스턴스 사용
Java
복사
기본 인스턴스를 static import와 함께 사용
import static study.querydsl.entity.QMember.*;
@Test
public void startQuerydsl3() {
//member1을 찾아라.
Member findMember = queryFactory
.select(member)
.from(member)
.where(member.username.eq("member1"))
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
Java
복사
JPAQueryFactory를 필드로 제공하면 동시성 문제는 어떻게 될까?
•
동시성 문제는 JPAQueryFactory를 생성할 때 제공하는 EntityManager(em)에 달려있다. 스프링 프레임워크는 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도, 트랜잭션 마다 별도의 영속성 컨텍스트를 제공하기 때문에 동시성 문제는 걱정하지 않아도 된다.
기본 검색 쿼리
@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");
}
Java
복사
•
검색조건은 .and(), .or()를 메소드 체인으로 연결할수있다.
•
select ~ from 을 selectFrom으로 사용가능
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%’ 검색
...
Java
복사
And 조건을 파라미터로 처리
@Test
public void searchAndParam() {
Member findMember = queryFactory
.selectFrom(member)
.where(
member.username.eq("member1"),
member.age.eq(10)
)
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
Java
복사
결과 조회
•
fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
•
fetchOne() : 단 건 조회
◦
결과가 없으면 : null
◦
결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
•
fetchFirst() : limit(1).fetchOne()
•
fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행
◦
Deprecated
•
fetchCount() : count 쿼리로 변경해서 count 수 조회
◦
Deprecated
@Test
public void resultFetch() {
List<Member> fetch = queryFactory
.selectFrom(member)
.fetch();
Member fetchOne = queryFactory
.selectFrom(member)
.fetchOne();
Member fetchFirst = queryFactory
.selectFrom(member)
.fetchFirst();
QueryResults<Member> results = queryFactory
.selectFrom(member)
.fetchResults();
results.getTotal();
List<Member> content = results.getResults();
queryFactory
.selectFrom(member)
.fetchCount();
}
Java
복사
정렬
/**
* 회원 정렬 순서
* 1. 회원 나이 내림차순(desc)
* 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())
//.orderBy(member.age.desc(), member.username.asc().nullFirst())
.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();
}
Java
복사
•
asc() : 오름차순 정렬, desc() : 내림차순 정렬
•
nullLast() : null이면 가장 마지막, nullFirst() : null이면 가장 처음
집합
@Test
public void aggregation() {
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);
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);
}
Java
복사
groupBy(), having() 예시
.groupBy(item.price)
.having(item.price.gt(1000))
Java
복사
조인
기본 조인
•
조인의 기본 문법은 첫번째 파라미터에 조인 대상을 지정하고, 두번째 파라미터에 별칭(alias)으로 사용할 Q타입을 지정하면 된다.
join(조인 대상, 별칭으로 사용할 Q타입)
Java
복사
/**
* TeamA에 소속된 모든 회원
* @throws Exception
*/
@Test
public void join() throws Exception {
List<Member> result = queryFactory
.selectFrom(member)
.join(member.team, team)
//.leftJoin(member.team, team)
//.innerJoin(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("member1", "member2");
}
Java
복사
세타조인
•
연관관계가 없는 필드로 조인
/**
* 세타 조인
* 회원의 이름이 팀 이름과 같은 회원 조회
*/
@Test
public void theta_join() throws Exception {
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");
}
Java
복사
•
from절에 여러 엔티티를 선택해서 세타조인
•
외부 조인 불가능 → 다음에 설명할 조인 on을 사용하면 외부 조인 가능
조인 on절
•
조인 대상 필터링
•
연관관계 없는 엔티티 외부 조인
조인 대상 필터링
/**
* 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
* JPQL : select m, t from Member m left join m.team t on t.name = 'teamA'
*/
@Test
public void join_on_filtering() throws Exception {
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(member.team, team).on(team.name.eq("teamA"))
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
Java
복사
on 절을 활용해 조인 대상을 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링 하는 것과 기능이 동일하다.
따라서 on 절을 활용한 조인 대상 필터링을 사용할 때, 내부조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자.
연관관계 없는 엔티티 외부 조인
/**
* 연관관계 없는 엔티티 외부 조인
* 회원의 이름이 팀 이름과 같은 대상 -> 외부조인
*/
@Test
public void join_on_no_relation() throws Exception {
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();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
Java
복사
•
하이버네이트 5.1부터 on을 사용해서 서로 관계가 없는 필드로 오부 조인하는 기능이 추가되었다. 물론 내부 조인도 가능하다!
•
주의! 문법을 잘 봐야한다. leftJoin()부분에 일반 조인과 다르게 엔티티 하나만 들어간다!
◦
일반 조인 : leftJoin(member.team, team)
◦
on 조인 : from(member).leftJoin(team).on(xxx)
페치조인
•
페치조인은 SQL에서 제공하는 기능은 아니다. SQL조인을 활용해서 연관된 엔티티를 SQL 한 번에 조회하는 기능이다. 주로 성능 최적화에 사용하는 방법이다.
페치 조인 미적용
@PersistenceUnit
EntityManagerFactory emf;
@Test
public void fetchJoinNo() throws Exception {
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("페치 조인 미적용").isEqualTo(false);
}
Java
복사
페치 조인 적용
@Test
public void fetchJoinUse() throws Exception {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.username.eq("member1"))
.fetchOne();
// team이 loaded되었는지 확인!
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 적용").isEqualTo(true);
}
Java
복사
서브쿼리
•
com.querydsl.jpa.JPAExpressions 사용
/**
* 나이가 가장 많은 회원 조회
* select member1 from Member member1 where ( select max(memberSub.age) from Member memberSub )
*/
@Test
public void subQuery() throws Exception {
QMember memberSub = new QMember("memberSub"); // alias가 중복되면 안되기 때문에..
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(
JPAExpressions
.select(memberSub.age.max())
.from(memberSub)
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(40);
}
/**
* 나이가 평균 이상인 회원
*/
@Test
public void subQueryGoe() throws Exception {
QMember memberSub = new QMember("memberSub"); // alias가 중복되면 안되기 때문에..
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.goe(
JPAExpressions
.select(memberSub.age.avg())
.from(memberSub)
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(30, 40);
}
/**
* 나이가 평균 이상인 회원
*/
@Test
public void subQueryIn() throws Exception {
QMember memberSub = new QMember("memberSub"); // alias가 중복되면 안되기 때문에..
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.in(
JPAExpressions
.select(memberSub.age)
.from(memberSub)
.where(memberSub.age.gt(10))
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(20, 30, 40);
}
@Test
public void selectSubquery() throws Exception {
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);
}
}
Java
복사
•
from절의 서브쿼리 한계
◦
from절의 서브쿼리(인라인 뷰)는 지원하지 않는다! → Querydsl도 지원하지 않는다.
◦
하이버네이트 구현체를 사용하면 select절의 서브쿼리는 지원한다.
•
from절의 서브쿼리 해결방안
◦
서브쿼리를 join으로 변경한다. (가능한 상황도 있고, 불가능한 상황도 있다.)
◦
애플리케이션에서 쿼리를 2번 분리해서 실행한다.
◦
nativeSQL을 사용한다.
Case문
•
select, 조건절(where)에서 사용 가능
단순한 조건
@Test
public void basicCase() throws Exception {
List<String> result = queryFactory
.select(member.age
.when(10).then("열살")
.when(20).then("스무살")
.otherwise("기타"))
.from(member)
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
Java
복사
복잡한 조건
@Test
public void complexCase() throws Exception {
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();
for (String s : result) {
System.out.println("s = " + s);
}
}
Java
복사
→ 웬만해서는,,, DB단에서 처리하지 않는 것이 좋다. 애플리케이션/Presentation Layer에서 해결하도록.. DB에서 case문을 짜서 가져오는 것은 Best 방법이 아님.
상수, 문자 더하기
상수
@Test
public void constant() throws Exception {
List<Tuple> result = queryFactory
.select(member.username, Expressions.constant("A"))
.from(member)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
Java
복사
문자더하기
@Test
public void concat() throws Exception {
// username_age
List<String> result = queryFactory
.select(member.username.concat("_").concat(member.age.stringValue())) // type이 다를 경우 안됨!
.from(member)
.where(member.username.eq("member1"))
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
Java
복사
member.age.stringValue(): 문자가 아닌 다른 타입들은 stringValue()로 문자로 변환할 수 있다. 이 방법은 ENUM을 처리할 때도 자주 사용한다.