Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 스프링MVC
- 스프링 데이터 JPA
- 컬렉션 조회 최적화
- JPA
- 스프링
- 페이징
- API 개발 고급
- 기본문법
- 트위터
- 임베디드 타입
- 타임리프
- jpa 활용
- Spring Data JPA
- 실무활용
- JPA 활용2
- 예제 도메인 모델
- 로그인
- 불변 객체
- 프로젝트 환경설정
- 김영한
- 스프링 mvc
- Bean Validation
- JPA 활용 2
- 일론머스크
- 값 타입 컬렉션
- 벌크 연산
- JPQL
- 타임리프 문법
- QueryDSL
- 검증 애노테이션
Archives
- Today
- Total
RE-Heat 개발자 일지
[Querydsl] [7] 스프링 데이터 JPA가 제공하는 Querydsl 기능 본문
인프런 김영한 님의 강의를 듣고 작성한 글입니다.
여기서 소개하는 기능은 제약이 커서 복잡한 실무 환경에서 쓰기엔 많이 부족한 편이다.
[1] 인터페이스 지원 - QuerydslPredicateExecutor
QuerydslPredicateExecutor
리포지토리
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom, QuerydslPredicateExecutor<Member> {
//select m From Member m where m.username =:username
List<Member> findByUsername(String username);
}
- QuerydslPredicateExecutor<>를 상속한 것을 알 수 있다.
테스트
@Test
public void querydslPredicateExecutorTest() throws Exception {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
Member member1 = new Member("member1", 10, teamA);
Member member2 = new Member("member2", 20, teamA);
Member member3 = new Member("member3", 30, teamB);
Member member4 = new Member("member4", 40, teamB);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
QMember member = QMember.member;
Iterable<Member> result = memberRepository.findAll(member.age.between(10, 40).and(member.username.eq("member1")));
for (Member findMember : result) {
System.out.println("findMember = " + findMember);
}
}
- QuerydslPredicateExecutor가 제공하는 findAll()을 사용하면 ()에 조건을 바로 넣을 수 있다
생성된 쿼리
■ 한계점
- 조인을 쓸 수 없다는 게 가장 크다.(묵시적 조인은 가능하나 left join을 쓸 수 없다)
- 클라이언트가 Querydsl에 의존해야 한다. 서비스 클래스가 Querydsl에 의존해야 한다.
- 복잡한 실무 환경에서 사용하기엔 한계가 명확하다
[2] Querydsl Web 지원
공식 URL : https://docs.spring.io/spring-data/jpa/docs/2.2.3.RELEASE/reference/html/#core.web.type-safe
URL로 Querydsl을 만들 수 있다.
그러나
세팅이 매우 복잡해 오히려 이 방식을 쓰는 게 더 헷갈린다.
■ 한계점
- 단순한 조건만 가능하다
- 조건을 커스텀하는 기능이 복잡하고 명시적이지 않다
- 컨트롤러가 Querydsl에 의존
- 복잡한 실무 환경에선 쓰기 어렵다
영한님 : 이거 쓰면 득보다 실이 많다
[3] 리포지토리 지원 - QuerydslRepositorySupport
기존 방식과 QuerydslRepositorySupport를 적용한 방식(우)
■ 장점
- getQuerydsl().applyPagination() 스프링 데이터가 제공하는 페이징을 Querydsl로 편리하게 변환 가능하다. (단! Sort는 오류발생)
- from() 으로 시작하는 것이 가능하다. (최근에는 QueryFactory를 사용해서 select() 로 시작하는 것이 더 명시적이다.)
- EntityManager를 제공한다.
■ 한계점
- Querydsl 3.x 버전을 대상으로 만들어서 Querydsl 4.x에 나온 JPAQueryFactory로 시작할 수 없다.
- select로 시작할 수 없다. from으로 시작해야 한다.
- offset, limit를 뺄 수 있으나 chain이 끊긴다.
- QueryFactory를 제공하지 않는다.
- 스프링 데이터 Sort 기능이 정상 동작하지 않는다.
[4] Querydsl 지원 클래스 직접 만들기
QuerydslRepositorySupport가 지닌 한계를 극복하기 위해 직접 Querydsl 지원 클래스를 만들어보자
Querydsl4RepositorySupport
/**
* 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);
}
}
사용코드
@Repository
public class MemberTestRepository extends Querydsl4RepositorySupport {
public MemberTestRepository(Class<?> domainClass) {
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()),
ageBetween(condition.getAgeLoe(), condition.getAgeGoe())
);
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, query ->
query.selectFrom(member)
.leftJoin(member.team, team)
.where(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageBetween(condition.getAgeLoe(), condition.getAgeGoe())
)
);
}
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()),
ageBetween(condition.getAgeLoe(), condition.getAgeGoe())
)
,
countQuery -> countQuery.select(member.id)
.from(member)
.leftJoin(member.team, team)
.where(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageBetween(condition.getAgeLoe(), condition.getAgeGoe())
)
);
}
private BooleanExpression ageBetween(Integer ageLoe, Integer ageGoe) {
if (ageLoe != null & ageGoe != null) {
return ageLoe(ageLoe).and(ageGoe(ageGoe));
} else {
return null;
}
}
private BooleanExpression usernameEq(String username) {
return hasText(username) ? member.username.eq(username) : null;
}
private BooleanExpression teamNameEq(String teamName) {
return hasText(teamName) ? team.name.eq(teamName) : null;
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe != null ? member.age.goe(ageGoe) : null;
}
private BooleanExpression ageLoe(Integer ageLoe) {
return ageLoe != null ? member.age.loe(ageLoe) : null;
}
}
① applyPagination() : 기존 방식과 달리 체인이 끊기지 않고 람다식으로 전달
- return applyPagination< 이 부분이 직접 만든 클래스 사용하는 부분이다.
② applyPagination2() : contentQuery와 CountQuery를 분리함
'백엔드 > Querydsl' 카테고리의 다른 글
[Querydsl] [6] 실무 활용 - 스프링 데이터 JPA와 Querydsl (0) | 2023.09.30 |
---|---|
[Querydsl] [5] 실무 활용 - 순수 JPA와 Querydsl (0) | 2023.09.30 |
[Querydsl] [4] 중급 문법 (하편) - 동적 쿼리·벌크 연산·SQL function (0) | 2023.09.30 |
[Querydsl] [4] 중급 문법 (상편) - 프로젝션 반환·@QueryProjection (0) | 2023.09.29 |
[Querydsl] [3] 기본 문법 (하편) (0) | 2023.09.17 |