📦 Database/QueryDSL

간단한 조건 검색 (필터링, 상세검색) 기능 만들기 (Kotlin, QueryDSL)

신건우 2023. 6. 25. 18:44

검색 기능 만들기

QueryDSL의 Boolean Builder와 OrderSpecifier를 활용한 검색기능을 만들어 보았습니다.


요구사항

  • 필터링: 와인 종류, 알코올 도수 범위, 와인의 가격 범위, 와인의 스타일, 와인의 등급, 지역
  • 정렬: 와인 이름, 알코올 도수, 산도, 바디감, 단맛, 타닌, 와인의 점수, 와인의 가격
  • 검색: 와인 이름

class CustomWineRepositoryImpl(
    @Autowired private val queryFactory: JPAQueryFactory
) : QuerydslRepositorySupport(Wine::class.java), CustomWineRepository {

    override fun findSingleWineByCondition(filter: FilterDTO.Filter?, sort: FilterDTO.Sort?, name: String?): Tuple? {
        val builder = BooleanBuilder()

        // Type 필터링
        if (!filter?.type.isNullOrEmpty()) builder.and(wine.type.eq(filter?.type))

        // Alcohol 범위 필터링
        if (filter?.minAlcohol != null && filter.maxAlcohol != null && filter.minAlcohol!! >= 0.0 && filter.maxAlcohol!! <= 20.0) {
            builder.and(wine.alcohol.between(filter.minAlcohol, filter.maxAlcohol))
        } else if (filter?.minAlcohol != null) {
            builder.and(wine.alcohol.goe(filter.minAlcohol))
        } else if (filter?.maxAlcohol != null) {
            builder.and(wine.alcohol.loe(filter.maxAlcohol))
        }

        // Price 범위 필터링
        if (filter?.minPrice != null && filter.maxPrice != null && filter.minPrice!! >= 0 && filter.maxPrice!! <= 1000000) {
            builder.and(wine.price.between(filter.minPrice, filter.maxPrice))
        } else if (filter?.minPrice != null) {
            builder.and(wine.price.goe(filter.minPrice))
        } else if (filter?.maxPrice != null) {
            builder.and(wine.price.loe(filter.maxPrice))
        }

        // Style 필터링
        if (!filter?.style.isNullOrEmpty()) {
            builder.and(wine.style.eq(filter?.style))
        }

        // Grade 필터링
        if (!filter?.grade.isNullOrEmpty()) {
            builder.and(wine.grade.eq(filter?.grade))
        }

        // Region 필터링
        if (!filter?.region.isNullOrEmpty()) {
            builder.and(wine.region.name.eq(filter?.region))
        }

        // 정렬 조건
        val orderByExpressions = mutableListOf<com.querydsl.core.types.OrderSpecifier<*>>()

        when {
            !sort?.name.isNullOrEmpty() -> orderByExpressions.add(wine.name.desc())
            sort?.alcohol != null -> orderByExpressions.add(wine.alcohol.desc())
            sort?.acidity != null -> orderByExpressions.add(wine.acidity.desc())
            sort?.body != null -> orderByExpressions.add(wine.body.desc())
            sort?.sweetness != null -> orderByExpressions.add(wine.sweetness.desc())
            sort?.tannin != null -> orderByExpressions.add(wine.tannin.desc())
            sort?.score != null -> orderByExpressions.add(wine.score.desc())
            sort?.price != null -> orderByExpressions.add(wine.price.desc())
        }

        // Name 검색어
        if (!name.isNullOrEmpty()) {
            builder.and(wine.name.containsIgnoreCase(name))
        }

        return queryFactory
            .select(
                wine.type,
                wine.name,
                wine.alcohol,
                wine.acidity,
                wine.body,
                wine.sweetness,
                wine.tannin,
                wine.price,
                wine.style,
                wine.grade,
                wine.importer.name,
                wine.wineryName,
                wine.region.name,
                wine.region.parentName,
                wine.aroma,
                wine.pairing,
                wine.wineGrape
            )
            .from(wine)
            .innerJoin(wine.aroma).fetchJoin()
            .innerJoin(wine.wineGrape).fetchJoin()
            .innerJoin(wine.pairing).fetchJoin()
            .innerJoin(wine.region).fetchJoin()
            .innerJoin(wine.importer).fetchJoin()
            .where(builder)
            .orderBy(*orderByExpressions.toTypedArray())
            .fetchOne()
    }

    override fun findMultiWineByCondition(filter: FilterDTO.Filter?, sort: FilterDTO.Sort?, name: String?): MutableList<Tuple>? {

        val builder = BooleanBuilder()

        // Type 필터링
        if (!filter?.type.isNullOrEmpty()) builder.and(wine.type.eq(filter?.type))

        // Alcohol 범위 필터링
        if (filter?.minAlcohol != null && filter.maxAlcohol != null && filter.minAlcohol!! >= 0.0 && filter.maxAlcohol!! <= 20.0) {
            builder.and(wine.alcohol.between(filter.minAlcohol, filter.maxAlcohol))
        } else if (filter?.minAlcohol != null) {
            builder.and(wine.alcohol.goe(filter.minAlcohol))
        } else if (filter?.maxAlcohol != null) {
            builder.and(wine.alcohol.loe(filter.maxAlcohol))
        }

        // Price 범위 필터링
        if (filter?.minPrice != null && filter.maxPrice != null && filter.minPrice!! >= 0 && filter.maxPrice!! <= 1000000) {
            builder.and(wine.price.between(filter.minPrice, filter.maxPrice))
        } else if (filter?.minPrice != null) {
            builder.and(wine.price.goe(filter.minPrice))
        } else if (filter?.maxPrice != null) {
            builder.and(wine.price.loe(filter.maxPrice))
        }

        // Style 필터링
        if (!filter?.style.isNullOrEmpty()) {
            builder.and(wine.style.eq(filter?.style))
        }

        // Grade 필터링
        if (!filter?.grade.isNullOrEmpty()) {
            builder.and(wine.grade.eq(filter?.grade))
        }

        // Region 필터링
        if (!filter?.region.isNullOrEmpty()) {
            builder.and(wine.region.name.eq(filter?.region))
        }

        // 정렬 조건
        val orderByExpressions = mutableListOf<com.querydsl.core.types.OrderSpecifier<*>>()

        when {
            !sort?.name.isNullOrEmpty() -> orderByExpressions.add(wine.name.desc())
            sort?.alcohol != null -> orderByExpressions.add(wine.alcohol.desc())
            sort?.acidity != null -> orderByExpressions.add(wine.acidity.desc())
            sort?.body != null -> orderByExpressions.add(wine.body.desc())
            sort?.sweetness != null -> orderByExpressions.add(wine.sweetness.desc())
            sort?.tannin != null -> orderByExpressions.add(wine.tannin.desc())
            sort?.score != null -> orderByExpressions.add(wine.score.desc())
            sort?.price != null -> orderByExpressions.add(wine.price.desc())
        }

        // Name 검색어
        if (!name.isNullOrEmpty()) {
            builder.and(wine.name.containsIgnoreCase(name))
        }


        return queryFactory
            .select(
                wine.type,
                wine.name,
                wine.region.name
            )
            .from(wine)
            .innerJoin(wine.region).fetchJoin()
            .where(builder)
            .orderBy(*orderByExpressions.toTypedArray())
            .fetch()
    }
}