Querydsl

HUSII·2023년 7월 15일
0

JPA

목록 보기
6/7

Querydsl 설정

build.gradle에 설정 추가
(오른쪽위에)gradle -> task -> other -> compile.Querydsl 클릭
Q(Entity 이름) 파일이 build 파일에 자동으로 생김

Querydsl 다시 세팅하는 법
./gradlew clean
./gradlew compileQuerydsl or ```./gradlew compileJava

Q파일은 Git에 올리면 안됨
build 파일은 .gitignore 처리 (처음에 자동으로 처리된다)


Querydsl 사용하는 방법

JPAQueryFactory 객체 생성
JPAQueryFactory query = new JPAQueryFactory(em);

Querydsl을 쓸때 쿼리와 관련된것은 실제 엔티티를 넣는게 아니라 Q Entity를 넣어야함


JPQL vs Querydsl

jpql은 문자로 sql처리라서 처음에 오류를 체크하기 힘들다

querydsl은 sql을 자바 코드로 처리해서, 컴파일시점에 오류를 잡을 수 있다

Querydsl은 jpql의 빌더 역할, 결국은 jpql로 변환되서 실행된다
Querydsl을 통해 실행되는 jpql을 보고싶다면 설정파일에서
hibernate.use_sql_comments true로 설정


Q-Type

Q파일에 생성된 엔티티가 있다 <- 이걸 쓰면됨 QMember.member

static import로 더 깔끔하게 사용가능

같은 테이블을 조인할때는 이름을 구분해야하므로
QMember m1 = new QMember("m1"); 처럼 이름 구분해줌


결과 조회

fetch() 리스트 조회
fetchOne() 단 건 조회 (결과 없으면 null, 둘 이상이면 에러)
fetchCount() 카운트 쿼리만 보냄
fetchFirst() == limit(1).fetchOne()
fetchResults() 리스트 조회, 페이징 추가 (쿼리 두번 실행)

쿼리가 복잡할때는 카운트 쿼리도 복잡해져서, 따로 구현할 필요도 있다

Querydsl의 결과 attribute가 여러개일때
List<Tuple>로 받는다 or 직접 생성한 Dto로 받는다

튜플 조회

프로젝션 둘 이상일때 사용 com.querydsl.core.Tuple
tuple.get(필드명) - 해당 필드 조회


페이징

서비스에서 레포지토리 호출할때 Pageable 객체 인자로 넣어줌
조회 건수 제한.offset().limit().fetch() List<> 리턴
전체 조회 수가 필요 .fetchResults() QueryResults<> 리턴

fetchResults()는 카운트 쿼리가 실행되니 주의가 필요
카운트 쿼리 최적화 가능하다면 카운트 쿼리는 따로 구현


조인

(연관관계o)첫번째 파라미터에 조인 대상 지정, 두번째 파라미터에 별칭으로 지정할 Q타입
(연관관계x)첫번째 파라미터에 별칭으로 지정할 Q타입

inner join할때 on절로 처리 vs where절로 처리
그냥 where절 쓴다. 이유는 where가 익숙하니까
-> 조인 대상을 필터링할때, 내부조인이면 익숙한 where절 사용하고, 외부조인이 필요한 경우에만 on절을 사용하자

페치조인
.join(특정 엔티티).fetchJoin()

해당 필드가 로딩이 되었는지 체크(LAZY체크하기 위함)

@PersistenceUnit
EntityManagerFactory emf;
boolean emf.getPersistenceUnitUtil().isLoaded(엔티티의 특정 필드);

서브쿼리

com.querydsl.jpa.JPAExpressions 사용
(엔티티가 중복될때는 Q타입을 새로 생성해줘야함)

JPQL 서브쿼리의 한계
JPQL은 서브쿼리로 from절의 서브쿼리 지원안함
해결방안:
1. 서브쿼리는 조인으로 바꿀수있다.
2. 애플리케이션에서 쿼리를 2번으로 분리해서 실행
3. 네이티브 쿼리(nativeSQL) 사용

🔎 DB는 데이터를 가져오는 용도로만 쓰자

한방쿼리는 정말 좋을까?

db에서 데이터를 필터링(21 ~ 30 = 20대)하는게 좋을까?
db는 raw 데이터를 주고받는곳이기 때문에, 최소한의 필터링과 그룹핑을하고
이렇게 세세한 필터링은 애플리케이션 로직(presentation layer)에서 처리

숫자, 문자 더하기
숫자value.stringValue() 하면 문자됨
문자로 변환하고 .concat()

enum타입일대 값이 나오지 않는다면 .stringValue()해줌


프로젝션(Projection)

select 대상 지정하는 것
대상이 하나면 타입을 명확하게 지정할 수 있음
대상이 둘 이상이면 튜플이나 DTO로 조회
튜플: querydsl이 여러개의 객체를 대비해서 만들어놓은 타입

튜플을 레포지토리 계층을 넘어서서 서비스 계층이나 컨트롤러까지 넘어가는건 좋은 설계가 아니다
튜플은 querydsl에 종속적이기 때문

JPQL의 결과를 DTO로 받는경우
em.createQuery("select m.username, m.age from Member m", MemberDto.class)(X)
em.createQuery("select new 패키지명.MemberDto(m.username, m.age) from Member m", MemberDto.class)(O)
JPQL에서 생성자 방식만 지원함

Querydsl은 생성자, setter, 필드 접근 3가지 모두 지원함
setter .select(Projections.bean(MemberDto.class, m.username, m.age)
필드 .select(Projections.field(MemberDto.class, m.username, m.age) (기본 생성자 있어야함)
생성자 .select(Projections.constructor(MemberDto.class, m.username, m.age)

3개 모두 변수명이 같아야함
다른 Dto로 조회할때 필드명 맞추는법 .as(원하는 필드명)
혹은 ExpressionUtils.as()로 맞춰도됨 - 서브쿼리에서 자주씀

@QueryProjection
생성자에 붙는 어노테이션
해당 객체도 Q파일로 생성됨
장점: 쿼리의 프로젝션이 맞지않을떄 컴파일시에 에러가 난다
(기존 DTO 프로젝션은 런타임시에 에러가 난다)
단점: Q파일 생성하는 것, 의존관계 문제(해당 객체가 Querydsl에 의존하게됨) - DTO는 레포지토리 계층 뿐만 아니라 서비스 계층이나 컨트롤러에도 나올 수 있기 때문


동적 쿼리

  1. BooleanBuilder 사용
BooleanBuilder builder = new BooleanBuilder();
if() {
	builder.and(member.username.eq());
}
...
where(builder)
  1. Where 다중 파라미터 사용 - 더 가독성 좋음

    메서드 반환값으로 Predicate 인터페이스보다는 BooleanExpression이 낫다
    -> 컴포지션 가능

동적쿼리를 짤때는 기본조건이 있는게 좋고 limit라도 있는게 좋다
조건이 아예없다면 데이터 다 끌고와서 성능 개떨어짐
가급적이면 페이징이 같이 들어가는게 좋음
-> 단건조회라면 문제없지만 많으면 문제

StringUtils.hasText() null or "" 포함해서 문자 있는지 체크

CountQuery 최적화

JPAQuery<Member> countQuery = queryFactory
  .select(member)
  .from(member)
  .leftJoin(member.team, team)
  .where(usernameEq(condition.getUsername()),
  teamNameEq(condition.getTeamName()),
  ageGoe(condition.getAgeGoe()),
  ageLoe(condition.getAgeLoe()));
// return new PageImpl<>(content, pageable, total);
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchCount); 
// Page<MemberTeamDto> return

count 쿼리가 생략 가능한 경우 생략해서 처리

  • 페이지 시작이면서 컨텐츠 사이즈가 페이지 사이즈보다 작을 때
  • 마지막 페이지 일 때 (offset + 컨텐츠 사이즈를 더해서 전체 사이즈 구함)
profile
공부하다가 생긴 궁금한 것들을 정리하는 공간

0개의 댓글