Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

좋아요 기능 버그 해결을 위한 캐싱 자료구조 및 로직 변경 #788

Merged
merged 33 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ce62e0f
Merge pull request #683 from woowacourse-teams/hotfix/#682
dladncks1217 Oct 7, 2023
0b53fb4
Merge pull request #735 from woowacourse-teams/develop
LJW25 Oct 19, 2023
8fa3188
Update README.md
hgo641 Dec 9, 2023
891814a
Merge pull request #783 from woowacourse-teams/develop
mcodnjs Jan 23, 2024
c033764
fix: LikeCount 업데이트 로직 수정
mcodnjs Jan 26, 2024
52b1d06
refactor: updateMemberLikeCache 메서드 분리
mcodnjs Jan 26, 2024
ccbc23b
fix: 커뮤니티 여행 전체 조회 시 Redis 사용하도록 수정
mcodnjs Jan 26, 2024
968ca26
feat: RedisTemplate 빈 등록
mcodnjs Jan 27, 2024
5689976
feat: 좋아요 업데이트 로직 구현
mcodnjs Jan 28, 2024
12d48c8
feat: db와 redis 동기화 스케줄러 구현
mcodnjs Jan 28, 2024
5373b30
feat: 좋아요 조회 로직 구현
mcodnjs Jan 28, 2024
23378f7
refactor: 사용하지 않는 메서드 삭제
mcodnjs Jan 28, 2024
7109d2d
refactor: LikeElement 필드 변경
mcodnjs Jan 29, 2024
d1c4364
refactor: Likes 테이블에 없는 tripId에 default 값 할당
mcodnjs Jan 29, 2024
418b3e6
refactor: LikeRedisKeyConstants 생성
mcodnjs Jan 29, 2024
64dbb6f
refactor: 사용하지 않는 dto 삭제
mcodnjs Jan 29, 2024
90984b3
refactor: like ttl 상수화
mcodnjs Jan 29, 2024
ee61595
fix: likes 테이블 소문자로 변경
mcodnjs Jan 29, 2024
79335e8
fix: 커스텀 쿼리로 변경
mcodnjs Jan 29, 2024
56bb511
fix: 캐시된 tripId가 하나라도 있을 경우만 db 조회하도록 변경
mcodnjs Jan 29, 2024
7043b79
fix: likes 조회 시 memberIds 파싱 로직 수정
mcodnjs Jan 29, 2024
2ea5b03
fix: redis에 가변인자로 추가하도록 변경
mcodnjs Jan 29, 2024
4583c0e
fix: like key prefix 수정
mcodnjs Jan 29, 2024
68882fe
fix: like key prefix 수정
mcodnjs Jan 30, 2024
2378fa4
fix: empty_marker 타입 변경
mcodnjs Jan 30, 2024
7c2e8ff
fix: 업데이트 시 캐시가 없는 경우 DB에서 조회해오도록 수정
mcodnjs Jan 31, 2024
d33ccad
refactor: 업데이트 메서드 인자 수정 및 로직 리팩토링
mcodnjs Jan 31, 2024
59edb00
refactor: toLikeInfo로 메서드 네이밍 변경
mcodnjs Jan 31, 2024
c658576
refactor: likeElements로 변수명 변경
mcodnjs Jan 31, 2024
4a62d4c
refactor: 메서드 위치 변경
mcodnjs Jan 31, 2024
babb205
refactor: 메서드명 변경 및 LikeInfo dto 패키지로 이동
mcodnjs Jan 31, 2024
8530e26
test: LikeService 테스트 추가
mcodnjs Jan 31, 2024
339be7b
test: LikeSyncScheduler 테스트 추가
mcodnjs Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import hanglog.community.dto.response.RecommendTripListResponse;
import hanglog.global.exception.BadRequestException;
import hanglog.like.domain.LikeInfo;
import hanglog.like.domain.repository.CustomLikeRepository;
import hanglog.like.domain.repository.LikeRepository;
import hanglog.like.dto.LikeElement;
import hanglog.like.dto.LikeElements;
Expand Down Expand Up @@ -49,11 +50,12 @@ public class CommunityService {

private static final int RECOMMEND_AMOUNT = 5;

private final LikeRepository likeRepository;
private final TripRepository tripRepository;
private final TripCityRepository tripCityRepository;
private final CityRepository cityRepository;
private final PublishedTripRepository publishedTripRepository;
private final LikeRepository likeRepository;
private final CustomLikeRepository customLikeRepository;
private final RecommendStrategies recommendStrategies;
private final RedisTemplate<String, Object> redisTemplate;

Expand Down Expand Up @@ -136,7 +138,7 @@ private Map<Long, LikeInfo> getLikeInfoByTripIds(final Long memberId, final List
}
}

final List<LikeElement> likeElementByTripIds = likeRepository.findLikeElementByTripIds(nonCachedTripIds);
final List<LikeElement> likeElementByTripIds = customLikeRepository.findLikeElementByTripIds(nonCachedTripIds);
likeElementByTripIds.addAll(getEmptyLikeElements(likeInfoByTrip, nonCachedTripIds));
likeElementByTripIds.forEach(this::cachingLike);
likeInfoByTrip.putAll(new LikeElements(likeElementByTripIds).toLikeMap(memberId));
Expand All @@ -149,7 +151,7 @@ private LikeInfo getLikeInfoByTripId(final Long memberId, final Long tripId) {
return readLikeInfoFromCache(key, memberId);
}

