build.gradle
에 설정 추가
(오른쪽위에)gradle -> task -> other -> compile.Querydsl 클릭
Q(Entity 이름) 파일이 build 파일에 자동으로 생김
Querydsl 다시 세팅하는 법
./gradlew clean
./gradlew compileQuerydsl
or ```./gradlew compileJava
Q파일은 Git에 올리면 안됨
build 파일은 .gitignore 처리 (처음에 자동으로 처리된다)
JPAQueryFactory 객체 생성
JPAQueryFactory query = new JPAQueryFactory(em);
Querydsl을 쓸때 쿼리와 관련된것은 실제 엔티티를 넣는게 아니라 Q Entity를 넣어야함
jpql은 문자로 sql처리라서 처음에 오류를 체크하기 힘들다
querydsl은 sql을 자바 코드로 처리해서, 컴파일시점에 오류를 잡을 수 있다
Querydsl은 jpql의 빌더 역할, 결국은 jpql로 변환되서 실행된다
Querydsl을 통해 실행되는 jpql을 보고싶다면 설정파일에서
hibernate.use_sql_comments
true로 설정
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()해줌
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는 레포지토리 계층 뿐만 아니라 서비스 계층이나 컨트롤러에도 나올 수 있기 때문
BooleanBuilder builder = new BooleanBuilder();
if() {
builder.and(member.username.eq());
}
...
where(builder)
메서드 반환값으로 Predicate 인터페이스보다는 BooleanExpression이 낫다
-> 컴포지션 가능
동적쿼리를 짤때는 기본조건이 있는게 좋고 limit라도 있는게 좋다
조건이 아예없다면 데이터 다 끌고와서 성능 개떨어짐
가급적이면 페이징이 같이 들어가는게 좋음
-> 단건조회라면 문제없지만 많으면 문제
StringUtils.hasText()
null or "" 포함해서 문자 있는지 체크
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 쿼리가 생략 가능한 경우 생략해서 처리