Skip to content

Commit 88d6724

Browse files
feat: KopisApi 락 해제 추가 및, 락 일관화, 데이터 로그 테이블 추가 및 로그 테이블 데이터 기반 업데이트 변경
1 parent 2b9f0cb commit 88d6724

6 files changed

Lines changed: 138 additions & 98 deletions

File tree

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
package com.back.web7_9_codecrete_be.domain.concerts.entity;
22

3-
import jakarta.persistence.Entity;
4-
import jakarta.persistence.GeneratedValue;
5-
import jakarta.persistence.GenerationType;
6-
import jakarta.persistence.Id;
3+
import jakarta.persistence.*;
74
import lombok.Getter;
5+
import lombok.RequiredArgsConstructor;
86
import org.hibernate.annotations.CreationTimestamp;
97

108
import java.time.LocalDateTime;
119

1210
@Entity
1311
@Getter
12+
@RequiredArgsConstructor
1413
public class ConcertKopisApiLog {
1514
@Id
1615
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -19,17 +18,22 @@ public class ConcertKopisApiLog {
1918
@CreationTimestamp
2019
LocalDateTime createdDate;
2120

22-
String title;
21+
@Column(name ="work_type",nullable = false)
22+
String workType;
2323

24+
@Column(name = "status")
25+
String status;
26+
27+
@Column(name ="description")
2428
String description;
2529

30+
@Column(name = "back_up_index")
2631
Long backUpIndex;
2732

28-
public ConcertKopisApiLog(Long id, LocalDateTime createdDate, String title, String description, Long backUpIndex) {
29-
this.id = id;
30-
this.createdDate = createdDate;
31-
this.title = title;
33+
public ConcertKopisApiLog(String workType, String status, String description, Long backUpIndex) {
34+
this.workType = workType;
3235
this.description = description;
36+
this.status = status;
3337
this.backUpIndex = backUpIndex;
3438
}
3539
}

src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/ConcertUpdateTime.java

Lines changed: 0 additions & 31 deletions
This file was deleted.

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,45 @@
22

33
import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertKopisApiLog;
44
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.jpa.repository.Query;
56
import org.springframework.stereotype.Repository;
67

8+
import java.time.LocalDateTime;
9+
import java.util.List;
10+
import java.util.Optional;
11+
import java.util.OptionalLong;
12+
713
@Repository
814
public interface ConcertKopisApiLogRepository extends JpaRepository<ConcertKopisApiLog, Long> {
15+
List<ConcertKopisApiLog> getConcertKopisApiLogByStatusAndWorkType(String status, String workType);
16+
17+
@Query("""
18+
SELECT
19+
createdDate
20+
FROM
21+
ConcertKopisApiLog
22+
ORDER BY
23+
createdDate
24+
ASC
25+
LIMIT
26+
1
27+
""")
28+
LocalDateTime getLastSaveTime();
29+
30+
boolean existsConcertKopisApiLogByStatusAndWorkType(String status, String workType);
31+
32+
@Query("""
33+
SELECT
34+
backUpIndex
35+
FROM
36+
ConcertKopisApiLog
37+
WHERE
38+
status = 'fail'
39+
ORDER BY
40+
createdDate
41+
ASC
42+
LIMIT
43+
1
44+
""")
45+
Optional<Long> getLastFailIndex();
946
}

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

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.back.web7_9_codecrete_be.domain.concerts.service;
2+
3+
import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertKopisApiLog;
4+
import com.back.web7_9_codecrete_be.domain.concerts.repository.ConcertKopisApiLogRepository;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.stereotype.Service;
7+
8+
import java.time.LocalDateTime;
9+
import java.util.List;
10+
11+
@Service
12+
@RequiredArgsConstructor
13+
public class ConcertKopisApiLogService {
14+
private final ConcertKopisApiLogRepository concertKopisApiLogRepository;
15+
16+
public void saveErrorLog(String workType,Exception e,Long index) {
17+
ConcertKopisApiLog concertKopisApiLog = new ConcertKopisApiLog(workType,"fail", e.getMessage(), index);
18+
concertKopisApiLogRepository.save(concertKopisApiLog);
19+
}
20+
21+
public void saveSuccessLog(String workType,String description,Long index) {
22+
ConcertKopisApiLog concertKopisApiLog = new ConcertKopisApiLog(workType,"success", description, index);
23+
concertKopisApiLogRepository.save(concertKopisApiLog);
24+
}
25+
26+
public void saveStartLog(String workType,String description,Long index) {
27+
ConcertKopisApiLog concertKopisApiLog = new ConcertKopisApiLog(workType,"start", description, index);
28+
concertKopisApiLogRepository.save(concertKopisApiLog);
29+
}
30+
31+
public boolean isInitComplete() {
32+
return concertKopisApiLogRepository.existsConcertKopisApiLogByStatusAndWorkType("success","save");
33+
}
34+
35+
public LocalDateTime getLastSaveTime(){
36+
return concertKopisApiLogRepository.getLastSaveTime();
37+
}
38+
39+
public Long getLastSaveFailIndex(){
40+
return concertKopisApiLogRepository.getLastFailIndex().orElse(0L);
41+
}
42+
43+
}

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

Lines changed: 45 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ public class KopisApiService {
4545

4646
private final ConcertImageRepository imageRepository;
4747

48-
private final ConcertUpdateTimeRepository concertUpdateTimeRepository;
49-
50-
private final ConcertKopisApiLogRepository concertKopisApiLogRepository;
48+
private final ConcertKopisApiLogService concertKopisApiLogService;
5149

5250
private final ConcertRedisRepository concertRedisRepository;
5351

@@ -59,24 +57,20 @@ public class KopisApiService {
5957

6058
private final RestClient restClient;
6159

62-
private int savedIndex;
63-
6460
public KopisApiService(
6561
ConcertRepository concertRepository,
6662
ConcertPlaceRepository placeRepository,
6763
TicketOfficeRepository ticketOfficeRepository,
6864
ConcertImageRepository imageRepository,
69-
ConcertUpdateTimeRepository concertUpdateTimeRepository,
7065
ConcertRedisRepository concertRedisRepository,
71-
ConcertKopisApiLogRepository concertKopisApiLogRepository
66+
ConcertKopisApiLogService kopisApiLogService
7267
) {
7368
this.concertRepository = concertRepository;
7469
this.placeRepository = placeRepository;
7570
this.ticketOfficeRepository = ticketOfficeRepository;
7671
this.imageRepository = imageRepository;
77-
this.concertUpdateTimeRepository = concertUpdateTimeRepository;
7872
this.concertRedisRepository = concertRedisRepository;
79-
this.concertKopisApiLogRepository = concertKopisApiLogRepository;
73+
this.concertKopisApiLogService = kopisApiLogService;
8074
this.restClient = RestClient.builder()
8175
.baseUrl("https://kopis.or.kr/openApi/restful")
8276
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
@@ -88,12 +82,12 @@ public KopisApiService(
8882
@Transactional
8983
public void setConcertsList() throws InterruptedException {
9084
// 최초 시작 시간 저장
91-
if(concertUpdateTimeRepository.count() != 0) {
85+
if(concertKopisApiLogService.isInitComplete()) {
9286
log.error("이미 최초 저장이 되었습니다!. UpdateConcert를 통해 데이터를 갱신해주십시오!");
9387
return;
9488
}
95-
String key = "init";
9689

90+
String key = "kopis_lock";
9791
String value = concertRedisRepository.lockGet(key);
9892
if(value != null) {
9993
log.error("이미 실행중인 스레드입니다.");
@@ -102,6 +96,8 @@ public void setConcertsList() throws InterruptedException {
10296
concertRedisRepository.lockSave(key,"running...");
10397
}
10498

99+
// 시작 시간
100+
concertKopisApiLogService.saveStartLog("save","공연 데이터 초기 저장 시작",0L);
105101
LocalDateTime now = LocalDateTime.now();
106102
long startNs = System.currentTimeMillis();
107103

@@ -116,22 +112,16 @@ public void setConcertsList() throws InterruptedException {
116112
int addedConcertImages = 0;
117113

118114
int page = 1;
119-
try{
115+
116+
// 이전 실패가 있으면 실패 시점에서 다시 시도 아니면 0 에서 시작
117+
Long index = concertKopisApiLogService.getLastSaveFailIndex();
118+
try {
120119
// 모든 공연을 가져오기
121120
totalConcertsList = getAllConcertsListFromKopisAPI(page);
122-
}catch (Exception e){
123-
log.error("공연 목록 저장 도중 오류 발생");
124-
log.error("오류 내용 : " + e.getMessage());
125-
return;
126-
} finally {
127-
concertRedisRepository.unlockSave(key);
128-
}
121+
log.info("저장할 총 공연의 수: {}", totalConcertsList.size());
122+
log.info("공연 목록 로드 완료, 공연 세부 내용 로드 및 저장");
129123

130-
concertRedisRepository.lockSave(key,"running...");
131-
log.info("저장할 총 공연의 수: {}", totalConcertsList.size());
132-
log.info("공연 목록 로드 완료, 공연 세부 내용 로드 및 저장");
133-
try {
134-
for(int i = savedIndex; i < totalConcertsList.size(); i++) {
124+
for(int i = index.intValue(); i < totalConcertsList.size(); i++) {
135125
ConcertListElement concertListElement = totalConcertsList.get(i);
136126
// API에서 공연 상세 가져오기
137127
ConcertDetailResponse concertDetailResponse = getConcertDetailResponse(serviceKey, concertListElement.getApiConcertId());
@@ -143,7 +133,6 @@ public void setConcertsList() throws InterruptedException {
143133
String concertPlaceAPiKey = concertDetailResponse.getConcertDetail().getMt10id();
144134
// 캐시로 사용하는 맵이나 DB에서 콘서트 위치가 있는지 확인하기 -> 없으면 저장
145135
ConcertPlace concertPlace = getConcertPlaceOrSaveNewConcertPlace(concertPlaceMap, concertPlaceAPiKey);
146-
147136
addedConcertPlaces = concertPlaceMap.size();
148137
// 공연 저장
149138
Concert savedConcert = saveConcert(concertPlace, concertDetail);
@@ -153,37 +142,38 @@ public void setConcertsList() throws InterruptedException {
153142
addedConcertImages += saveConcertImages(concertDetail, savedConcert);
154143

155144
addedConcerts++;
156-
savedIndex++;
145+
index++;
157146
}
147+
148+
concertKopisApiLogService.saveSuccessLog("save","공연 데이터 초기 저장 완료",0L);
149+
log.info(now + "시 기준 " + totalConcertsList.size() + "개의 공연 데이터 저장 완료!");
150+
long endNs = System.currentTimeMillis();
151+
long durationSec = ((endNs - startNs) / 1000);
152+
log.info(durationSec/60 + "분, " + durationSec % 60 + "초 소요되었습니다." );
153+
cacheClear();
158154
} catch (Exception e) {
159155
log.error("개별 공연 세부 내용 저장 도중 오류 발생");
160156
log.error("오류 내용 : " + e.getMessage());
161-
e.printStackTrace();
162-
return ;
157+
concertKopisApiLogService.saveErrorLog("save",e,index);
163158
} finally {
164159
concertRedisRepository.unlockSave(key);
165160
}
166-
ConcertUpdateTime concertUpdateTime = new ConcertUpdateTime(now);
167-
concertUpdateTimeRepository.save(concertUpdateTime);
168-
savedIndex = 0;
169-
log.info(now + "시 기준 " + totalConcertsList.size() + "개의 공연 데이터 저장 완료!");
170-
long endNs = System.currentTimeMillis();
171-
long durationSec = ((endNs - startNs) / 1000);
172-
log.info(durationSec/60 + "분, " + durationSec % 60 + "초 소요되었습니다." );
173161
}
174162

175163
@Transactional
176164
public SetResultResponse updateConcertData() throws InterruptedException {
177-
String key = "init";
165+
String key = "kopis_lock";
178166
String value = concertRedisRepository.lockGet(key);
179167
if(value != null) {
180-
log.error("초기 업데이트가 진행중입니다.");
168+
log.error("락이 걸린 작업입니다.");
181169
return null;
170+
} else{
171+
concertRedisRepository.lockSave(key,"running...");
182172
}
183173

184-
ConcertUpdateTime concertUpdateTime = concertUpdateTimeRepository.getReferenceById(1L);
185-
LocalDate lastUpdatedDate = concertUpdateTime.getUpdateTime().toLocalDate();
186-
ConcertUpdateTime updatedTime = concertUpdateTime.setUpdateTime(LocalDateTime.now());
174+
175+
LocalDate lastUpdatedDate = concertKopisApiLogService.getLastSaveTime().toLocalDate();
176+
concertKopisApiLogService.saveStartLog("save","공연 데이터 업데이트 시작",0L);
187177
LocalDate sdate = lastUpdatedDate;
188178
LocalDate edate = LocalDate.now().plusYears(1);
189179

@@ -213,8 +203,7 @@ public SetResultResponse updateConcertData() throws InterruptedException {
213203
}
214204
log.info("공연 목록 로드 완료, 공연 세부 내용 로드 및 저장");
215205

216-
savedIndex = 0;
217-
for(int i = savedIndex; i < totalConcertsList.size(); i++) {}
206+
for(int i = 0; i < totalConcertsList.size(); i++) {}
218207
for (ConcertListElement performanceListElement : totalConcertsList) {
219208
ConcertDetailResponse concertDetailResponse = getConcertDetailResponse(serviceKey, performanceListElement.getApiConcertId());
220209
ConcertDetailElement concertDetail = concertDetailResponse.getConcertDetail();
@@ -250,15 +239,17 @@ public SetResultResponse updateConcertData() throws InterruptedException {
250239
Thread.sleep(300);
251240
}
252241

253-
// 갱신 후 업데이트 시간 저장, 캐시의 데이터 삭제
254-
concertUpdateTimeRepository.save(updatedTime);
255-
concertRedisRepository.deleteAllConcertsList();
256-
concertRedisRepository.deleteAllCachedConcertDetail();
257-
concertRedisRepository.deleteTotalConcertsCount(ListSort.VIEW);
242+
// 갱신 후 업데이트 시간 저장
243+
concertKopisApiLogService.saveSuccessLog("save","공연 데이터 업데이트 완료",0L);
244+
// 락 해제
245+
concertRedisRepository.unlockSave(key);
246+
// 이전 캐시 데이터 삭제
247+
cacheClear();
258248
return new SetResultResponse(addedConcerts,updatedConcerts,addedConcertPlaces,updatedConcertPlaces,addedConcertImages,updatedConcertImages,addedTicketOffices,updatedTicketOffices);
259249
}
260250

261251

252+
262253
@Transactional
263254
public void concertUpdateByKopisApi(Long concertId){
264255
// 해당 콘서트 ID로 콘서트 객체 찾기
@@ -388,6 +379,13 @@ private int saveConcertImages(ConcertDetailElement concertDetail, Concert savedC
388379
return concertDetail.getConcertImageUrls().size();
389380
}
390381

382+
// 캐시를 초기화합니다.
383+
private void cacheClear() {
384+
concertRedisRepository.deleteAllConcertsList();
385+
concertRedisRepository.deleteAllCachedConcertDetail();
386+
concertRedisRepository.deleteTotalConcertsCount(ListSort.VIEW);
387+
}
388+
391389
public ConcertListResponse getConcertsList() {
392390
return getConcertListResponse(serviceKey, sdate, edate, 1);
393391
}

0 commit comments

Comments
 (0)