๐Ÿ“š QueryDSL ํ™•์žฅ ์„ค๊ณ„: ์กฐ๊ฑดยท์ •๋ ฌ์„ ์ชผ๊ฐœ์„œ ์กฐ๋ฆฝํ•œ๋‹ค

๋ฐ•์ค€ํ˜•ยท2025๋…„ 8์›” 17์ผ

์Šคํ”„๋ง ๊ฐœ๋ฐœ

๋ชฉ๋ก ๋ณด๊ธฐ
14/20
post-thumbnail

0) TL;DR ๐Ÿงพ

  • ๋ฌธ์ œ: ์กฐ๊ฑด ์กฐํ•ฉ ํญ๋ฐœ, ๊ฑฐ๋Œ€ ๋ฉ”์„œ๋“œ, JPA -> MyBatis ์ „ํ™˜ ๋“ฑ ๋ฌธ์ œ
  • ํ•ด๋ฒ•: Predicate(์กฐ๊ฑด), Sort(์ •๋ ฌ), Score(์ธ๊ธฐ ์ ์ˆ˜) ๋“ฑ์„ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ  ์–ด๋Œ‘ํ„ฐ์—์„œ ์„ ์–ธ์ ์œผ๋กœ ์กฐ๋ฆฝํ•œ๋‹ค.
  • ํšจ๊ณผ: โœจ ๋ณ€๊ฒฝ ์šฉ์ด ยท ๐Ÿงช ํ…Œ์ŠคํŠธ์„ฑ ยท ๐Ÿงฉ ์žฌ์‚ฌ์šฉ์„ฑ ยท ๐Ÿงญ ์•„ํ‚คํ…์ฒ˜ ์ผ๊ด€์„ฑ.


๐ŸŽฏ 1) ์™œ ์ด ๊ตฌ์กฐ์ธ๊ฐ€?

๐Ÿ˜ต ํ˜„์žฅ์—์„œ ๊ฒช๋Š” ๋ฌธ์ œ

  • ํ•„ํ„ฐ ์กฐ๊ฑด์ด ๋Š˜์–ด๋‚ ์ˆ˜๋ก ๋ณต์žกํ•ด์ง
  • ํ‚ค์›Œ๋“œ, ์ฃผ์ œ, ์†Œ์Šค, ํƒ€์ž…, ์—ฐ๋„โ€ฆ ์กฐ๊ฑด์ด ๋งŽ์•„์ง€๋‹ˆ ์ฝ”๋“œ๊ฐ€ if/else๋กœ ๋’ค์—‰ํ‚จ๋‹ค.
  • ํ•œ ๋ฉ”์„œ๋“œ๊ฐ€ ๋„ˆ๋ฌด ์ปค์ง
  • ์ •๋ ฌ ์˜ต์…˜(์ตœ์‹ ์ˆœ, ๋‹ค์šด๋กœ๋“œ์ˆœ, ํ™œ์šฉ์ˆœ ๋“ฑ), ์ธ๊ธฐ ์ ์ˆ˜ ๊ณ„์‚ฐ, ์กฐ์ธ, ๊ทธ๋ฃน, ํŽ˜์ด์ง•๊นŒ์ง€ ํ•œ ๋ฉ”์„œ๋“œ ์•ˆ์— ๋‹ค ๋ชฐ๋ ค์žˆ๋‹ค.
  • ์ž‘์€ ๋ณ€๊ฒฝ๋„ ํฐ ์˜ํ–ฅ
  • ํ•„ํ„ฐ ํ•˜๋‚˜๋งŒ ๋ฐ”๊ฟจ๋Š”๋ฐ ์ „์ฒด ์ฟผ๋ฆฌ๊ฐ€ ํ”๋“ค๋ฆฌ๊ณ , ์‚ฌ์ด๋“œ์ดํŽ™ํŠธ๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•œ๋‹ค.
  • ๋กœ์ง์ด ์ง€์ €๋ถ„ํ•ด์ง

