728x90
개요
광주소프트웨어마이스터고등학교의 MSG팀에서 진행한 GAuth라는 프로젝트에서 검색 api를 만들면서 querydsl를 적용한 사례를 오늘 작성해보려고 한다. 작성해보고 나서의 장점에 대해서도 얘기해보겠다.
요구사항
요구사항은 이랬다. 학년, 반, 키워드를 통해 유저를 검색한다. 만약 학년, 반에 0이 들어온다면 전체 검색의 기준을 달고 키워드는 "" 빈 문자열이 들어오면 전체 검색 기준을 단다. 예를 들어 학년 반 키워드에 2, 0, "김" 이 들어오면 2학년 김씨 모두를 검색한다.
첫 구현
처음에는 findAll을 통해서 모든 유저리스트를 가져오고 filter를 통해서 유저를 걸러내었다.
class GetAcceptedUsersService(
private val userRepository: UserRepository,
) {
fun execute(grade: Int, classNum: Int, keyword: String, page: Int, size: Int): Page<SingleAcceptedUserResDto> =
userRepository.findAllByState(UserState.CREATED, PageRequest.of(page, size))
.let { filterUser(it, grade, classNum, keyword) }
.map { SingleAcceptedUserResDto(it) }
private fun filterUser(users: Page<User>, grade: Int, classNum: Int, keyword: String): Page<User> =
users.filter { grade == 0 || it.grade == grade }
.filter { classNum == 0 || it.classNum == classNum }
.filter { keyword.isEmpty() || it.name!!.contains(keyword) }
.toList()
.let { PageImpl(it, users.pageable, it.size.toLong()) }
}
이렇게 되면 문제가 생긴다. 내부 연산이 많이 일어나게 된다. 그리고 키워드를 거를때 contains를 사용하는데 가입된 유저의 수가 많을 수록 더 많은 연산을 수행하기때문에 프로그램의 성능이 저하된다.
두번째 구현
두번째 구현때는 좀 멍청한 하드코딩을 했는데 if문으로 인자 여부를 확인하고 그에 맞는 JPA find를 날렸다.
package com.msg.gauth.domain.user.services
import com.msg.gauth.domain.user.User
import com.msg.gauth.domain.user.enums.UserState
import com.msg.gauth.domain.user.presentation.dto.response.SingleAcceptedUserResDto
import com.msg.gauth.domain.user.repository.UserRepository
import com.msg.gauth.global.annotation.service.ReadOnlyService
import org.springframework.data.domain.Pageable
@ReadOnlyService
class GetAcceptedUsersService(
private val userRepository: UserRepository,
) {
fun execute(grade: Int, classNum: Int, name: String, pageable: Pageable): List<SingleAcceptedUserResDto> {
val userList: List<User>
= when {
grade == 0 && classNum == 0 && name == "" -> userRepository.findAllByStateOrderByGrade(UserState.CREATED, pageable)
grade == 0 && classNum == 0 -> userRepository.findAllByStateAndNameContainingOrderByGrade(UserState.CREATED, name, pageable)
classNum == 0 && name == "" -> userRepository.findAllByStateAndGradeOrderByGrade(UserState.CREATED, classNum, pageable)
grade == 0 && name == ""-> userRepository.findAllByStateAndClassNumOrderByGrade(UserState.CREATED, grade, pageable)
grade == 0 -> userRepository.findAllByStateAndClassNumAndNameContainingOrderByGrade(UserState.CREATED, classNum, name, pageable)
classNum == 0 -> userRepository.findAllByStateAndGradeAndNameContainingOrderByGrade(UserState.CREATED, grade, name, pageable)
name == "" -> userRepository.findAllByStateAndGradeAndClassNumOrderByGrade(UserState.CREATED, grade, classNum, pageable)
else -> userRepository.findAllByStateAndGradeAndClassNumAndNameContainingOrderByGrade(UserState.CREATED, grade, classNum, name, pageable)
}
return userList.map {
SingleAcceptedUserResDto(it)
}
이건 다시봐도 좀 에바다 ㅋㅋㅋ
결국 이 문제를 해결하기 위해서는?
동적 쿼리를 작성하기 위해 JPQL, Criteria나 QueryDSL을 사용해야한다.
그래서 결국 나는 QueryDSL를 채택하기로 했다.
QueryDSL!!
그렇게 쿼리 dsl로 작성한 코드다.
package com.msg.gauth.domain.user.repository
import com.fasterxml.jackson.databind.util.ArrayBuilders
import com.msg.gauth.domain.user.QUser.user
import com.msg.gauth.domain.user.User
import com.querydsl.core.BooleanBuilder
import com.querydsl.core.types.dsl.BooleanExpression
import com.querydsl.jpa.impl.JPAQueryFactory
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport
import org.springframework.stereotype.Repository
import javax.persistence.EntityManager
import javax.persistence.PersistenceContext
@Repository
class CustomUserRepositoryImpl(
private val jpaQueryFactory: JPAQueryFactory
) : CustomUserRepository {
override fun searchUser(grade: Int, classNum: Int, keyword: String): List<User> {
val booleanBuilder = BooleanBuilder()
if (grade != 0) {
booleanBuilder.and(user.grade.eq(grade))
}
if (classNum != 0) {
booleanBuilder.and(user.classNum.eq(classNum))
}
if (keyword.isNotEmpty()) {
booleanBuilder.and(user.name.like("%$keyword%"))
}
return jpaQueryFactory.selectFrom(user)
.where(booleanBuilder)
.fetch()
}
}
import com.msg.gauth.domain.user.presentation.dto.response.SingleAcceptedUserResDto
import com.msg.gauth.domain.user.repository.UserRepository
import com.msg.gauth.global.annotation.service.ReadOnlyService
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
@ReadOnlyService
class GetAcceptedUsersService(
private val userRepository: UserRepository,
) {
fun execute(grade: Int, classNum: Int, keyword: String): List<SingleAcceptedUserResDto> =
userRepository.searchUser(grade, classNum, keyword)
.map { SingleAcceptedUserResDto(it) }
}
이렇게 querydsl로 연산도 줄이고 더러운 하드 코딩도 줄일 수 있게 되었다.
앞으로 동적쿼리를 짤때 계속 공부하고 응용을 해보아야겠다.
이렇게 나의 첫 querydsl 적용기는 이렇게 끝을 내렸다.
728x90
'Server & Infra.' 카테고리의 다른 글
[ Service Mesh ] Istio, Istio Architecture 알아보기 (0) | 2023.07.26 |
---|---|
MSA, MA, SOA(Service Oriented Architecture), ESB, SOAP (0) | 2023.04.30 |
IntelliJ Spring boot 갑자기 8080포트가 사용중이라고 뜰때 대처방법 (0) | 2023.04.20 |
[DDD] 도메인 주도 설계 애그리거트(Aggregate) 알아보기 (0) | 2023.03.27 |
[Domain-Driven-Design] 바운디드 컨텍스트 (Bounded-Context) ✍️ (0) | 2023.03.25 |