해당 포스트에서 소개하는 기능은 제약이 커서 복잡한 실무 환경에서 사용하기에는 많이 부족하다고 한다. 그래도 Spring Data가 제공하는 기능이므로 간단히 알아보고, 왜 부족한지 이해해보자! (Spring에서 나중에 더 좋게 버전 업그레이드 할수도 있지않을까??)
인터페이스 지원 - QuerydslPredicateExecutor
공식 문서
최근 문서
사용 방법은 JPARepository에 상속하면 된다.
MemberRepository 코드 추가
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom, QuerydslPredicateExecutor<Member> {
}
테스트
@Test
public void querydslPredicateExcutorTest() {
Iterable<Member> result = memberRepository.findAll(QMember.member.age.between(20, 40).and(QMember.member.username.eq("member3")));
for (Member member : result) {
System.out.println("member = " + member);
}
}
findAll()로 모든 Member를 조회하는데, 조건을 추가할 수 있다. age가 20~30이며, username이 member3인 Member를 조회했다.
현재까지는 굉장히 좋은 것 같다. 뭐가 문제일까??
한계점
- 조인 X (RDB인데 조인이 불가능하다고?!) - 묵시적 조인은 가능하지만, left join이 불가능하다고 한다.
- 클라이언트가 Querydsl에 의존해야 한다. 서비스 클래스가 Querydsl이라는 구현 기술에 의존해야 한다.
- 이렇게하면 Repository를 만드는 의미가 없다.
- Repository를 만드는 이유는 복잡한 쿼리같은 경우 Querydsl을 repository계층 내부에 사용해서 외부로는 노출이 되지 않기 때문에 코드를 변경할 때 Repository 계층만 수정하면 된다.
- 하지만, Querydsl이 Service 계층으로 노출되는 경우 Service 계층도 수정을 해야한다.
- 복잡한 실무환경에서 사용하기에는 한계가 명확하다.
참고
QuerydslPredicateExecutor는 Pageable, Sort를 모두 지원하고 정상 동작한다.
Querydsl web 지원
공식 문서
최근 문서
한계점
- 단순한 조건만 가능 (eq 정도만 가능)
- 조건을 커스텀하는 기능이 복잡하고 명시적이지 않음
- 컨트롤러가 Querydsl에 의존
- 복잡한 실무환경에서 사용하기에는 한계가 명확
이건 쓰지말자...
Repository 지원 - QuerydslRepositorySupport
장점
- getQuerydsl().applyPagination()
- 스프링 데이터가 제공하는 페이징을 Querydsl로 편리하게 변환 가능 (단! Sort는 오류 발생) ??
- from() 으로 시작가능 (최근 QueryFactory를 사용해서 select()로 시작하는 것이 더 명시적이다)
- EntityManager 제공 (주입받지 않아도 된다.)
뭐 지금까지 나쁘지 않아 보인다. 코드를 몇줄 줄일 수 있을법하다. 하지만 한계가 명확하게 존재하다고 한다.
한계
- Querydsl 3.x 버전을 대상으로 만듬
- Querydsl 4.x 에 나온 JPAQueryFactory로 시작할 수 없음
- select로 시작할 수 없음 (from 시작)
- QueryFactory를 제공하지 않음
- 스프링 데이터 Sort 기능이 정상 동작하지 않음
Querydsl 지원 클래스 직접 만들기
스프링 데이터가 제공하는 QuerydslRepositorySupport가 지닌 한계를 극복하기 위해 직접 Querydsl 지원 클래스를 만들어보자!!
장점
- 스프링 데이터가 제공하는 페이징을 편리하기 변환
- 페이징과 카운트 쿼리 분리 가능
- 스프링 데이터 Sort 지원
- select(), selectFrom() 으로 시작 가능
- EntityManager, QueryFactory 제공
Querydsl4RepositorySupport
package study.querydsl.repository.support;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.Querydsl;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.function.Function;
/**
* Querydsl 4.x 버전에 맞춘 Querydsl 지원 라이브러리
*
* @author Younghan Kim
* @see
org.springframework.data.jpa.repository.support.QuerydslRepositorySupport
*/
@Repository
public abstract class Querydsl4RepositorySupport {
private final Class domainClass;
private Querydsl querydsl;
private EntityManager entityManager;
private JPAQueryFactory queryFactory;
public Querydsl4RepositorySupport(Class<?> domainClass) {
Assert.notNull(domainClass, "Domain class must not be null!");
this.domainClass = domainClass;
}
@Autowired
public void setEntityManager(EntityManager entityManager) {
Assert.notNull(entityManager, "EntityManager must not be null!");
JpaEntityInformation entityInformation =
JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager);
SimpleEntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
EntityPath path = resolver.createPath(entityInformation.getJavaType());
this.entityManager = entityManager;
this.querydsl = new Querydsl(entityManager, new
PathBuilder<>(path.getType(), path.getMetadata()));
this.queryFactory = new JPAQueryFactory(entityManager);
}
@PostConstruct
public void validate() {
Assert.notNull(entityManager, "EntityManager must not be null!");
Assert.notNull(querydsl, "Querydsl must not be null!");
Assert.notNull(queryFactory, "QueryFactory must not be null!");
}
protected JPAQueryFactory getQueryFactory() {
return queryFactory;
}
protected Querydsl getQuerydsl() {
return querydsl;
}
protected EntityManager getEntityManager() {
return entityManager;
}
protected <T> JPAQuery<T> select(Expression<T> expr) {
return getQueryFactory().select(expr);
}
protected <T> JPAQuery<T> selectFrom(EntityPath<T> from) {
return getQueryFactory().selectFrom(from);
}
protected <T> Page<T> applyPagination(Pageable pageable,
Function<JPAQueryFactory, JPAQuery> contentQuery) {
JPAQuery jpaQuery = contentQuery.apply(getQueryFactory());
List<T> content = getQuerydsl().applyPagination(pageable,
jpaQuery).fetch();
return PageableExecutionUtils.getPage(content, pageable,
jpaQuery::fetchCount);
}
protected <T> Page<T> applyPagination(Pageable pageable,
Function<JPAQueryFactory, JPAQuery> contentQuery, Function<JPAQueryFactory,
JPAQuery> countQuery) {
JPAQuery jpaContentQuery = contentQuery.apply(getQueryFactory());
List<T> content = getQuerydsl().applyPagination(pageable,
jpaContentQuery).fetch();
JPAQuery countResult = countQuery.apply(getQueryFactory());
return PageableExecutionUtils.getPage(content, pageable,
countResult::fetchCount);
}
}
MemberTestRepository
package study.querydsl.repository;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.stereotype.Repository;
import study.querydsl.dto.MemberSearchCondition;
import study.querydsl.entity.Member;
import study.querydsl.repository.support.Querydsl4RepositorySupport;
import java.util.List;
import static org.springframework.util.StringUtils.isEmpty;
import static study.querydsl.entity.QMember.member;
import static study.querydsl.entity.QTeam.team;
@Repository
public class MemberTestRepository extends Querydsl4RepositorySupport {
public MemberTestRepository() {
super(Member.class);
}
public List<Member> basicSelect() {
return select(member)
.from(member)
.fetch();
}
public List<Member> basicSelectFrom() {
return selectFrom(member)
.fetch();
}
public Page<Member> searchPageByApplyPage(MemberSearchCondition condition,
Pageable pageable) {
JPAQuery<Member> query = selectFrom(member)
.leftJoin(member.team, team)
.where(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe()));
List<Member> content = getQuerydsl().applyPagination(pageable, query)
.fetch();
return PageableExecutionUtils.getPage(content, pageable,
query::fetchCount);
}
public Page<Member> applyPagination(MemberSearchCondition condition,
Pageable pageable) {
return applyPagination(pageable, contentQuery -> contentQuery
.selectFrom(member)
.leftJoin(member.team, team)
.where(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())));
}
public Page<Member> applyPagination2(MemberSearchCondition condition,
Pageable pageable) {
return applyPagination(pageable, contentQuery -> contentQuery
.selectFrom(member)
.leftJoin(member.team, team)
.where(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())),
countQuery -> countQuery
.selectFrom(member)
.leftJoin(member.team, team)
.where(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe()))
); }
private BooleanExpression usernameEq(String username) {
return isEmpty(username) ? null : member.username.eq(username);
}
private BooleanExpression teamNameEq(String teamName) {
return isEmpty(teamName) ? null : team.name.eq(teamName);
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe == null ? null : member.age.goe(ageGoe);
}
private BooleanExpression ageLoe(Integer ageLoe) {
return ageLoe == null ? null : member.age.loe(ageLoe);
}
}
출처
'spring > 인프런 강의 정리' 카테고리의 다른 글
[Querydsl] 5. 실무 활용 - 스프링 데이터 JPA와 Querydsl (+페이징) (0) | 2022.05.20 |
---|---|
[Querydsl] 4. 실무 활용 - 순수 JPQ와 Querydsl (0) | 2022.05.19 |
[Querydsl] 3. 중급 문법 (0) | 2022.05.18 |
[Querydsl] 2. 기본 문법 (0) | 2022.05.17 |
[Querydsl] 1. 프로젝트 세팅 (0) | 2022.05.17 |