๐Ÿ”Ž QueryDSL โ€“ ๋™์  ์ฟผ๋ฆฌ ๋ฐ ์ •๋ ฌ์„ ์œ„ํ•œ Querydsl4RepositorySupport ๊ตฌํ˜„

๊น€๊ณต์˜ยท2024๋…„ 8์›” 14์ผ

how-to

๋ชฉ๋ก ๋ณด๊ธฐ
10/12

์ด ๊ธ€์—์„œ๋Š” ๋™์ ์œผ๋กœ ์กฐ๊ฑด๊ณผ ์ •๋ ฌ์„ ๋ฐ˜์˜ํ•˜๋Š” ๋™์  ์ฟผ๋ฆฌ์™€ ๋™์  ์ •๋ ฌ์„ ์œ„ํ•œ Querydsl4RepositorySupport ํด๋ž˜์Šค ๊ตฌํ˜„ ๋ฐ QueryDSL ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋‹ค๋ฃน๋‹ˆ๋‹ค.
๊ตฌํ˜„ ๋ฐฉ๋ฒ•์€ ๐Ÿงญ ํ™˜๊ฒฝ ์„ค์ • ๋ถ€ํ„ฐ ๋‚˜์™€ ์žˆ์Šต๋‹ˆ๋‹ค.

์™œ QueryDSL์„ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€

QueryDSL์€ ๋ฌด์—‡์ธ๊ฐ€

QueryDSL์€ ์ •์  ํƒ€์ž…์„ ์ด์šฉํ•ด SQL๊ณผ ๊ฐ™์€ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค. ์ฟผ๋ฆฌ๋ฅผ ๋ฌธ์ž์—ด๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, QueryDSL์ด ์ œ๊ณตํ•˜๋Š” Fluent API๋ฅผ ์ด์šฉํ•ด ์ฝ”๋“œ ํ˜•์‹์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด ์ฝ”๋“œ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์กฐ๋ฆฝํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด์„œ ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์ง€์›ํ•œ๋‹ค๋Š” ๋ง์ด๋‹ค.

JPQL๋งŒ์œผ๋กœ๋Š” ์•ˆ๋œ๋‹ค๊ณ ์š”?

ํ˜„์žฌ ๊ฐœ๋ฐœ ์ค‘์ธ ํ”„๋กœ์ ํŠธ์˜ ์š”๊ตฌ์‚ฌํ•ญ ์ค‘์—๋Š” ์ด๋ฏธ์ง€์˜ ์ •๋ณด์— ๋Œ€ํ•œ ๋‹ค์ค‘ ์กฐ๊ฑด ๊ฒ€์ƒ‰ ์ง€์›์— ๊ด€ํ•ด ๋ช…์‹œ๋˜์–ด์žˆ๋‹ค. ๋‹ค์ค‘ ์กฐ๊ฑด ๊ฒ€์ƒ‰์„ ์œ„ํ•ด์„œ๋Š” ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ด์•ผํ•˜๋Š”๋ฐ, ๋‹ค์ค‘ ์กฐ๊ฑด ๊ฒ€์ƒ‰ ๊ตฌํ˜„ ์ „์—๋Š” JPQL์„ ์‚ฌ์šฉํ–ˆ์—ˆ๋‹ค. ์ „์ฒด ๋ฆฌ์ŠคํŠธ์—์„œ ์ •ํ•ด์ง„ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ๊ฒฐ๊ณผ๋งŒ ๊ฒ€์ƒ‰ํ•˜๋ฉด ๋๊ธฐ์— ์ •์  ์ฟผ๋ฆฌ์ธ JPQL๋งŒ ์‚ฌ์šฉํ•ด๋„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด์ „์— QueryDSL์„ ์‚ฌ์šฉํ•ด๋ณธ ์ ์ด ์—†์—ˆ๊ธฐ์— ๊ดœํžˆ ์–ด๋ ต๊ฒŒ ๋А๊ปด์ ธ JPQL์—์„œ ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์—†์„๊นŒ ๊ณ ๋ฏผํ•˜๊ณ  ์ฐพ์•„๋ดค๋‹ค. JPQL์—์„œ ๋™์  ์ฟผ๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์กฐ๊ฑด์— ๋”ฐ๋ผ ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜๋™์œผ๋กœ String์œผ๋กœ ๋งŒ๋“ค์–ด ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„ฃ์–ด์ฃผ๋Š” ๊ท€์ฐฎ์€ ๊ณผ์ •์„ ๊ฑฐ์ณ์•ผ ํ•œ๋‹ค. ๊ฒฐ๊ตญ JPQL๋กœ ๋™์  ์ฟผ๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ํ•˜๋“œ ์ฝ”๋”ฉ๊ณผ ๋‹ค๋ฅผ ๋ฐ”๊ฐ€ ์—†๋‹ค๊ณ  ํŒ๋‹จํ•ด ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์ง€์›ํ•˜๋Š” QueryDSL์„ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.