final LikeElement likeElement = likeRepository.findLikesElementByTripId(tripId)
final LikeElement likeElement = customLikeRepository.findLikesElementByTripId(tripId)
.orElse(LikeElement.empty(tripId));
cachingLike(likeElement);
return likeElement.toLikeMap(memberId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package hanglog.like.domain.repository;

import hanglog.like.domain.Likes;
import hanglog.like.dto.LikeElement;
import java.util.List;
import java.util.Optional;

public interface CustomLikeRepository {

void saveAll(final List<Likes> likes);

Optional<LikeElement> findLikesElementByTripId(final Long tripId);

List<LikeElement> findLikeElementByTripIds(final List<Long> tripIds);
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,14 @@
package hanglog.like.domain.repository;

import hanglog.like.domain.Likes;
import hanglog.like.dto.LikeElement;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface LikeRepository extends JpaRepository<Likes, Long> {

@Query(value = """
SELECT l.trip_id AS tripId, COUNT(l.id) AS likeCount, GROUP_CONCAT(l.member_id) AS memberIds
FROM likes l
WHERE l.trip_id IN :tripIds
GROUP BY l.trip_id
""", nativeQuery = true)
List<LikeElement> findLikeElementByTripIds(@Param("tripIds") final List<Long> tripIds);

@Query(value = """
SELECT l.trip_id AS tripId, COUNT(l.id) AS likeCount, GROUP_CONCAT(l.member_id) AS memberIds
FROM likes l
WHERE l.trip_id = :tripId
GROUP BY l.trip_id
""", nativeQuery = true)
Optional<LikeElement> findLikesElementByTripId(@Param("tripId") final Long tripId);

@Modifying
@Query("DELETE FROM Likes WHERE tripId IN :tripIds")
void deleteByTripIds(final Set<Long> tripIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,30 @@

import hanglog.like.domain.Likes;
import hanglog.like.domain.repository.CustomLikeRepository;
import hanglog.like.dto.LikeElement;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

@RequiredArgsConstructor
@Repository
public class CustomLikeRepositoryImpl implements CustomLikeRepository {

private static final RowMapper<LikeElement> likeElementRowMapper = (rs, rowNum) ->
new LikeElement(
rs.getLong("tripId"),
rs.getLong("likeCount"),
parseMemberIds(rs.getString("memberIds")));

private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;

@Override
Expand All @@ -34,4 +48,46 @@ private MapSqlParameterSource getLikeToSqlParameterSource(final Likes likes) {
.addValue("tripId", likes.getTripId())
.addValue("memberId", likes.getMemberId());
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

요 아래 메서드들 왜 LikeRepository 에서 CustomLikeRepository로 옮겨진 건가요? @Query 가독성이 나빠서인가요?.? 궁금합니닷.
달라진 게 parseMemberIds()가 추가된 것 말고는 모르겠는데 혹시 이전 메서드에서는 memberIds가 제대로 안불러와졌던건가요?..?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

음음 일단, 기존에 사용하던 쿼리랑 뽑아오는 값이 달라요!!! GROUP_CONCAT 쪽 주의!!

-- 기존
l.tripId, 
COUNT(l.memberId), 
EXISTS(SELECT 1 FROM Likes l_1 WHERE l_1.memberId = :memberId AND l_1.tripId = l.tripId)

-- 현재
l.trip_id AS tripId, 
COUNT(l.id) AS likeCount, 
GROUP_CONCAT(l.member_id) AS memberIds

Redis 자료구조를 Set으로 사용하면서 특정 멤버가 좋아요 했는지 여부 뿐만 아니라 memberId 정보를 모두 캐싱해야 했어요!!
그래서 isLike를 뽑아오던 기존에 쿼리 대신 Set<memberId> 이 친구를 뽑아오는 쿼리로 변경하였는데,
하이버네이트 문법 상으로 GROUP_CONCAT은 적용할 수 없는 한계가 있었고, nativeQuery 옵션을 주면 쿼리는 잘 날라가는데 반환값이 LikeElement로 매핑이 안되서 직접 파싱해줘야 했어요 ㅠㅅㅠ 그래서 쩔 수 없이 직접 구현했습니다 ㅎㅎ ..

@Override
public Optional<LikeElement> findLikesElementByTripId(final Long tripId) {
final String sql = """
SELECT l.trip_id AS tripId, COUNT(l.id) AS likeCount, GROUP_CONCAT(l.member_id) AS memberIds
FROM likes l
WHERE l.trip_id = :tripId
GROUP BY l.trip_id
""";

final MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("tripId", tripId);
final List<LikeElement> results = namedParameterJdbcTemplate.query(sql, parameters, likeElementRowMapper);
if (results.isEmpty()) {
return Optional.empty();
}
return Optional.of(results.get(0));
}

@Override
public List<LikeElement> findLikeElementByTripIds(final List<Long> tripIds) {
final String sql = """
SELECT l.trip_id AS tripId, COUNT(l.id) AS likeCount, GROUP_CONCAT(l.member_id) AS memberIds
FROM likes l
WHERE l.trip_id IN (:tripIds)
GROUP BY l.trip_id
""";

final MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("tripIds", tripIds);
return namedParameterJdbcTemplate.query(sql, parameters, likeElementRowMapper);
}

private static Set<Long> parseMemberIds(final String memberIds) {
if (!StringUtils.hasText(memberIds)) {
return Collections.emptySet();
}
final String[] idArray = memberIds.split(",");
return Arrays.stream(idArray)
.map(Long::valueOf)
.collect(Collectors.toSet());
}
}