☘️ λ°±μ—”λ“œ: Backend

Spring λ¬΄ν•œμŠ€ν¬λ‘€ κ΅¬ν˜„ (1) - μ»€μ„œ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜

🐀 쀀콩이 2023. 1. 1. 10:36

🧐 νŽ˜μ΄μ§€λ„€μ΄μ…˜μ΄λž€?

 

  • μ½˜ν…μΈ λ₯Ό μ—¬λŸ¬ νŽ˜μ΄μ§€λ‘œ λ‚˜λˆ„κ³ , 이전 ν˜Ήμ€ λ‹€μŒ νŽ˜μ΄μ§€λ‘œ λ„˜μ–΄κ°€κ±°λ‚˜ νŠΉμ • νŽ˜μ΄μ§€λ‘œ λ„˜μ–΄κ°ˆ 수 μžˆλŠ” 링크λ₯Ό νŽ˜μ΄μ§€ μƒλ‹¨μ΄λ‚˜ ν•˜λ‹¨μ— λ°°μΉ˜ν•˜λŠ” 방법

 

μ‡Όν•‘λͺ° ν•˜λ‹¨, 검색 κ²°κ³Ό ν•˜λ‹¨μ—μ„œ μ΅μˆ™ν•˜κ²Œ 찾아보싀 수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

🧐 λ¬΄ν•œμŠ€ν¬λ‘€μ΄λž€?

 

  • λΈŒλΌμš°μ € λ˜λŠ” μŠ€λ§ˆνŠΈν°μ—μ„œ 슀크둀 λ§‰λŒ€κ°€ ν•˜λ‹¨μ— λ„λ‹¬ν•˜λŠ” 것을 λ°©μ§€ν•˜λŠ” 것을 λ§ν•©λ‹ˆλ‹€.
  • μ‚¬μš©μžκ°€ νŽ˜μ΄μ§€λ₯Ό 더 μ•„λž˜λ‘œ 슀크둀 ν•  λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ μ½˜ν…μΈ κ°€ μΆ”κ°€λ©λ‹ˆλ‹€.

 

μΈμŠ€νƒ€κ·Έλž¨ ν”Όλ“œ, μ‡Όν•‘λͺ° μƒν’ˆ 리슀트λ₯Ό μ•„λž˜λ‘œ μŠ€ν¬λ‘€ν•˜λ‹€ 보면 잠깐의 λ‘œλ”©μ„ 거치고 컨텐츠가 μΆ”κ°€λ˜λŠ” κ²½ν—˜μ„ ν•˜μ‹ μ  있죠?! λ¬΄ν•œμŠ€ν¬λ‘€μ„ μ μš©ν•œ κ²½μš°μž…λ‹ˆλ‹€.

 

 

🧐 μ»€μ„œ 기반이 뭔데?

 

ν”νžˆ λ¬΄ν•œ μŠ€ν¬λ‘€μ„ κ΅¬ν˜„ν•  λ•Œ 두 κ°€μ§€ 방법을 μ‚¬μš©ν•©λ‹ˆλ‹€.

 

1. μ˜€ν”„μ…‹ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜

2. μ»€μ„œ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜

 

μ˜€ν”„μ…‹ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜μ€ MySQL κΈ°μ€€μœΌλ‘œ offsetlimit μ„ μ‚¬μš©ν•œ 쿼리λ₯Ό μ΄μš©ν•©λ‹ˆλ‹€.

 

ν•˜μ§€λ§Œ μ΄λŠ” μ„±λŠ₯ μ €ν•˜ λ¬Έμ œκ°€ μžˆλŠ”λ°, λ°”λ‘œ offset κ°’이 클 λ•Œ λ¬Έμ œκ°€ λ°œμƒν•©λ‹ˆλ‹€.

 

select * from item
order by created_at desc
limit 10
offset 100000000;

 

μœ„μ™€ 같은 쿼리의 경우 offset 값이 1얡이기 λ•Œλ¬Έμ— μ•žμ˜ 1μ–΅κ°œμ˜ 데이터λ₯Ό λͺ¨λ‘ 읽은 뒀에, λ‹€μŒ 10개의 데이터λ₯Ό μ‘°νšŒν•˜μ—¬ μ‘λ‹΅ν•©λ‹ˆλ‹€. μ΄λŠ” λ’€λ‘œ 갈수둝 읽어야 ν•˜λŠ” 데이터가 λ§Žμ•„μ§„λ‹€λŠ” κ±Έ λœ»ν•˜κ³  점점 느렀질 수 밖에 μ—†μŠ΅λ‹ˆλ‹€.

 

μ»€μ„œ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜μ€ μ΄λŸ¬ν•œ λ¬Έμ œμ μ„ ν•΄κ²°ν•΄μ€λ‹ˆλ‹€.

 

 

πŸ’‘ μ»€μ„œ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜

 

  • Cursor κ°œλ…μ„ μ‚¬μš©ν•©λ‹ˆλ‹€.
  • μ‚¬μš©μžμ—κ²Œ 응닡해쀀 λ§ˆμ§€λ§‰ λ°μ΄ν„°μ˜ μ‹λ³„μž 값을 Cursor둜 μ‚¬μš©ν•©λ‹ˆλ‹€.

 

예λ₯Ό λ“€μ–΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

# 1 νŽ˜μ΄μ§€
select * from item
order by id asc
limit 10;

# 2 νŽ˜μ΄μ§€
select * from item
where id > 10 # 1 νŽ˜μ΄μ§€ 쑰회 κ²°κ³Ό cursor 값이 10
order by id asc
limit 10;

 

1 νŽ˜μ΄μ§€μ˜ μš”μ²­μœΌλ‘œ 쑰회된 item λ“€μ˜ id λŠ” 1 ~ 10 μž…λ‹ˆλ‹€. 이 λ•Œ λ§ˆμ§€λ§‰ μ‹λ³„μžμΈ id 10이 cursorκ°€ 되고 이λ₯Ό λ‹€μŒ νŽ˜μ΄μ§€ μš”μ²­ μ‹œ μ‚¬μš©ν•©λ‹ˆλ‹€. μ˜€ν”„μ…‹ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜κ³Ό 비ꡐ해보면 λ§ˆμ§€λ§‰μœΌλ‘œ 읽은 데이터 (id 10) 의 λ‹€μŒ 데이터 (id 11) λΆ€ν„° 10개λ₯Ό μ‘°νšŒν•˜κΈ° λ•Œλ¬Έμ— 맀번 μ›ν•˜λŠ” 데이터 개수만큼만 μ‘°νšŒν•œλ‹€λŠ” 이점이 μžˆμŠ΅λ‹ˆλ‹€.

 

 

πŸ‘» μ»€μ„œ 기반 λ¬΄ν•œμŠ€ν¬λ‘€ κ΅¬ν˜„

 

이제 Spring 으둜 λ¬΄ν•œμŠ€ν¬λ‘€μ„ κ΅¬ν˜„ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

슀크둀 νŽ˜μ΄μ§€λ„€μ΄μ…˜μ„ νŽΈλ¦¬ν•˜κ²Œ κ΅¬ν˜„ν•˜κΈ° μœ„ν•œ ν΄λž˜μŠ€μž…λ‹ˆλ‹€.
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class ScrollPaginationCollection<T> {

    private final List<T> itemsWithNextCursor;// ν˜„μž¬ 슀크둀의 μš”μ†Œ + λ‹€μŒ 슀크둀의 μš”μ†Œ 1개 (λ‹€μŒ 슀크둀이 μžˆλŠ”μ§€ 확인을 μœ„ν•œ)private final int countPerScroll;

    public static <T> ScrollPaginationCollection<T> of(List<T> itemsWithNextCursor, int size) {
        return new ScrollPaginationCollection<>(itemsWithNextCursor, size);
    }

    public boolean isLastScroll() {
        return this.itemsWithNextCursor.size() <= countPerScroll;
    }

    public List<T> getCurrentScrollItems() {
        if (isLastScroll()) {
            return this.itemsWithNextCursor;
        }
        return this.itemsWithNextCursor.subList(0, countPerScroll);
    }

    public T getNextCursor() {
        return itemsWithNextCursor.get(countPerScroll - 1);
    }

}

 

  • List<T> itemsWithNextCursor : ν˜„μž¬ 슀크둀의 데이터 + λ‹€μŒ 슀크둀의 데이터 1κ°œλ‹€μŒ 슀크둀이 μžˆλŠ”μ§€ ν™•μΈν•˜κΈ° μœ„ν•΄ λ‹€μŒ 슀크둀의 μš”μ†Œ 1개λ₯Ό 더 ν¬ν•¨ν•©λ‹ˆλ‹€.
  • int countPerScroll : 슀크둀 1νšŒμ— μ‘°νšŒν•  λ°μ΄ν„°μ˜ κ°œμˆ˜μž…λ‹ˆλ‹€.
  • boolean isLastScroll() : ν˜„μž¬ 슀크둀이 λ§ˆμ§€λ§‰ μŠ€ν¬λ‘€μΈμ§€ ν™•μΈν•˜κΈ° μœ„ν•œ λ©”μ†Œλ“œμž…λ‹ˆλ‹€.쿼리둜 데이터λ₯Ό μ‘°νšŒν•œ κ²°κ³Ό countPerScroll μ˜ 숫자 μ΄ν•˜λ‘œ 쑰회되면 λ§ˆμ§€λ§‰ 슀크둀이라고 νŒλ‹¨ν•©λ‹ˆλ‹€.
  • List<T> getCurrentScrollItems() : λ§ˆμ§€λ§‰ 슀크둀일 경우 itemsWithNextCursor λ₯Ό return ν•˜κ³ λ§ˆμ§€λ§‰ 슀크둀이 아닐 경우 λ‹€μŒ 슀크둀의 데이터 1개λ₯Ό μ œμ™Έν•˜κ³  return ν•©λ‹ˆλ‹€.
  • T getNextCursor() : ν˜„μž¬ 슀크둀의 데이터 쀑 λ§ˆμ§€λ§‰ 데이터λ₯Ό cursor둜 μ‚¬μš©ν•˜κ³  이λ₯Ό return ν•©λ‹ˆλ‹€.

 

μ‹€μ œ μ„œλΉ„μŠ€ λ‘œμ§μ—μ„œ ScrollPaginationCollection<T> 클래슀λ₯Ό μ‚¬μš©ν•œ μ˜ˆμ‹œμž…λ‹ˆλ‹€.
public GetFeedsResponse getFeeds(String userEmail, Long roomId, int size, Long lastFeedId) {
    User user = FeedServiceUtils.findUserByEmail(userRepository, userEmail);
       Room room = FeedServiceUtils.findRoomByRoomId(roomRepository, roomId);

    PageRequest pageRequest = PageRequest.of(0, size + 1);
    Page<Feed> page = feedRepository.findAllByRoomAndIdLessThanOrderByIdDesc(room, lastFeedId, pageRequest);
    List<Feed> feeds = page.getContent();

    ScrollPaginationCollection<Feed> feedsCursor = ScrollPaginationCollection.of(feeds, size);
    GetFeedsResponse response = GetFeedsResponse.of(feedsCursor, FeedImageCollection.of(feeds, feedImageRepository), feedRepository.countAllByRoom(room));

    return response;
}

 

ν˜„μž¬ μ„œλΉ„μŠ€ λ‘œμ§μ—μ„œ String userEmail, Long roomId, int size, Long lastFeedId λ₯Ό 인자둜 λ°›κ³  μžˆλŠ”λ° μ—¬κΈ°μ„œ int size, Long lastFeedId μ— 집쀑해야 ν•©λ‹ˆλ‹€.

 

  • int size : 슀크둀 1νšŒμ— μ‘°νšŒν•  λ°μ΄ν„°μ˜ 개수
  • Long lastFeedId : μ»€μ„œλ‘œ μ‚¬μš©ν•˜λŠ” 데이터 μ‹λ³„μžμž…λ‹ˆλ‹€.id λ‚΄λ¦Όμ°¨μˆœμœΌλ‘œ 데이터λ₯Ό μ‘°νšŒν•˜κΈ° λ•Œλ¬Έμ— λ‹€μŒ μŠ€ν¬λ‘€μ€ lastFeedId λ³΄λ‹€ μž‘μ€ id의 λ°μ΄ν„°λ§Œ ν™•μΈν•©λ‹ˆλ‹€.

