querydsl을 이용한 페이징/검색 기능 구현

유승욱·2024년 2월 8일
1

Querydsl을 사용하는 이유

데이터베이스를 이용해야 할 때 JPA나 JPQL을 이용하면 SQL을 작성하거나 쿼리를 처리하는 소스 부분이 줄어들기 때문에 무척 편리하지만 어노테이션을 이용해서 지정하기 때문에 고정된 형태라는 단점이 있다.
예를 들어 '검색'이라는 기능이 필요한데 '제목/내용/작성자'와 같은 단일 조건으로 검색되는 경우도 있지만 '제목과 내용, 제목과 작성자'와 같이 복합적인 검색 조건이 생길 수 있는데, 이러한 경우 모든 경우의 수를 별도의 메소드로 작성해야하는 어려움이 발생한다.
이러한 문제를 해결하기 위해 코드를 동적으로 작성하게 해주는 Querydsl을 사용할 것이다.

Querydsl을 사용하기 위한 프로젝트 설정

Querydsl을 이용하기 위해서는 build.gradle설정을 다음과 같이 변경해주어야한다.

먼저 Querydsl의 버전을 지정합니다.

buildscript {
	ext {
		queryDslVersion = "5.0.0"
	}
}

그 다음 dependencies 부분에 Querydsl 관련 라이브러리들을 추가합니다.

implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"
	annotationProcessor(
			"jakarta.persistence:jakarta.persistence-api",
			"jakarta.annotation:jakarta.annotation-api",
			"com.querydsl:querydsl-apt:${queryDslVersion}:jakarta")

마지막으로 build.gradle의 마지막 부분에 sourceSets를 지정합니다.

sourceSets {

	main {
		java {
			srcDirs = ["$projectDir/src/main/java", "$projectDir/build/generated"]
		}
	}
}

compileJava.dependsOn('clean')

Querydsl의 설정 확인

Querydsl의 설정이 올바르게 되었는지를 확인하는 방법은 Q도메인 클래스가 정상적으로 만들어지는지 확인하는 것입니다.
프로젝트 내의 Gradle 메뉴를 열어서 'other' 부분의 compileJava를 실행하면 build 폴더에 Qdomain 클래스가 생성되는 것을 볼 수 있습니다.

기존의 Repository와 Querydsl 연동하기

Querydsl을 기존 코드에 연동하기 위해서는 다음과 같은 과정으로 작성합니다.
1. Querydsl을 이용할 인터페이스 선언
2. '인터페이스 이름 + Impl'이라는 이름으로 클래스를 선언(이때 현재 프로젝트에서는 페이징 처리를 도와주는 QuerydslRepositorySupport라는 부모 클래스를 지정하고 인터페이스를 구현할 것이다. 추후에 QuerydslRepositorySupport를 사용하지 않고 페이징과 검색 기능을 구현해보도록 하겠다.)
3. 기존의 Repository에는 부모 인터페이스로 Querydsl을 위한 인터페이스를 지정

1. Querydsl을 이용할 인터페이스 선언

현재 프로젝트는 다음과 같은 패키지 구조를 가진다.

먼저 BordSearch라는 이름의 인터페이스를 선언해준다.

2. '인터페이스 이름 + Impl'이라는 이름으로 클래스를 선언

BoardSearch의 구현체인 BoardSearchImpl에는 QuerydslRepositorySupport를 상속해주고 다음과 같이 생성자를 적어줘야 한다.(이때 실제 구현 클래스는 반드시 '인터페이스 이름 + Impl'로 작성해야한다. 그렇지 않으면 제대로 동작하지 않는다고 한다.)

3. 기존의 Repository에는 부모 인터페이스로 Querydsl을 위한 인터페이스를 지정

마지막으로 기존의 BoardRepository의 선언부에 BoardSearch 인터페이스를 추가로 지정해준다.

Q도메인을 이용한 쿼리 작성 예시 및 Pageable 처리 하기

Querydsl의 목적은 '타입' 기반으로 '코드'를 이용해서 JPQL 쿼리를 생성하고 실행하는 것이다. 이때 코드를 만드는 클래스가 Q도메인 클래스입니다.
작성된 BoardSearchImpl에서 Q도메인을 이용하는 코드를 작성해 보자.

search1()은 아직 완성됨 코드는 아니지만 Q도메인을 어떻게 사용하고 JPQLQuery라는 타입을 어떻게 사용하는지 보여줍니다.

JPQLQuery는 @Query로 작성했던 JPQL을 코드를 통해서 생성할 수 있게 한다. 이를 통해서 where나 group by 혹은 조인 처리 등이 가능해진다.
JPQLQuery의 실행은 fetch()라는 기능을 이용하고, fetchCount()를 이용하면 count 쿼리를 실행할 수 있다.

