π§ νμ΄μ§λ€μ΄μ μ΄λ?
- μ½ν μΈ λ₯Ό μ¬λ¬ νμ΄μ§λ‘ λλκ³ , μ΄μ νΉμ λ€μ νμ΄μ§λ‘ λμ΄κ°κ±°λ νΉμ νμ΄μ§λ‘ λμ΄κ° μ μλ λ§ν¬λ₯Ό νμ΄μ§ μλ¨μ΄λ νλ¨μ λ°°μΉνλ λ°©λ²
μΌνλͺ° νλ¨, κ²μ κ²°κ³Ό νλ¨μμ μ΅μνκ² μ°Ύμλ³΄μ€ μ μμ΅λλ€.
π§ 무νμ€ν¬λ‘€μ΄λ?
- λΈλΌμ°μ λλ μ€λ§νΈν°μμ μ€ν¬λ‘€ λ§λκ° νλ¨μ λλ¬νλ κ²μ λ°©μ§νλ κ²μ λ§ν©λλ€.
- μ¬μ©μκ° νμ΄μ§λ₯Ό λ μλλ‘ μ€ν¬λ‘€ ν λλ§λ€ μλ‘μ΄ μ½ν μΈ κ° μΆκ°λ©λλ€.
μΈμ€νκ·Έλ¨ νΌλ, μΌνλͺ° μν 리μ€νΈλ₯Ό μλλ‘ μ€ν¬λ‘€νλ€ λ³΄λ©΄ μ κΉμ λ‘λ©μ κ±°μΉκ³ 컨ν μΈ κ° μΆκ°λλ κ²½νμ νμ μ μμ£ ?! 무νμ€ν¬λ‘€μ μ μ©ν κ²½μ°μ λλ€.
π§ 컀μ κΈ°λ°μ΄ λλ°?
νν 무ν μ€ν¬λ‘€μ ꡬνν λ λ κ°μ§ λ°©λ²μ μ¬μ©ν©λλ€.
1. μ€νμ κΈ°λ° νμ΄μ§λ€μ΄μ
2. 컀μ κΈ°λ° νμ΄μ§λ€μ΄μ
μ€νμ
κΈ°λ° νμ΄μ§λ€μ΄μ
μ MySQL κΈ°μ€μΌλ‘ offset
, limit
μ μ¬μ©ν 쿼리λ₯Ό μ΄μ©ν©λλ€.
νμ§λ§ μ΄λ μ±λ₯ μ ν λ¬Έμ κ° μλλ°, λ°λ‘ 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
κ° μ‘°νλ©λλ€. κ·Έλ κΈ° λλ¬Έμ 컀μ κΈ°λ° νμ΄μ§λ€μ΄μ
μ νμ©νλ €λ©΄ 쑰건μ λ§λ 컀μ μ μ μ΄ μ€μν©λλ€.
컀μ μ μ μ΄ μ΄λ ΅λ€λ©΄ μ±λ₯μ 컀μ κΈ°λ° νμ΄μ§λ€μ΄μ λ³΄λ€ λ¨μ΄μ§μ§λ§ μμμ μΈκΈνλ μ€νμ κΈ°λ° νμ΄μ§λ€μ΄μ μ νμ©νλ©΄ μ½κ² ꡬνν μ μμ΅λλ€.
λ€μ κΈμμλ μ€νμ κΈ°λ° νμ΄μ§λ€μ΄μ μ νμ©νλ λ°©λ²μ λν΄ λ€λ€λ³΄κ² μ΅λλ€.
'βοΈ λ°±μλ: Backend' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
[Spring] Slack API νμ©νμ¬ μλ¬ λͺ¨λν°λ§νκΈ° (0) | 2023.08.03 |
---|---|
[Spring] JPA μμμ± μ»¨ν μ€νΈ μ΄ν΄νκΈ° (0) | 2023.06.27 |
[Spring] νΌλλλ JPA κ°λ μ 리 (Hibernate, Spring Data JPA) (0) | 2023.06.27 |
[Spring] λ©ν° λͺ¨λ νλ‘μ νΈ μ μ© (0) | 2023.06.26 |
[Spring] JPA λΉκ΄μ λ½ μ μ©μΌλ‘ λμμ± λ¬Έμ ν΄κ²°νκΈ° (0) | 2023.06.15 |
[Spring] MySQLκ³Ό mongoDB λ°μ΄ν°λ² μ΄μ€ 2κ° μ°λνκΈ° (0) | 2023.05.10 |
Spring 무νμ€ν¬λ‘€ ꡬν (2) - μ€νμ κΈ°λ° νμ΄μ§λ€μ΄μ (0) | 2023.01.01 |