λ‹€μŒμ€ Page<T> μΈν„°νŽ˜μ΄μŠ€, Pageable μΈν„°νŽ˜μ΄μŠ€, PageRequest ν΄λž˜μŠ€μ— λŒ€ν•œ 이해가 ν•„μš”ν•©λ‹ˆλ‹€.

  • Page<T> μΈν„°νŽ˜μ΄μŠ€λŠ” νŽ˜μ΄μ§€ 정보λ₯Ό λ‹΄μŠ΅λ‹ˆλ‹€.
  • Pageable μΈν„°νŽ˜μ΄μŠ€λŠ” νŽ˜μ΄μ§€ μ²˜λ¦¬μ— ν•„μš”ν•œ 정보λ₯Ό λ‹΄κ³  μžˆμŠ΅λ‹ˆλ‹€.
  • PageRequest ν΄λž˜μŠ€λŠ” Pageable μ˜ 정보가 담겨 객체화 된 ν΄λž˜μŠ€μž…λ‹ˆλ‹€.

JpaRepository κ°€ μƒμ†λœ μΈν„°νŽ˜μ΄μŠ€μ˜ νŒŒλΌλ―Έν„°λ‘œ PageRequest λ₯Ό μ „λ‹¬ν•˜λ©΄ Page<T> λ₯Ό return ν•©λ‹ˆλ‹€.