์–ด.....๊ทผ๋ฐ ์™œ ์ •๋ ฌ์ด ์•ˆ๋˜์ง€?

ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„์„ ์œ„ํ•ด pagination์„ ์ง€์›ํ•œ๋‹ค. ์ด๋•Œ ํŽ˜์ด์ง•์„ ์œ„ํ•ด ํ”„๋ก ํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์•„์˜ค๋Š” ์กฐ๊ฑด๋“ค(page, size, sort, keyword) ์ค‘ ์ •๋ ฌ ์กฐ๊ฑด์ด ์ ์šฉ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค. ์ปค์Šคํ…€ํ•˜์ง€ ์•Š์€ QueryDSL์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ, pagination ์ž์ฒด๋Š” ์ ์šฉ์ด ๋˜์—ˆ์ง€๋งŒ ์ •๋ ฌ ์กฐ๊ฑด์€ ์ ์šฉ๋˜์ง€ ์•Š์•˜๋‹ค. ์ฐพ์•„๋ณด๋‹ˆ ๋™์  ์ •๋ ฌ์„ ์œ„ํ•ด OrderSpecifier๋ฅผ ์ง์ ‘ ์„ค์ •ํ•ด์ค˜์•ผ ํ–ˆ๋‹ค. ๋™์  ์ •๋ ฌ ์ ์šฉ๊ณผ QueryDSL์„ ๋” ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด Querydsl4RepositorySupport๋ฅผ ์ปค์Šคํ…€ํ•ด ์‚ฌ์šฉํ–ˆ๋‹ค.

๐Ÿงญ ํ™˜๊ฒฝ ์„ค์ •

๊ฐœ๋ฐœ ํ™˜๊ฒฝ

  • Spring Boot v3.3.1
  • Gradle build
  • MySQL

์˜์กด์„ฑ ์„ค์ •

build.gradle

QueryDSL์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์˜์กด์„ฑ๊ณผ source set์„ ์„ค์ •ํ•ด์ค€๋‹ค. source set์—์„œ๋Š” java compile ํ›„ ์ƒ๊ธฐ๋Š” Q domain class๋“ค์˜ ์ƒ์„ฑ ์œ„์น˜๋ฅผ ์„ค์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
์•„๋ž˜ ์„ค์ •์—์„œ๋Š” QueryDSL 5.1.0์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. QueryDSL์€ 4.x.x๊ฐ€ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜์ง€๋งŒ, 5.x.x ๋ฒ„์ „์ด release ๋˜๋ฉด์„œ fetchResults()์™€ fetchCount()๊ฐ€ deprecated ๋˜์—ˆ๊ธฐ์— 5.0.0 ์ดํ›„ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ํ–ฅํ›„ ์œ ์ง€๋ณด์ˆ˜์„ฑ์— ์ข‹์„ ๊ฒƒ์ด๋ผ ํŒ๋‹จํ•ด QueryDSL 5.1.0๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

