※ 본문은 김영한 선생님의 인프런 '실전! Querydsl' 강의를 듣고 정리한 내용임을 알립니다.
★ Querydsl 설정 관련
- Querydsl 관련 Q파일들은 깃허브에 올리지 않도록 하자.
- build.gradle의 Querydsl 설정은 협업이나 여러가지 변동사항이 있을 때 설정이 꼬일 수 있으니 주의하자.
▶ 기본 문법
1. 기본 Q-Type 활용
- Q 클래스 인스턴스를 사용하는 2가지 방법
→ 별칭 직접 지정 : QMember qMember = new QMember("m");
→ 기본 인스턴스 사용 : QMember qMember = QMember.member;
=> 같은 테이블을 join하는 경우가 아니라면 기본 인스턴스를 활용하자.
2. 검색 조건 쿼리
- where 검색 조건은 .and() / .or()를 메소드 체인으로 연결 가능
- select, from을 selectFrom으로 합칠 수 있음
- .eq("m") / .ne("m") / .isNotNull()
- .in(10, 20) / .notIn(10, 20) / .between(10, 30)
- .goe(20) / .gt(20) / .loe(20) / .lt(20)
- .like("m%") / .contains("m") / .startsWith("m")
3. 결과 조회
- fetch() : 리스트 조회, 데이터가 없으면 빈 리스트 반환
- fetchOne() : 단건 조회, 결과가 없으면 null, 결과가 둘 이상이면 NonUniqueResultException 발생
- fetchFirst() : limit(1).fetchOne()
- fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행
- fetchCount() : count 쿼리로 변경해서 count 조회
4. 정렬
- desc(), asc() : 일반 정렬
- nullsLast(), nullsFirst() : null 데이터에 순서 부여
5. 페이징
- offset(1)
- limit(1)
6. 집합
- .count(m) / .sum(m.age) / .avg(m.age) / .max(m.age) / .min(m.age)
- .groupBy(item.price)
- .having(item.price.gt(1000))
7. 기본 join
: 첫 번째 파라미터에 join 대상을 지정, 두 번째 파라미터에 별칭으로 사용할 Q타입 지정
- .join(member.team, team)
- .leftJoin() / .rightJoin()
8. 세타 join
- from절에서 여러 Entity를 선택해서 join할 수 있음
- 원래 외부 join은 불가능했지만 join on을 사용하면 외부 join도 가능
9. join - on절
- join 대상을 필터링
- 연관관계가 없는 Entity 외부 join (※이 방법이 많이 쓰임)
※ on절을 활용해서 join 대상을 필터링할 때 내부 join을 사용하면 where절에서 필터링하는 것과 동일한 기능
→ 내부 join의 경우에는 where절로 해결하는 것이 좋다.
※ leftJoin()은 일반 join과 다르게 Entity 하나만 들어감
→ .from(member).leftJoin(team).on(~)
10. fetch join
- .join() 같은 join 기능 뒤에 fetch join() 추가
11. 서브 쿼리
: com.querydsl.jpa.JPAExpressions 사용
- where절 안의 in절에서도 사용 가능
- 주로 where절에서 사용하며, 하이버네이트 구현체를 사용하면 select절의 서브쿼리도 지원
- from절의 서브쿼리는 JPQL 서브쿼리의 한계로 지원하지 않음
=> from절 서브쿼리 해결방안
- 서브쿼리를 join으로 변경 (가능한 상황도 있고 불가능한 상황도 있음)
- 쿼리를 2번 분리해서 실행
- Native 쿼리 사용
※ JPAExpressions는 static import 가능
12. Case문
- .when(10).then("10개")
- .otherwise("기타")
- 복잡한 경우 CaseBuilder() 사용
※ Case를 굳이 DB에서 처리하는 것은 좋지 않다.
13. 상수 / 문자 더하기
: Expressions.constant() 사용
- .stringValue() : ENUM 타입을 처리할 때 유용
▶ 중급 문법
1. 프로젝션 결과 반환 - 기본
※ 프로젝션의 용도 : select 대상 지정
- 프로젝션 대상이 하나일 경우 타입을 명확하게 지정할 수 있음
- 프로젝션 대상이 둘 이상이면 Tuple이나 DTO로 조회
★ Tuple은 Querydsl에 종속적으므로 Repository 안에서 사용하고 밖에서 사용할 때는 DTO로 변환할 것
2. 프로젝션 결과 반환 - DTO 조회
: 순수 JPA에서 DTO를 조회할 때는 new 명령어가 필요
→ DTO의 package 이름을 다 적어줘야 하기 때문에 지저분함
=> Querydsl 빈 생성(Bean population)은 DTO를 조회하기 위한 3가지 방법 지원
└> 프로퍼티 접근 / 필드 직접 접근 / 생성자 사용
└> select절에서 Projections.bean / Projections.fields / Projections.constructor 방식으로 사용
3. 프로젝션 결과 반환 - @QueryProjection
: DTO 생성자에 @QueryProjection 어노테이션을 사용해서 DTO 또한 Querydsl로 활용 가능하게 함
→ 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법
→ 하지만 Q파일을 생성해야 하고, 구조적으로 봤을 때 DTO도 Querydsl에 의존적이게 되는 것이 애매할 수 있음
4. 동적 쿼리
방법1. BooleanBuilder 사용
방법2. Where 다중 파라미터 사용
└> where 조건에 null 값은 무시
└> 메소드를 다른 쿼리에서도 재활용 가능
└> 코드의 가독성이 높아짐
※ null 값은 주의해서 체크해야 함
※ Where 다중 파라미터 사용 방법을 권장함
5. 수정 / 삭제 배치 쿼리
: .execute()로 실행
★ 영속성 컨텍스트가 항상 우선권을 가짐
→ 벌크 연산을 하게 될 때마다 em.flush();와 em.clear();를 통해 영속성 컨텍스트를 계속 초기화해주는 것이 낫다.
6. SQL function 호출하기
: SQL function은 JPA와 같이 Dialect에 등록된 내용만 호출 가능
→ DB의 Dialect 클래스에 들어가서 템플릿을 찾은 다음 Expressions.stringTemplate("function('템플릿' ... )") 호출
※ 스프링 데이터 JPA와 Querydsl을 연동해서 페이징하는 방법
방법1. QueryResults<DTO>를 .fetchResults()하는 방법
방법2. List<DTO>의 .fetch()와 total count값(long)을 따로 쿼리하는 방법
※ return 값은 new PageImpl<>(content, pageable, total) 값을 넘기면 됨
=> 웹 서비스가 단순하다 싶으면 그냥 .fetchResults()의 방법을 써도 무방하다.
'JVM > JPA' 카테고리의 다른 글
스프링 데이터 JPA (0) | 2021.04.27 |
---|---|
객체지향 쿼리 언어 (JPQL) (0) | 2021.04.18 |
값 타입 (0) | 2021.04.16 |
Proxy와 연관관계 관리 (0) | 2021.04.16 |
Entity Mapping (0) | 2021.04.15 |