[Spring] QueryDSL
개인적으로 공부하면서 정리한 내용입니다 : )
FLAG Project - BE Study
2022.11.10
QueryDSL
- 왜 QueryDSL을 사용할까?
- SQL, JPQL의 문제점
- JPA Criteria
- 문제의 해결 : QueryDSL
- QueryDSL의 장점
- 동적쿼리 vs 정적쿼리
- JPQL vs QueryDSL
- Q-type class
- 기본문법
- 검색조건 쿼리
- AND 조건 (파라미터로 처리)
- 나중에 추가 예정
(결과 조회, 정렬, 페이징, 집합, 기본 조인, 서브쿼리)
- 구현 예제
✅ QueryDSL?
JPQL의 작성을 도와주는 오픈소스 라이브러리
정적 타입을 이용해서 SQL과 같은 쿼리를 생성 할 수 있게 해주는 프레임워크
쿼리를 자바코드로 작성할 수 있게 도와주는 기술
(개 복잡한 쿼리를 만들기 위한 추가 도구 라고 보면 됨)
✅ 왜 QueryDSL을 사용할까? (WHY)
1- SQL과 JPQL의 문제점
- SQL, JPQL은 문자열로 Type-check 불가능
- 문자열이라서 동적쿼리 작성하는 것이 어려움
- 문법 오류를 애플리케이션 로딩 시점에 알 수 있다. ( 컴파일 시점엔 알 수 없다.)
- 로직 실행 전까지 작동 여부를 알수 없다.
- 쿼리 실행 시점에 오류를 발견한다.
2- 그래서 나온 JPA Criteria는 코드로 JPQL을 작성해서 문법 오류를 컴파일 단계에서 잡을 수 있게됨
또 동적 쿼리를 편리하게 작성할 수 있게 됨.
but) Criteria는 굉장히 복잡하고 어려움
→ 작성된 코드의 복잡성으로 어떤 JPQL이 생성될지 파악하기 쉽지 않음
3- 문제의 해결 (QueryDSL)
✔ 쿼리를 문자가 아닌 자바코드로 작성해 쉽고 간결하게,
모양도 쿼리와 비슷하게 개발할 수 있도록 하는 것이 QueryDSL
⏺ QueryDSL의 장점 👍
- 문자가 아닌 자바코드로서 작성
- 컴파일 시점에 문법 오류 발견
- 자바 코드이기 때문에 자바 컴파일러가 검증 가능하다
- IDE의 도움으로 코드 자동완성
- 단순하고, 쉬움 (코드의 모양이 JPQL과 흡사, 쿼리스럽고 직관적임)
- 동적 쿼리 작성이 편리 // 자바코드로 작성하니까 겠지?
- 유지보수에 용이
⏺ QueryDSL의 단점
- 코딩을 더 많이 해야함 ㅋㅋ
* 너무 복잡한 쿼리를 사용할 때만 QueryDSL을 사용하는걸 권장하는 것 같음
* 적당한 쿼리는 JPA 함수 쓰기..(너무 비효율적이고 느리면 QueryDSL로 가는걸 추천한다)
⏺ 동적쿼리 vs 정적쿼리
◼ 정적쿼리 (Static)
어떤 조건/상황에도 변경되지 않는 쿼리
어떻게 DB에 접근할지가 미리 정의되는 형태의SQL, SQL구문이 정적으로 "컴파일시점"에 정의 됨.
→ 변수를 사용하지 않는, 일반적으로 작성된 쿼리
◼ 동적쿼리 (Dynamic)
특정 조건/상황에 따라 변경되는 쿼리
런타임에서 사용자의 입력값에 따라 동적으로 SQL구문을 생성해 실행하는 방식
→ <값이 바뀌는 변수>를 넣어 쿼리문 작성
⏺ JPQL vs QueryDSL
💻 JPQL
@Test
void startJPQL() {
Member findByJPQL = em.createQuery(
"select m from Member m " +
"where m.username = :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();
assertThat(findByJPQL.getUsername()).isEqualTo("member1");
}
username이 일치하는 멤버를 조회함.
문제점
1) 문자열 작성으로 컴파일 단계에서 오류를 발견할 수 있음.
2) 사소한 띄어쓰기로 예외가 발생할 수 있지만, 실제로 코드가 동작할때까진 오류를 발견할 수 없음.
💻 QueryDSL
@Test
void startQuerydsl() {
QMember m = new QMember("m");
Member findMember = jpaQueryFactory
.select(m)
.from(m)
.where(m.username.eq("member1"))
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
위의 JPQL예제와 동일한 결과를 가져오지만, 쿼리에서 사용하는 명령어를 자바코드로서 작성한다.
✅ Q-type class
- QueryDsl을 사용하면, Q-type을 통해 쿼리를 작성한다.
- QueryDSL 설정을 성공적으로 마치면, @Entity가 붙은 클래스를 찾아 자동 생성된다.
- @Entity가 붙은 User.java 클래스가 있다면 QUser.java 파일이 자동으로 생성되는것
- Q타입 == 쿼리타입
- Q-type의 class들은 QueryDSL을 사용하여 메소드 기반으로 쿼리를 작성할 때
우리가 만든 도메인 클래스의 구조를 설명해주는 메타데이터 역할을 하며 쿼리의 조건을 설정할 때 사용됨.
⏺ Q클래스 인스턴스를 사용하는 2가지 방법
1) 별칭 직접 지정
QMember qMember = new QMember("m");
→ JPQL실행시 select m From Member m ~~ 식으로 작성
→ 같은 테이블을 Join / 서브쿼리 사용하는 경우가 아니면 잘 사용 안함.
2) 기본 생성 인스턴스 사용
QMember qMember = QMember.member;
→ static-import 사용
import static jpabook.jpashop.domain.QMember.member; // 기본 인스턴스
✅ 기본 문법
⏺ 검색조건 쿼리
QueryDSL 기본 쿼리 기능 코드
JPAQuery query = new JPAQuery(em);
QItem item = QItem.item;
List<Item> list = query.from(item)
.where(item.name.eq("좋은상품").and(item.price.gt(20000)))
.list(item);
얘를 실행하면 실행된 JPQL은
select item
from Item item
where item.name = ?1 and item.price > ?2
//대충 ?1 과 ?2가 동적쿼리- 변수부분임
✔ where절에는 and/ or을 사용할 수 있다.
@Test
void search() {
Member findMember = jpaQueryFactory
.selectFrom(member)
.where(member.username.eq("member1")
.and(member.age.eq(10)))
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
아래는 시간이 없어서 나중에 여유롭거나 직접 사용할 때쯤 추가할 예정이다.
⏺ AND 조건은 파라미터로 처리
⏺ 결과 조회
⏺ 정렬
⏺ 페이징
⏺ 집합
⏺ 기본 조인
⏺ 서브쿼리
✅ 구현 예제
1) entityManager 주입받기
QueryDSL이 쿼리를 생성할 수 있도록 엔티티매니저를 주입해야한다.
💻 QueryDslConfig
@Configuration
public class QuerydslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory(){
return new JPAQueryFactory(entityManager);
}
}
2) Spring data JPA를 구현할 repository를 인터페이스로 만들고,
JpaRepository와 사용자 정의 Repository ( MemberRepositoryCustom )를 상속받는다.
💻 MemberRepository
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
List<Member> findByUsername(String username);
}
3) QueryDsl을 사용하려면 사용자 정의 Reppository Interface를 만들어줘야한다.
💻 MemberRepositoryCustom
public interface MemberRepositoryCustom {
List<MemberTeamDto> search(SearchCond searchCond);
}
4) 사용자정의 인터페이스의 구현체 클래스를 만든다.
(여기에서 본격적인 QueryDSL 로직을 작성한다. )
💻 MemberRepositoryImpl
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom{
private final JPAQueryFactory queryFactory;
@Override
public List<MemberTeamDto> search(SearchCond cond) {
List<MemberTeamDto> result = queryFactory
.select(new QMemberTeamDto(
member.id.as("memberId"),
member.age,
member.username,
team.name.as("teamName")
))
.from(member)
.leftJoin(member.team , team)
.where(
usernameEq(cond.getUsername()),
teamNameEq(cond.getTeamName()),
ageLoe(cond.getAgeLoe()),
ageGoe(cond.getAgeGoe())
)
.fetch();
return result;
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe != null ? member.age.goe(ageGoe) : null;
}
private BooleanExpression ageLoe(Integer ageLoe) {
return ageLoe != null ? member.age.goe(ageLoe):null;
}
private BooleanExpression teamNameEq(String teamName) {
return hasText(teamName) ? team.name.eq(teamName):null;
}
private BooleanExpression usernameEq(String username) {
return hasText(username) ? member.username.eq(username):null;
}
}
⭐⭐⭐⭐⭐
1) 구현하는 클래스 (MemberRepositoryImpl)
2) 사용자 정의 인터페이스 (MemberRepositoryCustom)
3) 상속받아 해당 클래스를 사용하게 되는 레파지토리 (MemberRepository)
* 1, 3의 이름이 일치해야 스프링이 인식해 관리해준다.
++ 추가 학습
우아콘 2020
워밍업
1) extends / implements 사용하지 않기
일반적으로 QueryDSL을 사용한다고 하면, JpaRepositoy와 별도interface를 상속을 받음
그리고 해당 인터페이스를 구현하는 구현체 Impl가 필요하다.
-- 매번 interface와 / Impl 구조가 필요함. ((과함))
.>> 이때 Querydsl RepositorySupprot 를 상속받고, super생성자에 Entity를 등록하는 것도 있음 (이것도 불편함)
꼭 무언가를 상속/구현 , Entity를 지정하지 않더라도
쿼리DSL을 사용할 수 있는 방법은?
>> JPAQueryFactory만 있으면 Querydsl은 사용가능하다.
실제 querydsl을 사용할 땐 Factory만 있어서 생성자 주입받아서 사용하면, 모든 기능을 사용할 수 있어서
상속 구현 다 제거해도 됨.
2) 동적쿼리는 BooleanExpression
일반적으로 동적쿼리는 BooleanBuilder를 사용한다.
if문이 3개뿐이지만, 컬럼이 7개 10개가 되면 한 화면을 꽉 채우게 됨
어떤 쿼리인지 예상하기도 어려움
그래서 BooleanExpression을 많이 쓰는데,
메소드를 만들어, null로 리턴을 받을경우 자동으로 종료를 시킴
-- 모든 조건이 null이면 조건문이 다 삭제되어서 좋데
성능개선 select
1) exist 메소드 금지
이해하기 어려우니 그만해야겠다 기본이나 잘하고 성능개선을 보자 ㅎㅎ