λ‹€μ‹œ getFeeds λ©”μ†Œλ“œλ₯Ό μ‚΄νŽ΄λ΄…μ‹œλ‹€.

  • PageRequest pageRequest = PageRequest.of(0, size + 1) : PageRequest κ°μ²΄μ˜ of λ©”μ†Œλ“œλŠ” 인자둜 μ‘°νšŒν•  page μ™€ ν•œ νŽ˜μ΄μ§€λ‹Ή μ‘°νšŒν•  λ°μ΄ν„°μ˜ 개수 size λ₯Ό λ°›μŠ΅λ‹ˆλ‹€. μ»€μ„œ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜μ΄κΈ° λ•Œλ¬Έμ— 항상 lastFeedId μ΄ν›„μ˜ id λ‘œλ§Œ μ‘°νšŒν•˜λ―€λ‘œ 첫번째 νŽ˜μ΄μ§€μ˜ 정보λ₯Ό λ°›μœΌλ©΄ λ©λ‹ˆλ‹€. size μ—λŠ” λ‹€μŒ 슀크둀이 μžˆλŠ”μ§€ νŒλ‹¨ν•˜κΈ° μœ„ν•΄ λ‹€μŒ 슀크둀의 μš”μ†Œ 1개λ₯Ό ν¬ν•¨ν•œ size + 1 μ„ μž…λ ₯ν•©λ‹ˆλ‹€.
  • Page<Feed> page = feedRepository.findAllByRoomAndIdLessThanOrderByIdDesc(room, lastFeedId, pageRequest) : JpaRepository λ₯Ό μƒμ†ν•œ feedRepository μ— νŒŒλΌλ―Έν„°λ‘œ μ»€μ„œλ‘œ μ‚¬μš©ν•˜λŠ” lastFeedId μ™€ PageRequest λ₯Ό λ‹΄μ•„μ„œ 데이터λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€.
  • List<Feed> feeds = page.getContent() : Page<T> κ°€ μ œκ³΅ν•˜λŠ” getContent λ©”μ†Œλ“œλ‘œ μ‘°νšŒν•œ 데이터λ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€.

 

ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 전달할 dto인 GetFeedsResponse ν΄λž˜μŠ€μž…λ‹ˆλ‹€.
@ToString
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class GetFeedsResponse {

    private static final long LAST_CURSOR = -1L;

    private List<FeedsInfoResponse> contents = new ArrayList<>();
    private long totalElements;
    private long nextCursor;

    private GetFeedsResponse(List<FeedsInfoResponse> contents, long totalElements, long nextCursor) {
        this.contents = contents;
        this.totalElements = totalElements;
        this.nextCursor = nextCursor;
    }

    public static GetFeedsResponse of(ScrollPaginationCollection<Feed> feedsScroll, FeedImageCollection feedImages, long totalElements) {
        if (feedsScroll.isLastScroll()) {
            return GetFeedsResponse.newLastScroll(feedsScroll.getCurrentScrollItems(), feedImages, totalElements);
        }
        return GetFeedsResponse.newScrollHasNext(feedsScroll.getCurrentScrollItems(), feedImages, totalElements, feedsScroll.getNextCursor().getId());
    }

    private static GetFeedsResponse newLastScroll(List<Feed> feedsScroll, FeedImageCollection feedImages, long totalElements) {
        return newScrollHasNext(feedsScroll, feedImages, totalElements, LAST_CURSOR);
    }

    private static GetFeedsResponse newScrollHasNext(List<Feed> feedsScroll, FeedImageCollection feedImages, long totalElements, long nextCursor) {
        return new GetFeedsResponse(getContents(feedsScroll, feedImages), totalElements, nextCursor);
    }

    private static List<FeedsInfoResponse> getContents(List<Feed> feedsScroll, FeedImageCollection feedImages) {
        return feedsScroll.stream()
                .map(feed -> FeedsInfoResponse.of(feed, feedImages.getImagesByFeedId(feed.getId())))
                .collect(Collectors.toList());
    }
}

 

  • List<FeedsInfoResponse> contents : ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ μ΅œμ’…μ μœΌλ‘œ 전달될 λ°μ΄ν„°λ“€μž…λ‹ˆλ‹€.FeedsInfoResponse λŠ” μ„œλΉ„μŠ€ λ‘œμ§μ—μ„œ μ‘°νšŒν•œ Feed λ₯Ό κ°€κ³΅ν•œ ν˜•νƒœμž…λ‹ˆλ‹€.
  • long totalElements : 쑰회 κ°€λŠ₯ν•œ λ°μ΄ν„°μ˜ 총 κ°œμˆ˜μž…λ‹ˆλ‹€.
  • long nextCursor : λ‹€μŒ μŠ€ν¬λ‘€μ—μ„œ μ‚¬μš©ν•  μ»€μ„œμ˜ κ°’μž…λ‹ˆλ‹€.
  • long LAST_CURSOR = -1L : λ‹€μŒ 슀크둀이 μ‘΄μž¬ν•˜μ§€ μ•Šμ„ 경우 nextCursor μ— λ„£μ–΄μ£ΌκΈ° μœ„ν•œ κ°’μž…λ‹ˆλ‹€.nextCursor = -1L μΌ 경우 ν•΄λ‹Ή 슀크둀이 λ§ˆμ§€λ§‰ μŠ€ν¬λ‘€μž„μ„ λœ»ν•©λ‹ˆλ‹€.
  • List<FeedsInfoResponse> getContents(List<Feed> feedsScroll, FeedImageCollection feedImages) : contents λ‘œ 전달할 λ°μ΄ν„°λ‘œ κ°€κ³΅ν•˜κΈ° μœ„ν•œ λ©”μ†Œλ“œμž…λ‹ˆλ‹€.
  • GetFeedsResponse newScrollHasNext(List<Feed> feedsScroll, FeedImageCollection feedImages, long totalElements, long nextCursor) : λ‹€μŒ 슀크둀이 μ‘΄μž¬ν•˜λŠ” 경우 nextCursor μ— λ‹€μŒ μ»€μ„œ 값을 λ‹΄μ•„μ„œ 객체λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•œ λ©”μ†Œλ“œμž…λ‹ˆλ‹€.
  • GetFeedsResponse newLastScroll(List<Feed> feedsScroll, FeedImageCollection feedImages, long totalElements) : λ‹€μŒ 슀크둀이 μ‘΄μž¬ν•˜μ§€ μ•Šμ„ 경우 nextCursor μ— 1L μ„ λ‹΄μ•„μ„œ 객체λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•œ λ©”μ†Œλ“œμž…λ‹ˆλ‹€.
  • GetFeedsResponse of(ScrollPaginationCollection<Feed> feedsScroll, FeedImageCollection feedImages, long totalElements) : μ„œλΉ„μŠ€ λ‘œμ§μ—μ„œλŠ” ν•΄λ‹Ή λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•΄μ„œ μ‘°νšŒν•œ 데이터λ₯Ό ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 전달할 λ°μ΄ν„°λ‘œ κ°€κ³΅ν•©λ‹ˆλ‹€. ScrollPaginationCollection ν΄λž˜μŠ€μ˜ isLastScroll λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•΄μ„œ ν•΄λ‹Ή 슀크둀이 λ§ˆμ§€λ§‰ μŠ€ν¬λ‘€μΈμ§€ ν™•μΈν•©λ‹ˆλ‹€. 이후에 λ§ˆμ§€λ§‰ μŠ€ν¬λ‘€μΈμ§€ 여뢀에 따라 newLastScroll λ˜λŠ” newScrollHasNext λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€.

 