...
dependencies {
    ...
    // querydsl ์˜์กด์„ฑ ์ถ”๊ฐ€
    implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
    implementation 'com.querydsl:querydsl-core'
    annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
    annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
    ...
}
...
// source set ์„ค์ •
sourceSets {
    main {
        java {
            // Q domain class๋“ค์ด src/main/generated์— ์ƒ์„ฑ๋˜๋„๋ก ์„ค์ •
            srcDirs = ['src/main/java', 'src/main/generated']
        }
    }
}
...

Q domain class ์ƒ์„ฑ

์˜์กด์„ฑ ์„ค์ •์ด ์ œ๋Œ€๋กœ ๋˜์—ˆ๋Š” ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด Q domain class๋ฅผ ์ƒ์„ฑํ•ด ํ…Œ์ŠคํŠธํ•œ๋‹ค. Q domain class๋Š” ๊ธฐ์กด์— ๋งŒ๋“ค์–ด์ ธ์žˆ๋Š” JPA repository๋“ค์˜ domain entity class๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ƒ์„ฑ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ๊นŒ Q domain class๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹น์—ฐํžˆ domain entity class๋ฅผ ๋งŒ๋“ค์–ด๋†”์•ผ ํ•œ๋‹ค.
Q domain class๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ”„๋กœ์ ํŠธ ๋‚ด์˜ Gradle ๋ฉ”๋‰ด์—์„œ build/clean ์‹คํ–‰ โ†’ other/compileJava ๋˜๋Š” build/build๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋œ๋‹ค. ์‹คํ–‰ ํ›„ ์•ž์„œ ์„ค์ •ํ•ด๋‘” sourceSet ๋‚ด๋ถ€์— Q domain class๋“ค์ด ์ƒ์„ฑ๋˜๋ฉด ์ œ๋Œ€๋กœ ์„ค์ •๋œ ๊ฒƒ์ด๋‹ค.

๐Ÿš€ How to use

์•„๋ž˜์˜ ๊ธ€์€ ์ด๋ฏธ JPA Repository๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„๋œ domain entity์™€ repository class๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฐ€์ • ํ•˜์— ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

JPA Repository์™€ QueryDSL ์—ฐ๋™

QueryDSL์„ ์‚ฌ์šฉํ•  ์ธํ„ฐํŽ˜์ด์Šค ์„ ์–ธ

์—ฌ๊ธฐ์—์„œ๋Š” ๋‹ค์ค‘ ์กฐ๊ฑด ๊ฒ€์ƒ‰๋งŒ์„ ์œ„ํ•œ SearchContentRepository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์„ ์–ธํ•ด์ฃผ์—ˆ๋‹ค.

package example.api.content.repository.search;

public interface SearchContentRepository {

    Page<ContentViewDto> searchByKeyword(Pageable pageable, String keyword,
      	List<Long> uploadDateRange, AccountEntity account);
}

JPA repository์— QueryDSL ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์† ์„ค์ •

๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋˜ JPA repository(์—ฌ๊ธฐ์„œ๋Š” ContentRepository)์— ์•ž์„œ ์„ ์–ธํ•œ SearchContentRepository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ถ€๋ชจ๋กœ ์„ค์ •ํ•ด์ค€๋‹ค.

package example.api.content.repository;

@Repository
public interface ContentRepository extends JpaRepository<ContentEntity, Long>, SearchContentRepository {

    ContentEntity findByContentId(String contentId);
    ContentEntity findByContentIdAndAccount(String contentId, AccountEntity account);
}

Querydsl4RepositorySupport ๊ตฌํ˜„

๋™์  ์ •๋ ฌ ์ง€์›์„ ์œ„ํ•ด pageable์˜ sort๋กœ OrderSpecifier๋ฅผ ์ƒ์„ฑํ•˜๋Š” getOrderSpecifier() ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , pagination์„ ์ง€์›ํ•˜๋„๋ก applyPagination() ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ–ˆ๋‹ค.

package example.global.querydsl;

import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.Querydsl;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Repository;

/**
 * Querydsl4 repository support
 */
@Repository
@Getter
public abstract class Querydsl4RepositorySupport {

    private final Class domainClass;
    private Querydsl querydsl;
    private EntityManager entityManager;
    private JPAQueryFactory queryFactory;

    public Querydsl4RepositorySupport(Class domainClass) {
        this.domainClass = domainClass;
    }

    /**
     * Set entity manager
     *
     * @param entityManager entity manager
     */
    @Autowired
    public void setEntityManager(EntityManager entityManager) {

        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);
    }

    /**
     * Select query - ์ฟผ๋ฆฌ ์ƒ์„ฑ์„ ์œ„ํ•œ ๋ฉ”์†Œ๋“œ
     *
     * @param expr ์ฟผ๋ฆฌ ๋Œ€์ƒ
     * @param <T>  ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ํƒ€์ž…
     * @return JPAQuery
     */
    protected <T> JPAQuery<T> select(Expression<T> expr) {
        return getQueryFactory().select(expr);
    }

    /**
     * Select query - ์ฟผ๋ฆฌ ์ƒ์„ฑ์„ ์œ„ํ•œ ๋ฉ”์†Œ๋“œ
     *
     * @param from ์ฟผ๋ฆฌ ๋Œ€์ƒ
     * @param <T> ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ํƒ€์ž…
     * @return JPAQuery
     */
    protected <T> JPAQuery<T> selectFrom(EntityPath<T> from) {
        return getQueryFactory().selectFrom(from);
    }

    /**
     * Apply pagination - ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฉ”์†Œ๋“œ
     *
     * @param pageable     ํŽ˜์ด์ง• ์ •๋ณด
     * @param contentQuery ์ฝ˜ํ…์ธ  ์ฟผ๋ฆฌ
     * @param countQuery   ์นด์šดํŠธ ์ฟผ๋ฆฌ
     * @param <T>          ํŽ˜์ด์ง• ๊ฒฐ๊ณผ ํƒ€์ž…
     * @return ํŽ˜์ด์ง• ๊ฒฐ๊ณผ
     */
    protected <T> Page<T> applyPagination(Pageable pageable,
        Function<JPAQueryFactory, JPAQuery> contentQuery,
        Function<JPAQueryFactory, JPAQuery> countQuery,
        Class<?> sortTargetClass,
        String sortVariableName) {

        JPAQuery jpaContentQuery = contentQuery.apply(getQueryFactory());
        List<OrderSpecifier> orders = getOrderSpecifier(pageable.getSort(), sortTargetClass,
            sortVariableName);
        if (!orders.isEmpty()) {
            jpaContentQuery.orderBy(orders.toArray(new OrderSpecifier[0]));
        }

        List<T> content = getQuerydsl().applyPagination(pageable, jpaContentQuery).fetch();
//        JPAQuery countResult = countQuery.apply(getQueryFactory());
        Long totalCount = countQuery.apply(getQueryFactory()).fetchCount();

        return PageableExecutionUtils.getPage(content, pageable, () -> totalCount);
    }

    /**
     * Get order specifier - ๋™์  ์ •๋ ฌ์„ ์œ„ํ•œ ๋ฉ”์†Œ๋“œ
     *
     * @param sort             ์ •๋ ฌ ์กฐ๊ฑด
     * @param sortTargetClass  ์ •๋ ฌ ๋Œ€์ƒ ํด๋ž˜์Šค
     * @param sortVariableName ์ •๋ ฌ ๋Œ€์ƒ ๋ณ€์ˆ˜๋ช…
     * @return ์ •๋ ฌ ์กฐ๊ฑด
     */
    private List<OrderSpecifier> getOrderSpecifier(Sort sort, Class<?> sortTargetClass,
        String sortVariableName) {

        List<OrderSpecifier> orders = new ArrayList<>();

        // Sort
        sort.stream().forEach(order -> {
            Order direction = order.isAscending() ? Order.ASC : Order.DESC;
            String prop = order.getProperty();
            PathBuilder orderByExpression = new PathBuilder(sortTargetClass, sortVariableName);
            orders.add(new OrderSpecifier(direction, orderByExpression.get(prop)));
        });
        return orders;
    }
}