Querydsl로 Pageable 처리하기

Querydsl의 실행 시에 Pageable을 처리하는 방법은 BordSearchImpl이 상속한 QuerydslRepositorySupport라는 클래스의 기능을 이용합니다.
여기서 applyPagination()을 이용하면 offset과 limit를 처리를 자동으로 해준다.

테스트 코드를 실행하면 다음과 같은 sql문이 생성된다.

Querydsl로 검색 조건과 목록 처리

게시물에서 다양한 검색 조건이 있다면 이를 Querydsl을 이용해서 원하는 JPQL을 생성하고 실행할 수 있습니다.
검색의 경우 '제목(t), 내용(c), 작성자(w)'의 조합을 통해서 이루어진다고 가정하고 이를 페이징 처리와 함께 동작하도록 구성합니다.

BooleanBuilder

예를 들어 '제목이나 내용'에 특정한 키워드가 id가 0보다 큰 데이터를 찾는다면 sql에서는 다음과 같이 작성하게 된다.

이때 where조건에 and와 or이 섞여 있을 때는 연산자의 우선 순위가 다르기 때문에 or 조건은 '()'로 묶어서 하나의 단위를 만들어 주는 것이 좋다.
Querydsl을 이용할 때 '()'가 필요한 상황이라면 BooleanBuilder를 이용해서 작성할 수 있습니다.

검색을 위한 메소드 선언

검색을 위해서는 적어도 검색 조건들과 키워드가 필요하므로 이를 types와 keyword로 칭하고 types은 여러 조건의 조합이 가능하도록 처리하는 메소드를 BoardSearch에 추가해준다.

검색 조건을 의미하는 types는 '제목(t), 내용(c), 작성자(w)'로 구성된다고 가정하고 이를 반영해서 다음과 같은 코드를 작성할 수 있다.(search1() 함수는 테스트용이고 searchAll() 함수가 검색 기능과 페이징 기능을 합친 최종 코드이다.)

PageImpl을 이용한 Page<> 반환

페이징 처리의 최종 결과는 Page<> 타입을 반환하는 것이므로 Querydsl에서는 이를 직접 처리해야 하는 불편함이 있다. Sprig Data JPA에서는 이를 처리하기 위해 PageImpl<>이라는 클래스를 제공해서 3개의 파라미터로 Page<>를 생성할 수 있다.

List<>: 실제 목록 데이터
Pageable: 페이지 관련 정보를 가진 객체
long: 전체 개수

테스트 코드를 작성하고 페이지 관련 정보를 추출해보자.

Service코드 구현하기

목록과 검색 처리는 PageRequestDto, PageResponseDto 클래스를 작성하고 검색 타입과 키워드를 처리할 수 있도록 구성한다.

PageRequestDto

PageRequestDto는 페이징 관련 정보(page/size) 외에 검색 종류(type)와 키워드(keyword)를 추가해서 지정한다.

검색의 종류는 문자열 하나로 처리해서 나중에 각 문자를 분리하도록 구성한다.

PageRequestDto에는 몇 가지 필요한 기능들이 존재한다. 우선 혀재 검색 조건들을 BoardRepository에서 String[]으로 처리하기 때문에 type이라는 문자열을 배열로 반환해 주는 기능이 필요하고, 페이징 처리를 위해서 사용하는 Pageable타입을 반환하는 기능도 있으면 편리하므로 메소드로 구현해주었다.

PageResponseDto

PageResponseDto는 화면에 Dto의 목록과 시작 페이지/끝 페이지 등에 대한 처리를 담당하므로 다음과 같은 내용으로 구성한다.

BoardService/BoardServiceImpl

BoardService는 list()라는 이름으로 목록/검색 기능을 선언한다.

BoardServiceImpl은 우선 BoardRepository를 호출하는 기능부터 다음과 같이 작성한다.

테스트 코드를 작성해 확인해보자.
testList()에서는 '제목 혹은 내용 혹은 작성자'가 '1'이라는 문자열을 가진 데이터를 검색하고 페이징 처리한다.

컨트롤러와 화면 처리


페이징 처리 화면

검색 조건과 키워드를 추가

/board/list 뒤에 size, type, keyword가 붙은 것을 확인할 수 있다.

느낀점

Querydsl을 이용하면 다양한 경우의 수를 동적 쿼리를 통해 한 번에 처리할 수 있다는 점에서 코드의 효율성을 위해 활용하는 것이 좋다고 느꼈다.
그리고 추후에 QuerydslRepositorySupport대신 QueryFactory를 사용해서 페이징 기능을 구현해보도록 하겠다.

0개의 댓글