Search
〰️

Querydsl 기본문법

기본 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을 처리할 때도 자주 사용한다.