Skip to content

[#164][FEATURE] 그룹 해시태그 조회/검색#171

Open
unikal1 wants to merge 16 commits intodevfrom
feat/group-hashtag
Open

[#164][FEATURE] 그룹 해시태그 조회/검색#171
unikal1 wants to merge 16 commits intodevfrom
feat/group-hashtag

Conversation

@unikal1
Copy link
Contributor

@unikal1 unikal1 commented Jan 14, 2026

close [FEATURE] 그룹 해시태그 조회/검색 #164

✨ 구현 기능 명세

1. 그룹 해시태그 저장/정규화

그룹 생성 시 입력된 해시태그는 StringUtils.normalize로 정규화하여 DB에는 검색 친화적인 형태로 저장하고, 사용자에게 보여줄 문자열은 displayTag로 분리해 보관했습니다.

  • 정규화 규칙: 공백 → _, # 제거, 특수문자 제거, 소문자 변환, 연속 _ 축약, 앞뒤 _ 제거
  • 이미 존재하는 해시태그는 usedCount를 bulk 증가시키고, 신규 태그는 생성한 뒤 GroupHashTag 매핑을 저장합니다.

예시

  • 입력: "#Spring Boot", "Spring__Boot", " spring-boot "
  • normalize 결과: "spring_boot" (모두 동일)
  • 저장 구조(개념)
HashTag(tag="spring_boot", usedCount=+1)
GroupHashTag(displayTag="#Spring Boot", hashTag=HashTag("spring_boot"))

사용자 노출은 항상 displayTag(입력 원본)에 맞춰 반환됩니다.

2. 그룹 목록/상세 조회 응답에 hashTags 포함

GroupHashTagWorker를 통해 groupId별 해시태그 목록을 조회하고, 응답 DTO에 hashTags를 추가했습니다.

  • 목록: GetGroupsRes.hashTags
  • 상세: GetGroupDetailRes.hashTags

예시

{
  "groupId": 12,
  "name": "알고리즘 스터디",
  "hashTags": ["#DP", "#그래프", "#Spring Boot"]
}

3. 그룹 검색 API (해시태그/태그/이름)

GET /groups/search에서 tag/name/hashTag 중 하나만 허용되도록 제약을 추가했고, open/approval 필터 및 커서 기반 페이징을 지원했습니다.

  • 기본값: open=true, approval=true
  • 정렬: POPULAR(회원 수), NEW(생성일 desc), OLD(생성일 asc)

요청 예시

GET /groups/search?hashTag=Spring%20Boot&open=true&approval=false&size=5&sort=POPULAR

4. 검색 구현(QueryDSL) - hashTag는 exists 서브쿼리

N:M 조인 중복 row를 피하기 위해 hashTag 검색은 exists 서브쿼리로 필터링했습니다.

코드 예시

private BooleanExpression getHashTagByGroup(String rawHashTag) {
    String normHashTag = stringUtils.normalize(rawHashTag);
    return JPAExpressions.selectOne()
            .from(groupHashTag)
            .where(groupHashTag.group.id.eq(group.id),
                   groupHashTag.hashTag.tag.contains(normHashTag))
            .exists();
}

5. 커서 페이지네이션 + deterministic ordering

size + 1 조회로 hasNext를 판단하고, 정렬 필드 + id tie-breaker로 결과 누락/중복을 방지했습니다.

  • POPULAR: totalMember desc, id desc
  • NEW: createdDate desc, id desc
  • OLD: createdDate asc, id asc

Cursor.value 의미

  • POPULAR: totalMember
  • NEW/OLD: createdDate

예시 (POPULAR)

WHERE (totalMember < :value)
   OR (totalMember = :value AND id < :cursorId)
ORDER BY totalMember DESC, id DESC

✅ PR Point

  • 해시태그 검색을 exists 서브쿼리로 처리해 N:M 조인 중복을 제거하고, "해시태그 존재 여부"만 빠르게 확인하도록 구성했습니다.
  • 커서 정렬 기준에 id tie-breaker를 추가해 동일 정렬값에서 페이지 이동 시 결과 누락/중복을 방지했습니다.
  • 저장/표시 분리(정규화 tag vs displayTag)로 검색 성능과 사용자 노출 형태를 동시에 만족시키도록 설계했습니다.

어려웠던 점

  • tag/name/hashTag 중 하나만 허용하는 제약을 유지하면서도, 필터(open/approval) + 커서 + 정렬까지 한 쿼리에 조합하는 로직 구성이 까다로웠습니다.
  • 해시태그 정규화 규칙을 검색/저장/표시 전 영역에 적용하면서, 사용자 입력 원본을 그대로 노출할 수 있도록 데이터 구조를 분리한 점이 복잡했습니다.

- 기존에는 enum 을 펼쳐서 모든 내부 필드 값에 대해 대조하여 판별하였지만, 현재는 string-sortType 을 매핑하여 저장.

- 중복된 string 인 경우 예외 발생

Ref: #164
- 같은 sortType 에 대해, id 기반 정렬이 가능한 메서드 추가
- cursor 에서 최근 데이터에 대해, id 뿐만 아니라 기준이 되는 값도 받도록 설정

Ref: #164
- 각 조건에 따라 쿼리문을 조립하도록 설정

Ref: #164
@gemini-code-assist
Copy link

Summary of Changes

Hello @unikal1, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 그룹 관리 기능에 중요한 개선 사항을 도입합니다. 주요 변경 사항은 해시태그 및 기타 조건으로 그룹을 검색할 수 있는 새로운 기능을 추가하고, 그룹 상세 및 목록 조회 시 해시태그 정보를 함께 제공하는 것입니다. 또한, 리포지토리 패키지 구조를 재정비하여 코드의 모듈성을 높였으며, 커서 기반 페이지네이션 시스템을 확장하여 더 다양한 정렬 기준을 지원하도록 개선했습니다.

Highlights

  • 그룹 검색 기능 추가: 해시태그, 태그, 그룹 이름 등 다양한 조건으로 그룹을 검색할 수 있는 새로운 API 엔드포인트 /searchGroupController에 추가되었습니다. 이 기능은 커서 기반 페이지네이션을 지원하며, 그룹의 공개 여부 및 승인 필요 여부로 필터링할 수 있습니다.
  • 리포지토리 패키지 구조 개선: 기존 dao 패키지 내의 여러 리포지토리들이 groupEntryRepository, groupMemberRepository, groupRepository와 같은 하위 패키지로 재구성되어 도메인별 응집도를 높이고 코드 가독성을 향상시켰습니다.
  • 커서 기반 페이지네이션 시스템 확장: CursorDefault 어노테이션에 sortTypevalue 필드가 추가되어, ID 외의 다른 필드(예: totalMember, createdDate)를 기준으로 정렬하는 커서 기반 페이지네이션을 더욱 유연하게 지원하도록 개선되었습니다. AbstractPagingRepository는 보조 정렬 기준으로 ID를 사용하여 안정적인 페이지네이션을 보장합니다.
  • 그룹 상세 및 목록 응답에 해시태그 포함: GetGroupDetailResGetGroupsRes DTO에 그룹과 연관된 해시태그 목록이 포함되도록 수정되었습니다. 이를 위해 GroupHashTagRepository에 새로운 조회 메서드가 추가되었고, GroupService에서 해당 정보를 가져와 응답에 포함하도록 로직이 업데이트되었습니다.
  • QueryDSL 버전 업데이트 및 한글 지원: build.gradle에서 QueryDSL 버전이 5.0.0에서 5.1.0으로 업데이트되었으며, StringUtils의 정규화 패턴에 한글(\p{IsHangul})이 포함되어 한글 해시태그 및 검색어 처리가 가능해졌습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 Pull Request는 그룹 검색 기능과 커서 기반 페이지네이션을 도입하는 변경 사항을 담고 있습니다. 전반적으로 페이지네이션 로직과 Argument Resolver 개선 등 좋은 변경 사항이 많습니다. 하지만 Querydsl 구현에서 성능 저하를 유발할 수 있는 심각한 문제가 발견되었으며, 코드 명확성과 유지보수성을 높일 수 있는 몇 가지 개선점이 보입니다. 오해의 소지가 있는 메서드 이름 변경, Javadoc 템플릿 내용 채우기, 일부 로직을 더 간결하게 리팩토링하는 것에 대한 구체적인 의견을 남겼습니다.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e9be1aca8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@unikal1 unikal1 self-assigned this Jan 16, 2026
@unikal1 unikal1 requested a review from sleepyhoon January 20, 2026 07:21
@unikal1
Copy link
Contributor Author

unikal1 commented Jan 20, 2026

@codex review

@sleepyhoon
Copy link
Contributor

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

안녕하세요. 그룹 해시태그 조회 및 검색 기능 추가 PR에 대한 리뷰입니다. 전반적으로 커서 기반 페이징, QueryDSL을 활용한 동적 검색, exists 서브쿼리를 이용한 성능 최적화 등 새로운 기능 구현이 매우 잘 이루어졌습니다. 특히 AbstractPagingRepositorySortTypeResolver 리팩토링을 통해 코드의 타입 안정성과 재사용성을 높인 점이 인상적입니다. 몇 가지 API 안정성 및 필터 로직 정확성 개선을 위한 제안 사항을 리뷰 댓글로 남겼으니 확인 부탁드립니다. 좋은 코드를 작성해주셔서 감사합니다!

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 39ff738bab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

- 기존 로직에서 null 인 경우 필터맅 X로 변경

Ref: #164
- GroupSortType 에 함수형 인터페이스를 통해 parser 메서드를 추가하고 worker 에서 이를 통해 파싱 가능 유무 검증

Ref: #164
# Conflicts:
#	src/asciidoc/api/group.adoc
@unikal1 unikal1 closed this Jan 23, 2026
@unikal1 unikal1 deleted the feat/group-hashtag branch January 23, 2026 02:48
@unikal1 unikal1 restored the feat/group-hashtag branch January 23, 2026 02:49
@unikal1 unikal1 reopened this Jan 23, 2026
Copy link
Contributor

@sleepyhoon sleepyhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1차 리뷰 입니다. AbstractPagingRepository의 경우 이해도가 부족해 리뷰하지 못했습니다. 기존 방식과 어떤 차이점이 있는지 조금더 설명해주세요.

Comment on lines +71 to +78
@GetMapping("/search")
public ResponseEntity<CursorResponse<GetGroupsRes>> searchGroups(
@CursorDefault(sortType = GroupSortType.class, cursor = 0, size = 5, sort = "POPULAR") Cursor cursor,
@RequestParam(required = false, name = "hashTag") String hashTag,
@RequestParam(required = false, name = "tag") String tag,
@RequestParam(required = false, name = "name") String name,
@RequestParam(required = false, name = "open") Boolean open,
@RequestParam(required = false, name = "approval") Boolean approval) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3]
필드수가 많은데 hashTag, tag, name, open, approval 5개 필드를 포함한 dto로 필드를 받는 것은 어떤가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

적용해보겠습니다.

Comment on lines +124 to +131
try {
dto.validate();
} catch (IllegalArgumentException e) {
throw new GroupException(
GroupErrorCode.GROUP_SEARCH_FAIL,
e.getMessage(),
"[GroupServiceImpl#search] validate dto fail : " + e.getMessage());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2]
validate 메서드를 통해 유효성 검증하는 방식보다, @notblank, @NotNull 로 dto 안에서 유효성 검증을 하는 것은 어떤가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

실제 내부 validate 구현을 보시면 Validator 기반의 어노테이션으로 검증이 불가능함을 알 수 있습니다. 해당 검증 로직은 hashtag/tag/title 중 2개 이상의 값이 들어오는 경우에 대한 필터링입니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제 말을 잘못했네요. 주된 의미는 dto 안에서 유효성 검증을 하는건 어떨까였습니다. 예를 들어 생성자 내에서 validate 로직을 수행하면 서비스 코드에서 검증할 필요가 없으니 더 좋다고 생각했습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그러면 앞서 이야기하셨던, 입력을 dto 로 받자는 거랑 연관하여 같이 해보겠습니다.

Comment on lines +32 to +62
@SuppressWarnings({"rawtypes", "unchecked"})
protected <E> OrderSpecifier<?> getOrderSpecifier(EntityPath<E> root, SortType sortType) {
String field = sortType.getField();
Sort.Direction direction = sortType.getDirection();

EntityMetadata metadata = getEntityMetadata(entityClass);
EntityMetadata metadata = getEntityMetadata(root.getType());
Class<?> sortFieldType = metadata.getFieldType(field);
CACHE.put(entityClass, metadata);

PathBuilder<T> path = new PathBuilder<>(entityClass, metadata.getEntityName());
Order order = direction == Sort.Direction.ASC ? Order.ASC : Order.DESC;
// alias를 문자열로 만들지 말고 root의 metadata를 그대로 사용
PathMetadata rootMetadata = root.getMetadata();
PathBuilder<E> path = new PathBuilder<>((Class<E>) root.getType(), rootMetadata);

Order order = direction == Sort.Direction.ASC ? Order.ASC : Order.DESC;
return new OrderSpecifier<>(order, (Expression<? extends Comparable>) path.get(field, sortFieldType));
}

private EntityMetadata getEntityMetadata(Class<T> entityClass) {
if (CACHE.containsKey(entityClass)) return CACHE.get(entityClass);
@SuppressWarnings({"rawtypes", "unchecked"})
protected <E> OrderSpecifier<?>[] getOrderSpecifierWithId(EntityPath<E> root, SortType type) {
OrderSpecifier<?> primary = getOrderSpecifier(root, type);

Sort.Direction direction = type.getDirection();
Order order = direction == Sort.Direction.ASC ? Order.ASC : Order.DESC;

EntityMetadata metadata = getEntityMetadata(root.getType());
PathBuilder<E> path = new PathBuilder<>((Class<E>) root.getType(), root.getMetadata());

// querydsl 에서 필요한 건 Class 명 자체가 아니라 QEntity 의 static final 변수명
// ex) QGroup.group => "group"
String className = entityClass.getSimpleName();
String entityName = Character.toLowerCase(className.charAt(0)) + className.substring(1);
return CACHE.put(entityClass, new EntityMetadata(entityClass, entityName));
OrderSpecifier<?> secondary = new OrderSpecifier<>(order, (Expression<? extends Comparable>)
path.get(metadata.getIdFieldName(), metadata.getIdFieldType()));

return new OrderSpecifier<?>[] {primary, secondary};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1]
추가 설명을 부탁드립니다. 리뷰가 불가능합니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 이건 저도 이해하는데 오래 걸리긴 했습니다(원본이 제가 작성한게 아니라서요) 시간 되실 때 디스코드로 직접 이야기 하는 편이 빠를 것 같긴 합니다. 그럼에도 기본적인 것만 말해보겠습니다.

일단 AbstractPageRepository 는 오로지 OrderSpecifier 를 만드는데 초점을 두고 있습니다. 즉, 어떤 방법으로 정렬할지에 대한 기준을 정의하는 추상 클래스입니다. 그렇다면 단순히 정렬일 뿐인데 왜 이렇게 복잡한 로직이 필요하냐? 라는 의문이 들 수 있습니다. 그 이유는 "문자열"기반의 정렬 기준을 실제 엔티티의 필드로 치환하기 위함입니다.

이렇게 말하면 체감이 잘 안될텐데 한가지 예를 들어보겠습니다.
GroupSortType 에는 POPULAR 이 있습니다. 여기에 정의된 String field 값은 totalMember 입니다. 그리고 이 이름은 실제 group 엔티티의 필드이죠.

다만 문제는, 이런 "문자열"을 실제 엔티티의 "필드의 타입으로 바꾸는 과정은 그리 호락호락 하지 않다는 것입니다. 리플렉션을 사용하는 것은 당연하고요, 매 요청마다 리플렉션이 일어나면 좀 번거로우니 캐싱을 사용하여 엔티티의 메타데이터를 메모리에 저장하는 과정까지도 추가할 수 있겠죠.

기본적으로 queryDSL 은 엔티티에 대해 Q 타입의 객체를 생성합니다. 그리고 해당 클래스에서 입력되는 class 는 이러한 Q 타입이죠. 이러한 Q 타입을 받는 entityPath 는 getType 메서드를 통해 원본 엔티티를 뽑을 수 있습니다.

내부 클래스인 EntityMetaData 는 이러한 엔티티 객체에 대해 다음 정보를 추출 및 기록합니다.

  1. 해당 엔티티의 이름
  2. 해당 엔티티의 아이디 이름 및 타입
  3. 해당 엔티티의 필드 이름 및 타입(여러 개)

이후 해당하는 EntityMetaDataCACHE 에 저장하죠. 이는 getEntityMetaData()메서드에서 관리합니다.

입력된 타입에 대해 -> 캐시에서 검색 -> 있으면 있는걸 반환 -> 없으면 새로 생성해서 반환하고 캐시에 저장

그리고 getOrderSpecifierEntityPathSortType 을 파라미터로 받습니다.
EntityPath 는 쉽게 말해 Q 타입 엔티티의 조상 클래스이며 SortType 은 Cursor 에서 지정한 구현체가 오겠죠.

이후 해당 EntityPath(root) 의 메타데이터를 가져옵니다. 가져온 메타데이터에 대해, SortType 에 명시된 String field 를 사용해 EntityMetafieldClasses 를 조회하는 getFieldType 메서드를 호출하여, 실제 엔티티의 필드를 가져오게 됩니다.

여기까지가 앞서 말한 "문자열"로 주어진 필드의 타입을 구하는 과정입니다.

OrderSpecifier 를 구성할 때는, 정렬 방향(Order), PathBuilder 를 받고 PathBuilder 는 정렬 기준 대상 필드의 "필드 이름 - 문자열" 및 "필드 타입"이 필요합니다. 이러한 필드 타입을 얻기 위해 앞서 과정을 수행한 것입니다.

getOrdreSpecifierWithId 의 경우 orderSpecifier 를 2개 생성합니다. 왜 그런지에 대해, 아래 Cursor.java 에 대해 설명할 때 같이 말해보겠습니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다. 관련 주석을 간단하게 작성해두면 좋겠습니다.

* @since 2025-06-05
*/
public record Cursor(long cursor, int size, SortType sort) {
public record Cursor(Long cursor, String value, int size, SortType sort) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2]
String value의 추가 이유가 무엇인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정렬 기준이 POPULAR, 그러니까 group 엔티티의 totalMember 라 해보겠습니다.

클라이언트는 가장 인기있는 순으로 데이터를 요청했고, 그럼에 따라 총 멤버 수가 높은 순으로 정렬이 되어 왔습니다. 일단 한 페이지에 5개만 왔다고 해보죠.
(totalMember == ttm)

groupA / ttm = 30
groupB / ttm = 20
groupC / ttm = 20
groupD / ttm = 15
groupE / ttm = 15

근데 사실 ttm = 15 인 친구가 2개가 아닌 5개 였습니다.
groupF / ttm = 15
groupG / ttm = 15
groupH / ttm = 15

이런 식으로요.

ttm 으로 조회를 할 때, 항상 저렇게 순서대로 오면 상관이 없겠지만 어느날은 첫 페이지에 groupD / groupE 가 오고, 어느 날은 groupG, groupD 가 올 수도 있습니다. 조건만 맞으면 뒤죽박죽이라는거죠.

이거는 다음 페이지에서도 마찬가지인데, 기존에 cursor 만 존재했을 때는 groupE 의 id(5) 를 건내줬을 겁니다. 다음 페이지를 해당 값으로 요청 시 db 입장에서는 id = 5 보다 큰 친구들에 대해 ttm 에 대해 정렬하여 반환하겠죠.

근데 첫번째 페이지에 사실 groupD(id = 4) 가 아닌 groupF(id = 6) 이 들어갔다는 겁니다.

혹은, groupG, groupH 가 들어가느라 groupH 에 대한 id 로 다음 페이지 요청을 보내어 C, D, E, F 가 전부 씹힐 수도 있고요.

따라서 이러한 문제를 보안하기 위해 ttm 과 id 를 같이 건내주도록 하여 애초에 정렬을 두가지 기준으로 하고, 데이터 역시도 해당 값을 기준으로 가져오는 것이죠.

결론적으로 정렬 기준 필드와 value 가 같은 경우, id 에 대해 2차적으로 정렬되어 있을 테니 해당 id 보다 큰 값만 가져오기 위함입니다.

WHERE (g.totalMember < :total) OR (g.totalMember = :total AND g.id < :cursorId)

이런 식으로요

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

들어주신 예시를 이용하자면 cursor = totalMember 이고, value = id 인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아니요 그 반대입니다. cursor 가 id 이고, value 가 totalMember 입니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants