Skip to content

Commit ddce6fe

Browse files
Merge pull request #200 from prgrms-web-devcourse-final-project/refactor/#186
[Concert] 공연 캐싱 부분 리팩토링 + 공연 날짜 저장 후 캐싱 최신화 반영
2 parents 09092e4 + d348e48 commit ddce6fe

4 files changed

Lines changed: 68 additions & 59 deletions

File tree

src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertRedisRepository.java

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.springframework.data.redis.core.*;
1111
import org.springframework.stereotype.Repository;
1212

13+
import java.time.LocalDateTime;
1314
import java.util.*;
1415
import java.util.concurrent.TimeUnit;
1516

@@ -20,15 +21,15 @@ public class ConcertRedisRepository {
2021
private final RedisTemplate<String, String> redisTemplate;
2122
private final RedisTemplate<String, Object> objectRedisTemplate;
2223

23-
private static final String LOCK_FLAG_PREFIX = "initLoad: ";
24+
private static final String LOCK_FLAG_PREFIX = "initLoad:";
2425

25-
private static final String CONCERT_DETAIL_PREFIX = "concertDetail: ";
26+
private static final String CONCERT_DETAIL_PREFIX = "concertDetail:";
2627

27-
private static final String CONCERT_LIST_PREFIX = "concertList: ";
28+
private static final String CONCERT_LIST_PREFIX = "concertList:";
2829

29-
private static final String VIEW_COUNT_MAP = "viewCountMap";
30+
private static final String CONCERTS_COUNT_PREFIX = "totalConcertsCount:";
3031

31-
private static final String CONCERTS_COUNT_PREFIX = "totalConcertsCount: ";
32+
private static final String CONCERTS_VIEW_COUNTS = "concertsViewCount";
3233

3334
private static final int HOUR = 3600;
3435

@@ -50,7 +51,7 @@ public void unlockSave(String key) {
5051
}
5152

5253
// 공연 목록 캐싱
53-
public void listSave(ListSort sort, Pageable pageable, List<ConcertItem> list) {
54+
public void saveConcertsList(ListSort sort, Pageable pageable, List<ConcertItem> list) {
5455
String key = CONCERT_LIST_PREFIX + sort.name() + pageable.getPageNumber();
5556
objectRedisTemplate.opsForValue().set(key, list, HOUR, TimeUnit.SECONDS);
5657
}
@@ -70,64 +71,70 @@ public void deleteAllConcertsList() {
7071
}
7172

7273
// 공연 상세 캐싱
73-
public void detailSave(long concertId, ConcertDetailResponse concertDetailResponse) {
74+
public void saveConcertDetail(Long concertId, ConcertDetailResponse concertDetailResponse) {
7475
objectRedisTemplate.opsForValue().set(
7576
CONCERT_DETAIL_PREFIX + concertId,
7677
concertDetailResponse,
77-
HOUR,
78-
TimeUnit.SECONDS
78+
2,
79+
TimeUnit.DAYS
7980
);
81+
redisTemplate.opsForHash().put(CONCERTS_VIEW_COUNTS, concertId.toString(), concertDetailResponse.getViewCount() + "");
82+
}
83+
84+
// 공연 정보 가져오기
85+
public ConcertDetailResponse getCachedConcertDetail(Long concertId) {
86+
ConcertDetailResponse concertDetailResponse = getConcertDetailResponse(concertId);
87+
if (concertDetailResponse == null) return null;
88+
return concertDetailResponse;
8089
}
8190

82-
// todo : 객체 일부의 값만 바뀌는거니 해당 값만 바꿔서 저장하거나 Redis 내부의 값만 갱신할 수 있는 방법 찾기
83-
public ConcertDetailResponse getDetail(long concertId) {
91+
private ConcertDetailResponse getConcertDetailResponse(long concertId) {
8492
ConcertDetailResponse concertDetailResponse = (ConcertDetailResponse) objectRedisTemplate.opsForValue().get(CONCERT_DETAIL_PREFIX + concertId);
8593
if (concertDetailResponse == null) return null;
86-
int viewCount = concertDetailResponse.getViewCount();
87-
viewCountSet(concertId, viewCount + 1);
88-
concertDetailResponse.setViewCount(viewCount + 1);
89-
detailSave(concertId, concertDetailResponse);
9094
return concertDetailResponse;
9195
}
9296

9397
// 공연 상세 삭제
94-
public void deleteDetail(String concertId) {
98+
public void deleteConcertDetail(String concertId) {
9599
redisTemplate.delete(CONCERT_DETAIL_PREFIX + concertId);
96100
}
97101

98102
// 모든 공연 상세 삭제
99-
public void deleteAllConcertDetail() {
103+
public void deleteAllCachedConcertDetail() {
100104
deleteAllItemsByPREFIX(CONCERT_DETAIL_PREFIX);
101105
}
102106

103-
// 조회수 처리 -> 좀 지저분한데 개선 여지 찾아보기
104-
public int viewCountSet(long concertId, int viewCount) {
105-
Map<String, Integer> rawMap = (Map<String, Integer>) objectRedisTemplate.opsForValue().get(VIEW_COUNT_MAP);
106-
107-
if (rawMap == null) {
108-
Map<Long, Integer> viewCountMap = new HashMap<>();
109-
viewCountMap.put(concertId, viewCount);
110-
objectRedisTemplate.opsForValue().set(VIEW_COUNT_MAP, viewCountMap);
111-
} else {
112-
Map<Long, Integer> viewCountMap = convertViewCountMap(rawMap);
113-
viewCountMap.put(concertId, viewCount);
114-
objectRedisTemplate.opsForValue().set(VIEW_COUNT_MAP, viewCountMap);
115-
log.info(viewCountMap.size() + "view count size.");
107+
// 모든 공연의 조회수 맵 조회 -> 하나의 해시를 기준으로 가져올 수 있게 처리
108+
public Map<Long, Integer> getCachedViewCountMap() {
109+
Map<Object, Object> rawMap = redisTemplate.opsForHash().entries(CONCERTS_VIEW_COUNTS);
110+
Map<Long, Integer> viewCountMap = new HashMap<>();
111+
for (Map.Entry<Object, Object> rawEntity : rawMap.entrySet()) {
112+
Long concertID = Long.valueOf(rawEntity.getKey().toString());
113+
Integer viewCount = Integer.valueOf(rawEntity.getValue().toString());
114+
viewCountMap.put(concertID, viewCount);
116115
}
117-
return viewCount;
116+
return viewCountMap;
118117
}
119118

120-
// 조회수 맵 조회
121-
public Map<Long, Integer> getViewCountMap() {
122-
Map<String, Integer> rawMap = (Map<String, Integer>) objectRedisTemplate.opsForValue().get(VIEW_COUNT_MAP);
123-
if (rawMap == null) return null;
124-
objectRedisTemplate.delete(VIEW_COUNT_MAP);
125-
return convertViewCountMap(rawMap);
119+
// 공연의 조회수 조회
120+
public Long getCachedViewCount(Long concertId) {
121+
Long viewCount = Long.valueOf(redisTemplate.opsForHash()
122+
.get(
123+
CONCERTS_VIEW_COUNTS,
124+
concertId.toString()
125+
)
126+
.toString());
127+
128+
return viewCount;
126129
}
127130

128-
// 조회수 맵 삭제
129-
public void deleteViewCountMap() {
130-
objectRedisTemplate.delete(VIEW_COUNT_MAP);
131+
// 공연에 예매시작, 종료일자 추가
132+
public void updateCachedTickingDate(Long concertId, LocalDateTime TicketTime, LocalDateTime TicketEndTime) {
133+
ConcertDetailResponse concertDetailResponse = getConcertDetailResponse(concertId);
134+
if (concertDetailResponse == null) return;
135+
concertDetailResponse.setTicketTime(TicketTime);
136+
concertDetailResponse.setTicketEndTime(TicketEndTime);
137+
saveConcertDetail(concertId, concertDetailResponse);
131138
}
132139

133140
// String Integer 타입 맵을 Long Integer로 변환
@@ -164,20 +171,20 @@ private void deleteAllItemsByPREFIX(String prefix) {
164171

165172
// 총 공연의 개수 저장
166173
public Long saveTotalConcertsCount(Long totalConcertsCount, ListSort sort) {
167-
redisTemplate.opsForValue().set(CONCERTS_COUNT_PREFIX + sort.name(),totalConcertsCount.toString());
174+
redisTemplate.opsForValue().set(CONCERTS_COUNT_PREFIX + sort.name(), totalConcertsCount.toString());
168175
return totalConcertsCount;
169176
}
170177

171178
// 총 공연의 개수 조회
172179
public Long getTotalConcertsCount(ListSort sort) {
173-
String raw = redisTemplate.opsForValue().get(CONCERTS_COUNT_PREFIX + sort.name());
180+
String raw = redisTemplate.opsForValue().get(CONCERTS_COUNT_PREFIX + sort.name());
174181
if (raw == null) return -1L;
175-
else return Long.parseLong(redisTemplate.opsForValue().get(CONCERTS_COUNT_PREFIX + sort.name()));
182+
else return Long.parseLong(redisTemplate.opsForValue().get(CONCERTS_COUNT_PREFIX + sort.name()));
176183
}
177184

178185
// 총 공연의 개수 삭제
179186
public void deleteTotalConcertsCount(ListSort sort) {
180-
redisTemplate.delete(CONCERTS_COUNT_PREFIX+sort.name());
187+
redisTemplate.delete(CONCERTS_COUNT_PREFIX + sort.name());
181188
}
182189

183190
// 사용자가 좋아요를 누른 공연의 개수 조회(임시 캐시 느낌으로 짧게 저장, 조회시 시간 갱신 ~1일
@@ -189,7 +196,7 @@ public Long getUserLikedCount(User user) {
189196
}
190197

191198
// 사용자가 좋아요를 누른 공연의 개수 저장
192-
public Long saveUserLikedCount(User user,Long count) {
199+
public Long saveUserLikedCount(User user, Long count) {
193200
redisTemplate.opsForValue().set(
194201
CONCERTS_COUNT_PREFIX + user.getId(),
195202
count.toString(),

src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/ConcertService.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public List<ConcertItem> getConcertsList(Pageable pageable, ListSort sort) {
5555
case REGISTERED -> concertItems = concertRepository.getConcertItemsOrderByApiId(pageable);
5656
}
5757

58-
concertRedisRepository.listSave(sort,pageable,concertItems);
58+
concertRedisRepository.saveConcertsList(sort,pageable,concertItems);
5959
return concertItems;
6060
}
6161

@@ -100,34 +100,33 @@ public void setAutoComplete(){
100100
// 공연 상세 조회 조회시 조회수 1 증가 -> 캐싱에 따른 조회수 불일치 해소를 어떻게 할 것인가? V -> 이제 캐싱된거 날리고 새로운 수치 반영 어케할 것인지 + 여러번 조회수 올릴 시 처리 어떻게 할지
101101
@Transactional
102102
public ConcertDetailResponse getConcertDetail(long concertId) {
103-
ConcertDetailResponse concertDetailResponse = concertRedisRepository.getDetail(concertId);
103+
ConcertDetailResponse concertDetailResponse = concertRedisRepository.getCachedConcertDetail(concertId);
104104
if(concertDetailResponse == null){
105105
concertDetailResponse = concertRepository.getConcertDetailById(concertId);
106106
List<ConcertImage> concertImages = concertImageRepository.getConcertImagesByConcert_ConcertId(concertId);
107107
List<String> concertImageUrls = new ArrayList<>();
108108
for(ConcertImage concertImage : concertImages){
109109
concertImageUrls.add(concertImage.getImageUrl());
110110
}
111-
112111
concertRepository.concertViewCountUp(concertId);
113112
concertDetailResponse.setConcertImageUrls(concertImageUrls);
114-
concertDetailResponse.setViewCount(concertDetailResponse.getViewCount() + 1);
115-
concertRedisRepository.detailSave(concertId, concertDetailResponse);
116113
}
114+
// 조회수 1 증가하고 해당 데이터를 캐시에 저장.
115+
concertDetailResponse.setViewCount(concertDetailResponse.getViewCount() + 1);
116+
concertRedisRepository.saveConcertDetail(concertId, concertDetailResponse);
117117
return concertDetailResponse;
118118
}
119119

120120
// 조회수 갱신
121121
@Transactional
122122
public void viewCountUpdate(){
123-
Map<Long,Integer> viewCountMap = concertRedisRepository.getViewCountMap();
123+
Map<Long,Integer> viewCountMap = concertRedisRepository.getCachedViewCountMap();
124124
if(viewCountMap == null || viewCountMap.isEmpty()) {
125125
log.info("viewCountMap is empty");
126126
} else{
127127
for (Map.Entry<Long, Integer> viewCountEntry : viewCountMap.entrySet()) {
128128
concertRepository.concertViewCountSet(viewCountEntry.getKey(), viewCountEntry.getValue());
129129
}
130-
concertRedisRepository.deleteViewCountMap();
131130
concertRedisRepository.deleteAllConcertsList();
132131
log.info("viewCount updated");
133132
}
@@ -238,7 +237,10 @@ public ConcertDetailResponse setConcertTicketingTime(ConcertTicketTimeSetRequest
238237
if(ticketEndTime.isAfter(concert.getEndDate().atTime(LocalTime.MAX))) throw new BusinessException(ConcertErrorCode.CONCERT_TICKETING_END_TIME_IS_NOT_AFTER_CONCERT_END_DATE);
239238

240239
concert.ticketTimeSet(ticketTime, ticketEndTime);
240+
// DB에 저장
241241
Concert savedConcert = concertRepository.save(concert);
242+
// 캐시에도 갱신
243+
concertRedisRepository.updateCachedTickingDate(concert.getConcertId(),ticketTime,ticketEndTime);
242244
return concertRepository.getConcertDetailById(savedConcert.getConcertId());
243245
}
244246

src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/KopisApiService.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
import org.springframework.http.MediaType;
1616
import org.springframework.scheduling.annotation.Async;
1717
import org.springframework.scheduling.annotation.EnableAsync;
18-
import org.springframework.scheduling.annotation.EnableScheduling;
19-
import org.springframework.scheduling.annotation.Scheduled;
2018
import org.springframework.stereotype.Service;
2119
import org.springframework.transaction.annotation.Transactional;
2220
import org.springframework.web.client.RestClient;
@@ -112,7 +110,7 @@ public void setConcertsList() throws InterruptedException {
112110
totalConcertsList.add(p);
113111
}
114112
log.info("Total Concert List: {}", totalConcertsList.size() + "개의 데이터 가져오는중...");
115-
Thread.sleep(200);
113+
Thread.sleep(120);
116114
}
117115

118116
}catch (Exception e){
@@ -126,7 +124,9 @@ public void setConcertsList() throws InterruptedException {
126124
log.info("공연 목록 로드 완료, 공연 세부 내용 로드 및 저장");
127125
try {
128126
for (ConcertListElement performanceListElement : totalConcertsList) {
127+
// API에서 공연 상세 가져오기
129128
ConcertDetailResponse concertDetailResponse = getConcertDetailResponse(serviceKey, performanceListElement.getApiConcertId());
129+
Thread.sleep(120);
130130
ConcertDetailElement concertDetail = concertDetailResponse.getConcertDetail();
131131

132132
// 콘서트 위치 저장
@@ -137,6 +137,7 @@ public void setConcertsList() throws InterruptedException {
137137
if (concertPlace == null) {
138138
// 맵이나 DB에 없다면 API에서 해당 콘서트 위치를 가져와서 DB에 저장 후 캐시에 저장
139139
ConcertPlaceDetailResponse concertPlaceDetailElement = getConcertPlaceDetailResponse(serviceKey, concertPlaceAPiKey);
140+
Thread.sleep(120);
140141
ConcertPlaceDetailElement concertPlaceDetail = concertPlaceDetailElement.getConcertPlaceDetail();
141142
concertPlace = concertPlaceDetail.getConcertPlace();
142143
ConcertPlace savedConcertPlace = placeRepository.save(concertPlace);
@@ -167,8 +168,6 @@ public void setConcertsList() throws InterruptedException {
167168

168169
addedTicketOffices += saveConcertTicketOffice(concertDetail, savedConcert);
169170
addedConcertImages += saveConcertImages(concertDetail, savedConcert);
170-
171-
Thread.sleep(300);
172171
}
173172
} catch (Exception e) {
174173
log.error("개별 공연 세부 내용 저장 도중 오류 발생");
@@ -286,6 +285,7 @@ public SetResultResponse updateConcertData() throws InterruptedException {
286285
// 기존에 저장되어 있던 연관 테이블 데이터 삭제
287286
ticketOfficeRepository.deleteByConcertId(savedConcert.getConcertId());
288287
imageRepository.deleteByConcertId(savedConcert.getConcertId());
288+
// 갱신된 데이터로 연관 테이블 저장
289289
updatedTicketOffices += saveConcertTicketOffice(concertDetail, savedConcert);
290290
updatedConcertImages += saveConcertImages(concertDetail, savedConcert);
291291
}
@@ -296,7 +296,7 @@ public SetResultResponse updateConcertData() throws InterruptedException {
296296
// 갱신 후 업데이트 시간 저장, 캐시의 데이터 삭제
297297
concertUpdateTimeRepository.save(updatedTime);
298298
concertRedisRepository.deleteAllConcertsList();
299-
concertRedisRepository.deleteAllConcertDetail();
299+
concertRedisRepository.deleteAllCachedConcertDetail();
300300
concertRedisRepository.deleteTotalConcertsCount(ListSort.VIEW);
301301
return new SetResultResponse(addedConcerts,updatedConcerts,addedConcertPlaces,updatedConcertPlaces,addedConcertImages,updatedConcertImages,addedTicketOffices,updatedTicketOffices);
302302
}

src/main/java/com/back/web7_9_codecrete_be/global/scheduler/ConcertScheduler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class ConcertScheduler {
1818
private final ConcertNotifyService concertNotifyService;
1919

2020
// 공연 데이터 업데이트를 진행합니다.
21-
@Scheduled(cron = "0 0 2 * * *")
21+
@Scheduled(cron = "0 0 2 * * MON")
2222
public void concertUpdateSchedule() throws InterruptedException {
2323
kopisApiService.updateConcertData();
2424
}

0 commit comments

Comments
 (0)