QueryDSL ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด ๊ตฌํ˜„

์•ž์„œ ๋งŒ๋“  SearchContentRepository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ SearchStudyRepositoryImpl ํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์ด๋•Œ Querydsl4RepositorySupport ์ถ”์ƒ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•ด ์œ„์—์„œ ๊ตฌํ˜„ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
์กฐ๊ฑด ์ถ”๊ฐ€ ์‹œ ์กฐ๊ฑด์ด null์ธ์ง€ ํ™•์ธ ํ›„ builder์— ์ถ”๊ฐ€ํ•ด์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

package example.api.content.repository.search;

import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import example.api.account.domain.AccountEntity;
import kr.co.beamworks.cadaimbe.api.account.domain.QAccountEntity;
import kr.co.beamworks.cadaimbe.api.patient.domain.QPatientEntity;
import kr.co.beamworks.cadaimbe.api.study.domain.QStudyEntity;
import kr.co.beamworks.cadaimbe.api.study.domain.StudyEntity;
import kr.co.beamworks.cadaimbe.api.study.dto.response.StudyViewDto;
import kr.co.beamworks.cadaimbe.global.querydsl.Querydsl4RepositorySupport;
import kr.co.beamworks.cadaimbe.global.util.time.TimeConverter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;

@Repository
public class SearchStudyRepositoryImpl extends Querydsl4RepositorySupport implements SearchStudyRepository {

    /**
     * Creates a new {@link QuerydslRepositorySupport} instance for the given domain type.
     */
    public SearchStudyRepositoryImpl() {
        super(StudyEntity.class);
    }

    /**
     * Search study by keyword
     *
     * @param pageable pageable
     * @param keyword keyword
     * @param studyDateRange study date range(Unix time)
     * @param uploadDateRange upload date range(Unix time)
     * @param cadaiScoreRange cadai score range(0.0 ~ 100.0)
     * @param account account entity
     * @return study view dto page
     */
    @Override
    public Page<StudyViewDto> searchByKeyword(Pageable pageable, String keyword,
        List<Long> uploadDateRange, List<Double> cadaiScoreRange, AccountEntity account) {

        JPAQueryFactory queryFactory = new JPAQueryFactory(getEntityManager());
        QContentEntity studyEntity = QContentEntity.contentEntity;
        QAccountEntity accountEntity = QAccountEntity.accountEntity;

        BooleanBuilder builder = new BooleanBuilder();

        if (keyword != null && !keyword.isEmpty()) {
            builder.or(contentEntity.contentId.like("%" + keyword + "%"));
            builder.or(contentEntity.title.like("%" + keyword + "%"));
            builder.or(accountEntity.institution.like("%" + keyword + "%"));
            builder.or(contentEntity.description.like("%" + keyword + "%"));
        }

        // Add account condition if account is not null
        if (account != null) {
            builder.and(accountEntity.eq(account));
        }

        // Add range conditions
        if (uploadDateRange != null && uploadDateRange.size() == 2) {
            builder.and(contentEntity.uploadDate.between(TimeConverter.convertUnixTimeToLocalDateTime(uploadDateRange.get(0)),
                TimeConverter.convertUnixTimeToLocalDateTime(uploadDateRange.get(1))));
        }

        // ๋™์  ์ฟผ๋ฆฌ ์ƒ์„ฑ
        JPQLQuery<ContentViewDto> query = queryFactory
            .select(Projections.constructor(
                ContentViewDto.class,
                contentEntity.contentId,
                contentEntity.title,
                accountEntity.institution,
                contentEntity.description,
                contentEntity.uploadDate))
            .from(contentEntity)
            .join(contentEntity.account, accountEntity)
            .where(builder);

        // pagination ์ ์šฉ
        return applyPagination(pageable,
            factory -> (JPAQuery) query,
            factory -> (JPAQuery) query,
            ContentEntity.class,
            "contentEntity"
        );
    }
}