λ§ˆμ§€λ§‰μœΌλ‘œ getFeeds λ©”μ†Œλ“œλ‘œ λŒμ•„κ°€μ„œ 마무리 ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

  • ScrollPaginationCollection<Feed> feedsCursor = ScrollPaginationCollection.of(feeds, size) : μœ„μ—μ„œ μ†Œκ°œν•œ ScrollPaginationCollection<T> ν΄λž˜μŠ€μ˜ of λ©”μ†Œλ“œμ˜ 인자둜 ScrollPaginationCollection κ°μ²΄λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
  • GetFeedsResponse response = GetFeedsResponse.of(feedsCursor, FeedImageCollection.of(feeds, feedImageRepository), feedRepository.countAllByRoom(room)) : ν΄λΌμ΄μ–ΈνŠΈμΈ‘μ— 전달할 Response ν˜•μ‹μœΌλ‘œ λ³€ν™˜ν•΄μ€€ λ’€ 이λ₯Ό return ν•©λ‹ˆλ‹€.

 

⭐️ μ‹€μ œ Response 확인

 

슀크둀 νŽ˜μ΄μ§€λ„€μ΄μ…˜ 졜초 μš”μ²­μ˜ cursor κ°’μœΌλ‘œλŠ” long 의 μ΅œλŒ“κ°’μΈ 9223372036854775807 λ₯Ό λ‹΄μ•„μ„œ μš”μ²­ν•©λ‹ˆλ‹€.

 

GET localhost:8080/v1/feed?roomId=1&size=1&lastFeedId=9223372036854775807
{
    "status": 200,
    "message": "OK",
    "data": {
        "contents": [
            {
                "createdAt": 1662647379,
                "updatedAt": 1662647379,
                "feedId": 20,
                "userId": 1,
                "title": "title",
                "content": "content",
                "imageUrls": [
                    "image.png"
                ]
            }
        ],
        "totalElements": 20,
        "nextCursor": 20
    }
}

 

그러면 μœ„μ™€ 같이 data 에 GetFeedsResponse ν˜•νƒœλ‘œ κ°€κ³΅λœ 데이터λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

λ‹€μŒ μš”μ²­μœΌλ‘œλŠ” lastFeedId μ— nextCursor κ°’인 20 을 λ‹΄μ•„μ„œ μš”μ²­ν•©λ‹ˆλ‹€.

 

GET localhost:8080/v1/feed?roomId=1&size=1&lastFeedId=20
{
    "status": 200,
    "message": "OK",
    "data": {
        "contents": [
            {
                "createdAt": 1662647378,
                "updatedAt": 1662647378,
                "feedId": 19,
                "userId": 1,
                "title": "title",
                "content": "content",
                "imageUrls": [
                    "image.png"
                ]
            }
        ],
        "totalElements": 20,
        "nextCursor": 19
    }
}

 

cursor κ°’μœΌλ‘œ μž…λ ₯ν–ˆλ˜ 20보닀 μž‘μ€ id μ€‘ 1개λ₯Ό μ‘°νšŒν–ˆκΈ° λ•Œλ¬Έμ— feedId κ°€ 19 인 데이터가 쑰회된 λͺ¨μŠ΅μ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

