본문 바로가기

백엔드/JPA

스프링 데이터 JPA, 쿼리 dsl

쿼리 dsl을 적용하기 위해서는 스프링 데이터 JPA의 인터페이스에 사용자 정의 리포지토리를 상속해야한다.

public interface MemberRepo extends JpaRepository<Member, Long>, MemberCustom {

}

public interface MemberCustom {
    List<MemTeamDTO> search(MemberSearchDTO condition);
    List<MemTeamDTO> searchPageSimple(MemberSearchDTO condition, Pageable pageable);
    List<MemTeamDTO> searchPageComplex(MemberSearchDTO condition, Pageable pageable);
}


public class MemberCustomImpl implements MemberCustom {
// 인터페이스의 각 메소드 구현
}

 

페이징 적용하기

MemberSearchDTO condition = new MemberSearchDTO();
PageRequest page = PageRequest.of(0, 2); // offset, limit
Page<MemTeamDTO> result = memberRepo.searchPageSimple(condition, page);

페이징 함수 호출시에 PageRequest를 인자로 넘겨서 호출한다.

	@Override
    public Page<MemTeamDTO> searchPageSimple(MemberSearchDTO condition, Pageable pageable) {
        QueryResults<MemTeamDTO> results = qf
                .select(new QMemTeamDTO(member.id.as("memberId"), member.username, member.age, team.id, team.name.as("teamName")))
                .from(member)
                .leftJoin(member.team, team)
                .where(userNameEq(condition.getUsername()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe()))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults();
                
        List<MemTeamDTO> content = results.getResults();
        long total = results.getTotal();

        return new PageImpl<>(content, pageable, total);
    }

fetchResults 로 결과를 가져오게 되면 결과값 조회 쿼리 1개, count 쿼리 1개가 나간다.

 

count 쿼리 분리하기

분리하는 법은 간단하다. 위의 fetchResults를 fetch()와 fetchCount() 로 나누면 된다.

List<MemTeamDTO> content = qf
                .select(new QMemTeamDTO(member.id.as("memberId"), member.username, member.age, team.id, team.name.as("teamName")))
                .from(member)
                .leftJoin(member.team, team)
                .where( 조건 )
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();

        long total = qf
                .select(new QMemTeamDTO(member.id.as("memberId"), member.username, member.age, team.id, team.name.as("teamName")))
                .from(member)
                .leftJoin(member.team, team) // 조인 없애도 됨
                .where( // 조건)
                .fetchCount();

분리했을 경우 이점

 count 쿼리는 기본적으로 동일한 쿼리가 select 문에 count(*) 로만 바뀌어서 나가기 때문에 조회 쿼리가 복잡하면 성능이 안좋을 수 있다. 그래서 이렇게 분리했을 경우에는 count쿼리를 단순하게 만들 수 있다.

 

PageableExecutionUtils 적용

count 쿼리가 나가지 않아도 되는 경우는 두가지이다.

1. offset이 0일때 결과 갯수가 limit보다 작으면 그냥 결과 갯수가 총 count
2. 마지막 페이지면 offset + 결과 갯수 = count

이 둘을 처리해주는 것이 PageableExecutionUtils이다.

JPAQuery<MemTeamDTO> where = qf
                .select(new QMemTeamDTO(member.id.as("memberId"), member.username, member.age, team.id, team.name.as("teamName")))
                .from(member)
                .leftJoin(member.team, team)
                .where(//조건);
return PageableExecutionUtils.getPage(content, pageable, () -> where.fetchCount());
//좀 더 자바답게 수정하면
return PageableExecutionUtils.getPage(content, pageable, where::fetchCount);

count 쿼리를 분리했던 부분부터 리턴 값까지 이렇게 수정하면 된다. 그래서 만약 위 두가지 조건이 해당하지 않을 경우에만 getPage의 3번째 인자인 count 쿼리를 날리는 함수가 실행된다.

 

API에 적용하기

@GetMapping("/members")
public Page<MemTeamDTO> serchV3(MemberSearchDTO con, Pageable pageable){
  	return memberRepo.searchPageComplex(con, pageable);
}

테스트는 insomnia로 진행했다.

page와 size를 파라미터로 넘기면 Pageable이 생성되어 페이징이 잘 된 것을 확인할 수 있었다. 추가로

이렇게 size가 결과값의 갯수를 넘을 경우에는 count쿼리가 나가지 않는다.

 


관련글

https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84/dashboard

 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지 한번에 해결, 본 강의는 자바 백엔드 개발의 실전 코스를 완성하는 마지막 강의 입니다. 스프링 부트와 JPA 실무 완전 정복 로드맵을 우선 확인해주세요. 로드

www.inflearn.com