๐ŸŽฏ ์šฐ๋ฆฌ๊ฐ€ ์„ธ์šด ๋ชฉํ‘œ

  • ์กฐ๊ฑดยท์ •๋ ฌยท์ง‘๊ณ„๋Š” ๋”ฐ๋กœ๋”ฐ๋กœ
  • ํ•œ ๊ฐ€์ง€ ์ฑ…์ž„๋งŒ ๋งก๋„๋ก ์ชผ๊ฐœ๊ณ , ์–ด๋Œ‘ํ„ฐ์—์„œ ํผ์ฆ ๋งž์ถ”๋“ฏ ์กฐ๋ฆฝํ•œ๋‹ค.
  • ์ž…๋ ฅ์ด ์—†์œผ๋ฉด ์ž๋™ ๋ฌด์‹œ
  • null๋กœ ๊ฐ’์ด ์—†์œผ๋ฉด where ์ ˆ์—์„œ ๊ทธ๋ƒฅ ๋น ์ ธ์„œ, ๊น”๋”ํ•˜๊ฒŒ ํ•„ํ„ฐ ์กฐํ•ฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ์ฟผ๋ฆฌ ๋””ํ…Œ์ผ์€ ์–ด๋Œ‘ํ„ฐ์— ๋ชฐ์•„๋„ฃ๋Š”๋‹ค.
  • ์ฝ๊ธฐ ์ฟผ๋ฆฌ๋Š” ์ž์œ ๋กญ๊ฒŒ ์ตœ์ ํ™”
  • ํ•„์š”ํ•  ๋•Œ ์กฐ์ธยทํ”„๋กœ์ ์…˜ยทํŽ˜์ด์ง•์„ ๋งˆ์Œ๊ป ์จ๋„ ์ฝ”์–ด ๋กœ์ง์€ ํ”๋“ค๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.


๐Ÿ”„ 2) ํ•œ๋ˆˆ์— ๋ณด๋Š” ํ๋ฆ„

[Web Controller]
  โ†’ [UseCase (Port In)]
  	โ†’ [Service]
    	โ†’ [Port Out (์ฟผ๋ฆฌ ์ธํ„ฐํŽ˜์ด์Šค)]
      		โ†’ [Query Adapter (QueryDSL)]
         		โ”œโ”€ Predicate ์กฐ๋ฆฝ(ํ‚ค์›Œ๋“œ/ํ† ํ”ฝ/์†Œ์Šค/ํƒ€์ž…/์—ฐ๋„ ๋“ฑ)
         		โ”œโ”€ Sort ์กฐ๋ฆฝ(์ตœ์‹ /์˜ค๋ž˜๋œ/๋‹ค์šด๋กœ๋“œ/ํ™œ์šฉ์ˆœ)
         		โ”œโ”€ Score ๊ณ„์‚ฐ(๋‹ค์šด๋กœ๋“œยทํ”„๋กœ์ ํŠธ์ˆ˜ ๊ฐ€์ค‘์น˜ ๊ณ„์‚ฐ)
         		โ”œโ”€ ์กฐ์ธ/๊ทธ๋ฃน/ํŽ˜์ด์ง•
         		โ””โ”€ ์—”ํ‹ฐํ‹ฐ <-> ๋„๋ฉ”์ธ/DTO ๋งคํ•‘

์šด์˜ ๊ฐ€์‹œ์„ฑ: LoggerFactory.query().logQueryStart/End(...)๋กœ ์ฟผ๋ฆฌ ์‹œ์ž‘/์ข…๋ฃŒ๋ฅผ ํ‘œ์ค€ ๋กœ๊น…ํ•œ๋‹ค.



๐Ÿ—‚๏ธ 3) ํด๋” ๊ตฌ์„ฑ(ํ•ต์‹ฌ)