λ§ˆμ§€λ§‰ μš”μ†Œκ°€ id = 1 μ΄κΈ° λ•Œλ¬Έμ— lastFeedId μ— 2λ₯Ό λ‹΄μ•„μ„œ μš”μ²­μ„ λ³΄λ‚΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

GET localhost:8080/v1/feed?roomId=1&size=1&lastFeedId=2
{
    "status": 200,
    "message": "OK",
    "data": {
        "contents": [
            {
                "createdAt": 1662647366,
                "updatedAt": 1662647366,
                "feedId": 1,
                "userId": 1,
                "title": "title",
                "content": "content",
                "imageUrls": [
                    "image.png"
                ]
            }
        ],
        "totalElements": 20,
        "nextCursor": -1
    }
}

 

더 이상 μ‘°νšŒν•  데이터가 남지 μ•Šμ•˜κΈ° λ•Œλ¬Έμ— λ‹€μŒκ³Ό 같이 nextCursor μ— -1 이 λ‹΄κΈ΄ λͺ¨μŠ΅μ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

❗️ μ£Όμ˜μ‚¬ν•­

 

μœ„μ—μ„œ μ†Œκ°œν•œ 방법은 μ»€μ„œλ‘œ λ°μ΄ν„°μ˜ id κ°’을 μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€. MySQL κΈ°μ€€μœΌλ‘œ id μ— auto increment μ˜΅μ…˜μ„ μ£Όλ©΄ 데이터가 생성될 λ•Œλ§ˆλ‹€ id κ°’이 1μ”© μ¦κ°€ν•˜κΈ° λ•Œλ¬Έμ— μœ„μ™€ 같은 λ°©λ²•μœΌλ‘œ 데이터λ₯Ό μ‘°νšŒν•˜λ©΄ λ°μ΄ν„°λŠ” μ΅œμ‹ μˆœμœΌλ‘œ μ‘°νšŒλ©λ‹ˆλ‹€.

 

ν•˜μ§€λ§Œ λ‹€λ₯Έ 쑰건으둜 데이터λ₯Ό μ •λ ¬ν•΄μ„œ λ¬΄ν•œμŠ€ν¬λ‘€λ‘œ μ‘°νšŒν•œλ‹€λ©΄ μ–΄λ–»κ²Œ 될까?

 

μ•„λž˜μ™€ 같은 ν…Œμ΄λΈ”μ΄ μžˆλ‹€κ³  κ°€μ •ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

id index
1 2
2 3
3 4
4 1

 

id κ°€ μ•„λ‹Œ index κΈ°μ€€ λ‚΄λ¦Όμ°¨μˆœμœΌλ‘œ μ •λ ¬ν•˜λ©΄ λ‹€μŒκ³Ό 같은 μˆœμ„œκ°€ λ©λ‹ˆλ‹€.

id index
3 4
2 3
1 2
4 1

 

졜초 μ»€μ„œ κ°’μœΌλ‘œ lastFeedId = 9223372036854775807 λ₯Ό λ‹΄μ•„μ„œ μš”μ²­μ„ λ³΄λ‚΄κ²Œ 되면 첫번째 μˆœμ„œμΈ id 3 이 μ•„λ‹ˆλΌ id 4 κ°€ μ‘°νšŒλ©λ‹ˆλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— μ»€μ„œ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜μ„ ν™œμš©ν•˜λ €λ©΄ μ‘°κ±΄μ— λ§žλŠ” μ»€μ„œ 선정이 μ€‘μš”ν•©λ‹ˆλ‹€.

 

μ»€μ„œ 선정이 μ–΄λ ΅λ‹€λ©΄ μ„±λŠ₯은 μ»€μ„œ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜λ³΄λ‹€ λ–¨μ–΄μ§€μ§€λ§Œ μœ„μ—μ„œ μ–ΈκΈ‰ν–ˆλ˜ μ˜€ν”„μ…‹ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜μ„ ν™œμš©ν•˜λ©΄ μ‰½κ²Œ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

λ‹€μŒ κΈ€μ—μ„œλŠ” μ˜€ν”„μ…‹ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜μ„ ν™œμš©ν•˜λŠ” 방법에 λŒ€ν•΄ λ‹€λ€„λ³΄κ² μŠ΅λ‹ˆλ‹€.