์ด์ œ ๋ญ˜ ํ•ด์•ผ ํ•˜๋Š”๊ฐ€

๋ฆฌํŒฉํ† ๋ง์ด ํ•„์š”ํ•˜๋‹ค......

ํ”„๋กœ์ ํŠธ๋ฅผ ๋น ๋ฅด๊ฒŒ ๋๋‚ด๋Š” ๊ฒŒ ๊ฐ€์žฅ ํฐ ๋ชฉํ‘œ์˜€๊ธฐ ๋•Œ๋ฌธ์— JPQL๊ณผ QueryDSL์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„๋˜๊ณ  ์žˆ๋‹ค. ํ•˜์ง€๋งŒ QueryDSL์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์ƒ ๊ตณ์ด JPQL์„ ์‚ฌ์šฉํ•  ์ด์œ ๊ฐ€ ์—†๊ธฐ์— QueryDSL๋งŒ์„ ์‚ฌ์šฉํ•ด ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฆฌํŒฉํ† ๋งํ•  ์˜ˆ์ •์ด๋‹ค.

๋” ์œ ์—ฐํ•œ ๊ฒ€์ƒ‰์„ ์ง€์›ํ•˜์ž

์˜ˆ๋ฅผ ๋“ค์–ด ์ด๋ฆ„์„ ๊ฒ€์ƒ‰ํ•  ๋•Œ ์ œ๋ชฉ์ด 'Hello world'์ธ ๊ฒŒ์‹œ๊ธ€์ด ์žˆ๋‹ค๊ณ  ํ•˜์ž. ํ˜„์žฌ๊นŒ์ง€ ๊ตฌํ˜„๋œ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์—์„œ๋Š” Hello w, Hel ๋“ฑ์€ ๊ฒ€์ƒ‰ ์‹œ ํ•ด๋‹น ๊ฒŒ์‹œ๊ธ€์ด ๋‚˜์˜ค์ง€๋งŒ hello world๋ฅผ ๊ฒ€์ƒ‰ํ–ˆ์„ ๋•Œ๋Š” ๋‚˜์˜ค์ง€ ์•Š๋Š”๋‹ค. ์ด์ฒ˜๋Ÿผ lower case, ๋„์–ด์“ฐ๊ธฐ๊ฐ€ ์ƒ๋žต๋œ ๊ฒ€์ƒ‰์–ด์— ๋Œ€ํ•ด ๋” ์œ ์—ฐํ•˜๊ฒŒ ๊ฒ€์ƒ‰์„ ์ง€์›ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ถ”๊ฐ€์ ์ธ ๋ฆฌ์„œ์น˜์™€ ๊ฐœ๋ฐœ์ด ํ•„์š”ํ•˜๋‹ค.


Reference : querydsl์„ ์ด์šฉํ•œ ํŽ˜์ด์ง•/๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ๊ตฌํ˜„, [Querydsl]๋™์  sorting์„ ์œ„ํ•œ OrderSpecifier ํด๋ž˜์Šค ๊ตฌํ˜„, QueryDSL ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€

profile
๋‚˜๋Š”์•ผ ๋งํ•˜๋Š” ๊ฐœ๋ฐœ(๊ฐ)์ž

0๊ฐœ์˜ ๋Œ“๊ธ€