modules/dataset
 โ”œโ”€ adapter
 โ”‚   โ”œโ”€ jpa
 โ”‚   โ”‚   โ”œโ”€ entity/ QDataEntity ...
 โ”‚   โ”‚   โ””โ”€ mapper/ DataEntityMapper
 โ”‚   โ””โ”€ query
 โ”‚       โ”œโ”€ predicates/
 โ”‚       โ”‚   โ”œโ”€ DataFilterPredicate.java      // id/keyword/topicId/sourceId/typeId
 โ”‚       โ”‚   โ””โ”€ DataDatePredicate.java        // yearBetween
 โ”‚       โ”œโ”€ sort/
 โ”‚       โ”‚   โ”œโ”€ DataSortBuilder.java          // ์ •๋ ฌ ์˜ต์…˜ โ†’ OrderSpecifier[]
 โ”‚       โ”‚   โ””โ”€ DataPopularOrderBuilder.java  // ์ธ๊ธฐ ์ ์ˆ˜ ๊ณ„์‚ฐ์‹
 โ”‚       โ”œโ”€ ReadDataQueryDslAdapter.java      // ๋‹จ๊ฑด/์—ฐ๊ฒฐ/์ตœ๊ทผ/์ธ๊ธฐ/๊ทธ๋ฃน ์นด์šดํŠธ
 โ”‚       โ””โ”€ SearchDataQueryDslAdapter.java    // ๋ณตํ•ฉ ํ•„ํ„ฐ + ์ •๋ ฌ + ํŽ˜์ด์ง•
 โ”œโ”€ application
 โ”‚   โ”œโ”€ port/out/query/read/*, /search/*
 โ”‚   โ””โ”€ dto/request/search/FilteringDataRequest
 โ”‚             /response/support/DataWithProjectCountDto
 โ””โ”€ domain
     โ”œโ”€ model/Data
     โ””โ”€ enums/DataSortType (LATEST/OLDEST/DOWNLOAD/UTILIZE)


๐Ÿงฉ 4) ํ•ต์‹ฌ ๋นŒ๋”ฉ ๋ธ”๋ก(์ฝ”๋“œ ๋ฐœ์ทŒ)

4.1 Predicate โ€” ์กฐ๊ฑด์€ ๋ฉ”์„œ๋“œ๋กœ ์ชผ๊ฐœ๊ณ  null-๋ฌด์‹œ

public static BooleanExpression keywordContains(String keyword) {
    if (!StringUtils.hasText(keyword)) return null;
    return dataEntity.title.containsIgnoreCase(keyword)
        .or(dataEntity.description.containsIgnoreCase(keyword));
}

public static BooleanExpression yearBetween(Integer year) {
    if (year == null) return null;
    NumberTemplate<Integer> s = Expressions.numberTemplate(Integer.class, "year({0})", data.startDate);
    NumberTemplate<Integer> e = Expressions.numberTemplate(Integer.class, "year({0})", data.endDate);
    return s.loe(year).and(e.goe(year));
}

๋ฌด์—‡์„ ํ•˜๋‚˜์š”?

  • keywordContains๋Š” ์ œ๋ชฉ/์„ค๋ช… ์ปฌ๋Ÿผ์— ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ ๋ถ€๋ถ„ ๊ฒ€์ƒ‰์„ ๊ฑด๋‹ค.
  • yearBetween์€ ์ฃผ์–ด์ง„ ์—ฐ๋„๊ฐ€ startDate ~ endDate ์‚ฌ์ด์— ํฌํ•จ๋˜๋Š”์ง€๋ฅผ ํŒ๋‹จ

์™œ ์ด๋ ‡๊ฒŒ ์ชผ๊ฐœ๋‚˜์š”?

  • SRP(๋‹จ์ผ ์ฑ…์ž„): ์กฐ๊ฑด ํ•˜๋‚˜๋‹น ๋ฉ”์„œ๋“œ ํ•˜๋‚˜๋ฉด, ๋ณ€๊ฒฝ/์ถ”๊ฐ€/ํ…Œ์ŠคํŠธ๊ฐ€ ์‰ฝ๋‹ค.
  • null-๋ฌด์‹œ ํŒจํ„ด: ์ž…๋ ฅ์ด ์—†์œผ๋ฉด null์„ ๋ฐ˜ํ™˜ํ•ด์„œ where(a, b, null, d)์ฒ˜๋Ÿผ ์ž๋™์œผ๋กœ ์Šคํ‚ต๋œ๋‹ค( QueryDSL์ด null์€ ๋ฌด์‹œ ).

์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋‚˜์š”?

  • keywordContains: ๊ณต๋ฐฑ/๋นˆ ๋ฌธ์ž์—ด์€ ๋ฐ”๋กœ null ๋ฐ˜ํ™˜ โ†’ where ์ ˆ์—์„œ ์ œ์™ธ.
  • ์‹ค์ œ SQL์€ LOWER(title) like %keyword% OR LOWER(description) like %keyword% ํ˜•ํƒœ๊ฐ€ ๋œ๋‹ค.
  • yearBetween: DB ํ•จ์ˆ˜ year(date)๋กœ startDate โ‰ค year โ‰ค endDate๋ฅผ ๊ฒ€์‚ฌ.
  • ํ•œ ์ค„๋กœ ํ•ฉ์ณ ๊ฐ€๋…์„ฑ์„ ์ฑ™๊ธฐ๊ณ , ๋‹ค๋ฅธ ์กฐ๊ฑด๋“ค๊ณผ ์กฐ๋ฆฝ๋˜๋„๋ก BooleanExpression์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

๐Ÿงช ํ…Œ์ŠคํŠธ ํฌ์ธํŠธ:

  • keyword = null / "" / " "์ผ ๋•Œ null ๋ฐ˜ํ™˜๋˜๋Š”์ง€
  • year = null, ๋ฒ”์œ„ ๊ฒฝ๊ณ„(์˜ˆ: ๊ตฌ๊ฐ„ ์‹œ์ž‘/๋ ์—ฐ๋„) ํฌํ•จ ์—ฌ๋ถ€
  • ๋‹ค๊ตญ์–ด/๋Œ€์†Œ๋ฌธ์ž ์ผ€์ด์Šค

4.2 Sort & Score โ€” ์„ ์–ธ์  ์ •๋ ฌ / ๊ฐ€์ค‘์น˜ ์ ์ˆ˜

public static OrderSpecifier<?>[] fromSortOption(DataSortType sort, NumberPath<Long> projectCountPath) {
    QDataEntity data = QDataEntity.dataEntity;
    if (sort == null) return new OrderSpecifier[]{data.createdAt.desc()};
    return switch (sort) {
        case LATEST   -> new OrderSpecifier[]{data.createdAt.desc()};
        case OLDEST   -> new OrderSpecifier[]{data.createdAt.asc()};
        case DOWNLOAD -> new OrderSpecifier[]{data.downloadCount.desc()};
        case UTILIZE  -> new OrderSpecifier[]{projectCountPath.desc()};
    };
}

public static NumberExpression<Double> popularScore(QDataEntity data, NumberExpression<Long> projectCountExpr) {
    NumberExpression<Double> projectScore  = projectCountExpr.castToNum(Double.class).multiply(1.5);
    NumberExpression<Double> downloadScore = data.downloadCount.castToNum(Double.class).multiply(2.0);
    return downloadScore.add(projectScore);
}

๋ฌด์—‡์„ ํ•˜๋‚˜์š”?

  • fromSortOption์€ DataSortType์— ๋”ฐ๋ผ ์ •๋ ฌ ๊ธฐ์ค€์„ OrderSpecifier[]๋กœ ๋Œ๋ ค์ค€๋‹ค.
    (์ตœ์‹ /์˜ค๋ž˜๋œ/๋‹ค์šด๋กœ๋“œ/ํ™œ์šฉ์ˆœ)
  • popularScore๋Š” ๋‹ค์šด๋กœ๋“œร—2.0 + ํ”„๋กœ์ ํŠธ์ˆ˜ร—1.5๋กœ ์ธ๊ธฐ ์ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ์ธ๊ธฐ์žˆ๋Š” ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก์„ ์กฐํšŒํ•œ๋‹ค.

์™œ ์ด๋ ‡๊ฒŒ ์ชผ๊ฐœ๋‚˜์š”?

  • ์„ ์–ธ์  ์ œ์–ด: ์ •๋ ฌ ์ •์ฑ…์„ enum์— ๋งตํ•‘ํ•˜๋ฉด, โ€œ์–ด๋–ค ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•˜๋Š”๊ฐ€โ€๊ฐ€ ํ•œ๋ˆˆ์— ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
  • ์ƒˆ ๊ธฐ์ค€์ด ์ƒ๊ฒจ๋„ ์Šค์œ„์น˜ ํ•œ ์นธ ์ถ”๊ฐ€๋กœ ๋.
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋ถ„๋ฆฌ: ์ธ๊ธฐ ์ ์ˆ˜ ๊ณต์‹์„ ํ•œ ํŒŒ์ผ์— ๋ชจ์•„๋‘๋ฉด, ๊ฐ€์ค‘์น˜๋งŒ ๋ฐ”๊ฟ”๋„ ๋‹ค๋ฅธ ์ฟผ๋ฆฌ๋ฅผ ์•ˆ ๊ฑด๋“œ๋ ค๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

4.3 Search ์–ด๋Œ‘ํ„ฐ โ€” ๋ณตํ•ฉ ํ•„ํ„ฐ + ์ •๋ ฌ + ํŽ˜์ด์ง•

@Override
public Page<DataWithProjectCountDto> searchByFilters(FilteringDataRequest request,
                                                    Pageable pageable, DataSortType sortType) {
    NumberPath<Long> projectCountPath = Expressions.numberPath(Long.class, "projectCount");

    List<Tuple> tuples = queryFactory
        .select(data, projectData.id.count().as(projectCountPath))
        .from(data)
        .leftJoin(projectData).on(projectData.dataId.eq(data.id))
        .leftJoin(data.metadata).fetchJoin()
        .where(
            DataFilterPredicate.keywordContains(request.keyword()),
            DataFilterPredicate.topicIdEq(request.topicId()),
            DataFilterPredicate.dataSourceIdEq(request.dataSourceId()),
            DataFilterPredicate.dataTypeIdEq(request.dataTypeId()),
            DataDatePredicate.yearBetween(request.year())
        )
        .groupBy(data.id)
        .orderBy(DataSortBuilder.fromSortOption(sortType, projectCountPath))
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize())
        .fetch();

    List<DataWithProjectCountDto> contents = tuples.stream()
        .map(t -> new DataWithProjectCountDto(DataEntityMapper.toDomain(t.get(data)),
                                              t.get(projectCountPath)))
        .toList();

    long total = Optional.ofNullable(
        queryFactory.select(data.id.countDistinct())
            .from(data)
            .leftJoin(projectData).on(projectData.dataId.eq(data.id))
            .where(
                DataFilterPredicate.keywordContains(request.keyword()),
                DataFilterPredicate.topicIdEq(request.topicId()),
                DataFilterPredicate.dataSourceIdEq(request.dataSourceId()),
                DataFilterPredicate.dataTypeIdEq(request.dataTypeId()),
                DataDatePredicate.yearBetween(request.year())
            )
            .fetchOne()
    ).orElse(0L);

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

๋ฌด์—‡์„ ํ•˜๋‚˜์š”?

  • ์—ฌ๋Ÿฌ ํ•„ํ„ฐ(ํ‚ค์›Œ๋“œ/ํ† ํ”ฝ/์†Œ์Šค/ํƒ€์ž…/์—ฐ๋„)๋ฅผ ์กฐ๋ฆฝํ•˜๊ณ ,
    ํ”„๋กœ์ ํŠธ ์—ฐ๊ฒฐ์ˆ˜(projectCount)๋ฅผ ์ง‘๊ณ„ํ•œ ๋’ค,
    ์ •๋ ฌ + ํŽ˜์ด์ง•์„ ์ ์šฉํ•ด ํŽ˜์ด์ง€ ์‘๋‹ต์„ ๋งŒ๋“ค์–ด์ค€๋‹ค.

์™œ ์ด๋ ‡๊ฒŒ ๊ตฌ์„ฑํ–ˆ๋‚˜์š”?

  • ์ฝ๊ธฐ ์ „์šฉ ์ตœ์ ํ™”: ์กฐ์ธ/๊ทธ๋ฃน/ํ”„๋กœ์ ์…˜์„ ์–ด๋Œ‘ํ„ฐ ์ˆ˜์ค€์—์„œ ์ž์œ ๋กญ๊ฒŒ ์“ฐ๊ณ ,
    ๋„๋ฉ”์ธ/์œ ์Šค์ผ€์ด์Šค๋Š” ๋‹จ์ˆœํ•œ ๊ณ„์•ฝ๋งŒ ์œ ์ง€ํ•œ๋‹ค.
  • ์นด์šดํŠธ ๋ถ„๋ฆฌ: contents ์กฐํšŒ์™€ total ์นด์šดํŠธ๋ฅผ ๋ถ„๋ฆฌํ•˜๋ฉด,
    ๋ฐ์ดํ„ฐ ์–‘์ด ์ปค์ ธ๋„ ํ™•์žฅ์„ฑ์ด ์ข‹๋‹ค(ํ•œ ๋ฒˆ์— fetchResults()๋Š” ๋น„์ถ”์ฒœ/Deprecated).

์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋‚˜์š”?

  • projectCountPath๋ผ๋Š” ํŒŒ์ƒ ์ปฌ๋Ÿผ(NumberPath)์„ ์„ ์–ธํ•ด ํ”„๋กœ์ ํŠธ ํ™œ์šฉ ์ˆ˜count() ๊ฒฐ๊ณผ๋ฅผ ๋‹ด์„ ๋ณ„์นญ์„ ์ค€๋น„.
  • select(data, projectData.id.count().as(projectCountPath))๋กœ
    ์—”ํ‹ฐํ‹ฐ + ํ•ด๋‹น ๋ฐ์ดํ„ฐ์…‹์— ์—ฐ๊ฒฐ๋œ ํ”„๋กœ์ ํŠธ ์ˆ˜๋ฅผ ํ•œ ๋ฒˆ์— ์กฐํšŒ.
  • leftJoin(projectData)๋กœ ์—ฐ๊ฒฐ ์—ฌ๋ถ€๊ฐ€ ์—†์–ด๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚˜์˜ค๊ฒŒ ํ•˜๊ณ ,
    leftJoin(data.metadata).fetchJoin()์œผ๋กœ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ N+1 ๋ฐฉ์ง€(1:1 ๋งคํ•‘ ๊ด€๊ณ„).
  • where(...)์— Predicate ๋ฉ”์„œ๋“œ๋“ค์„ ๋‚˜์—ด(์ž…๋ ฅ ์—†์œผ๋ฉด null โ†’ ์ž๋™ ์Šคํ‚ต).
  • groupBy(data.id)๋กœ ๋ฐ์ดํ„ฐ์…‹๋ณ„๋กœ ์—ฐ๊ฒฐ์ˆ˜ ์ง‘๊ณ„.
  • orderBy(DataSortBuilder.fromSortOption(sortType, projectCountPath))๋กœ
    ์„ ์–ธ์  ์ •๋ ฌ์„ ์ ์šฉ.
  • offset/limit์œผ๋กœ ํŽ˜์ด์ง•.
  • ๊ฒฐ๊ณผ Tuple์„ DataEntityMapper๋กœ ๋„๋ฉ”์ธ ๋ณ€ํ™˜ํ•ด DTO์— ๋‹ด์Œ.
  • total์€ ๋ณ„๋„ ์ฟผ๋ฆฌ๋กœ countDistinct()๋งŒ ์ˆ˜ํ–‰(๋™์ผ where, ์กฐ์ธ์€ ์ตœ์†Œํ™” ๊ฐ€๋Šฅ).

ํ˜„์—…์—์„œ์˜ ํŒ/์ฃผ์˜์  (์ถ”ํ›„ ํ™•์žฅ ๊ณ ๋ ค)

  • โš–๏ธ fetch-join + ํŽ˜์ด์ง•: ์ปฌ๋ ‰์…˜(@OneToMany)์„ fetch-joinํ•˜๋ฉด ํŽ˜์ด์ง•์ด ๊ผฌ์ผ ์ˆ˜ ์žˆ๋‹ค.
  • ์ปฌ๋ ‰์…˜์ด๋ฉด ๋ฐฐ์น˜/2-step ์กฐํšŒ๋กœ ์ „๋žต์„ ์ถ”ํ›„ ๊ณ ๋ คํ•ด๋ณธ๋‹ค.
  • ๐Ÿงฎ countDistinct ๋น„์šฉ: ํฐ ํ…Œ์ด๋ธ”์—์„œ๋Š” ๋น„์‹ธ๋‹ค.
  • ์บ์‹œ/ํ”„๋ฆฌ์นด์šดํŠธ/๊ทผ์‹ค์‹œ๊ฐ„ ์ง‘๊ณ„ ํ…Œ์ด๋ธ”๋กœ ๋ณด์™„ํ•˜๊ฑฐ๋‚˜, ํ•„ํ„ฐ๊ฐ€ ๋‹จ์ˆœํ•˜๋ฉด ์นด์šดํŠธ ์ฟผ๋ฆฌ๋ฅผ ๊ฐ„์†Œํ™”ํ•œ๋‹ค(๋ถˆํ•„์š”ํ•œ ์กฐ์ธ ์ œ๊ฑฐ).
  • ๐Ÿš€ ๋Œ€๋Ÿ‰ ํŽ˜์ด์ง€ ํƒ์ƒ‰: ๊นŠ์€ ํŽ˜์ด์ง€๋กœ ๊ฐˆ์ˆ˜๋ก offset ๋น„์šฉ์ด ํฌ๋‹ค.
    ์ถ”ํ›„ ํ‚ค์…‹ ํŽ˜์ด์ง•(seek)์œผ๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋„๋ก ์ •๋ ฌ ์ปฌ๋Ÿผ(์˜ˆ: createdAt, id)์„ ์ค€๋น„ํ•ด๋‘๋ฉด ์ข‹๋‹ค.

๐Ÿงช ํ…Œ์ŠคํŠธ ํฌ์ธํŠธ:

  • ๊ฐ ํ•„ํ„ฐ ์กฐํ•ฉ(์ž…๋ ฅ/๋ฏธ์ž…๋ ฅ)์—์„œ ๊ฒฐ๊ณผ ๊ฑด์ˆ˜/ํŽ˜์ด์ง€ ์ˆ˜๊ฐ€ ๊ธฐ๋Œ€๋Œ€๋กœ ๋‚˜์˜ค๋Š”์ง€
  • ์ •๋ ฌ ์˜ต์…˜๋ณ„ ์ƒ์œ„ ๋ช‡ ๊ฑด์˜ ์ •๋ ฌ ์ผ์น˜ ์—ฌ๋ถ€
  • ์—ฐ๊ฒฐ์ˆ˜๊ฐ€ ์—†๋Š” ๋ฐ์ดํ„ฐ๋„ ๋น ์ง€์ง€ ์•Š๋Š”์ง€(left join ํ™•์ธ)


โœจ 5) ํšจ๊ณผ

  1. ๋ณ€๊ฒฝ ๋‚ด์„ฑ: โ€œ์กฐ๊ฑด ํ•˜๋‚˜ ๋ฐ”๋€Œ๋ฉด ๊ทธ ํŒŒ์ผ๋งŒ ๊ณ ์นœ๋‹ค.โ€
  2. ์œ ์ง€๋ณด์ˆ˜์„ฑ: ๊ฑฐ๋Œ€ ์ฟผ๋ฆฌ ๋Œ€์‹  ์ž‘์€ ๋ชจ๋“ˆ์„ ๋ฆฌ๋ทฐ/๊ต์ฒด.
  3. ํ…Œ์ŠคํŠธ์„ฑ: Predicate/Sort๋Š” ์ˆœ์ˆ˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ ๊ฒ€์ฆ ๊ฐ€๋Šฅ.
  4. ์ „ํ™˜ ์œ ์—ฐ์„ฑ: JPAโ†’MyBatis ์ „ํ™˜ ์‹œ์—๋„ Port ๊ณ„์•ฝ/DTO/๋„๋ฉ”์ธ ๋ชจ๋ธ์€ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ.


๐Ÿงฏ 6) ์ฃผ์˜์‚ฌํ•ญ - ์ถ”ํ›„ ๊ณ ๋ ค์‚ฌํ•ญ

  • ๐Ÿ”Ž ํ•จ์ˆ˜ ๊ธฐ๋ฐ˜ ํ‘œํ˜„์‹์€ ์ธ๋ฑ์Šค ํ™œ์šฉ์„ ๋ฐฉํ•ด โ†’ ๋ฒ”์œ„ ๋น„๊ต/์ƒ์„ฑ ์ปฌ๋Ÿผ ์ธ๋ฑ์Šค ํ™œ์šฉ.
  • โš–๏ธ fetch join + paging: ์ปฌ๋ ‰์…˜ fetch-join์€ ํŽ˜์ด์ง• ์ œ์•ฝ โ†’ ๋ฐฐ์น˜ ์‚ฌ์ด์ฆˆ/2-step ์กฐํšŒ/์ „์šฉ ์ฝ๊ธฐ ๋ชจ๋ธ ๋ถ„๋ฆฌ.
  • ๐Ÿงฎ countDistinct ๋น„์šฉ: ๋Œ€ํ˜• ๋ฐ์ดํ„ฐ์…‹์—์„œ๋Š” ๋น„์Œˆ โ†’ ์บ์‹œ/ํ”„๋ฆฌ์นด์šดํŠธ/๊ทผ์‹ค์‹œ๊ฐ„ ์ง‘๊ณ„ ๊ณ ๋ ค.
  • ๐Ÿงฑ ๊ฐ€์ค‘์น˜ ๋ณ€๊ฒฝ: ์ธ๊ธฐ ์ ์ˆ˜ ๊ฐ€์ค‘์น˜๋Š” ์„ค์ •๊ฐ’/์ „๋žต ํŒจํ„ด ๋ถ„๋ฆฌ๋กœ ์šด์˜ ๋ณ€๊ฒฝ ์šฉ์ด.


๐Ÿง‘โ€๐Ÿซ 7) ๊ฒฐ๋ก 

์ฟผ๋ฆฌ๋ฅผ ์ชผ๊ฐœ์„œ ์กฐ๋ฆฝํ•˜๋ฉด, ๊ธฐ๋Šฅ์ด ๋Š˜์–ด๋‚˜๋„ ๋ณต์žก๋„๋ฅผ ํ†ต์ œํ•  ์ˆ˜ ์žˆ๋‹ค.
ํ•ต์‹ฌ์€ ์ž‘๊ฒŒ ์ถ”๊ฐ€ ยท ์•ˆ์ „ํ•˜๊ฒŒ ๋ณ€๊ฒฝ ยท ์‰ฝ๊ฒŒ ํ…Œ์ŠคํŠธ๋‹ค.
ํŒ€ ์ปจ๋ฒค์…˜์œผ๋กœ ์ •์ฐฉ์‹œํ‚ค๋ฉด โ€œํ•„ํ„ฐ ํ•˜๋‚˜ ์ถ”๊ฐ€ํ–ˆ๋Š”๋ฐ ์™œ ์ „์ฒด๊ฐ€ ๊นจ์ง€์ฃ ?โ€๋ผ๋Š” ์งˆ๋ฌธ์ด ์‚ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์–ด ์„ค๊ณ„ ์ƒ ์œ ๋ฆฌํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค. ๐Ÿš€

์ถ”ํ›„ ๋” ๊ณ ๋ คํ•ด์•ผ ํ•  ์š”์†Œ๋“ค์ด ์žˆ๊ธฐ์— ๊ณ„์† ๋ฆฌํŒฉํ† ๋งํ•˜์—ฌ ๊ฐœ์„ ํ•ด ๋‚˜๊ฐˆ ๊ณ„ํš์„ ํ•ด์•ผ๊ฒ ๋‹ค.

profile
๋งค์ผ ๋งค์ผ ์„ฑ์žฅํ•˜๊ธฐ

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