sovled.ac에 호출을 적게하려고 노력하다보니 생각할것이 많아졌다. 확실히 내 로직은 빠른데 데이터값을 받아오는데 시간이 걸린다. 중복처리를 안하고 설계를 했다보니 db에 중복으로 데이타가 들어간걸 보고 맨붕 그래서 중복처리 하는김에 로직도 조금씩 리펙토링 했다. 정리가 잘안되서 두서없이 글쓸꺼 같아서 죄송합니다.
api로 user가 푼문제 받아 problem이랑 mapping해서 DB에 저장하기
간단 로직 설명
1. GET /solved/user
@GetMapping("/user")
public void fetchAndSaveUser() throws IOException, InterruptedException{
String username = "parkswon1";
int page = 1;
User user = userService.findByName(username);
if (user == null){
userService.saveUserByName(username);
user = userService.findByName(username);
}
String uri = SolvedAPI.getUserSolvedProblemByName(username, page);
HttpResponse<String> response = SolvedAPI.solvedacAPIRequest(uri);
JsonObject JsonUser = parser.parse(response.body()).getAsJsonObject();
userProblemMappingService.saveUserProblemMapping(JsonUser, user);
int endPage = (JsonUser.get("count").getAsInt() / 50) + 1;
for (page = 2; page <= endPage; page++){
uri = SolvedAPI.getUserSolvedProblemByName(username, page);
response = SolvedAPI.solvedacAPIRequest(uri);
JsonUser = parser.parse(response.body()).getAsJsonObject();
userProblemMappingService.saveUserProblemMapping(JsonUser, user);
}
}
아직 client쪽 로직을 구현을 안해서 username을 일단 내 solved.ac아이디로 넣어두고 만들었다. 문제를 가져올때는 한번에 가져올 수 있었지만 user가 푼문제경우 page를 나눠서 가져와야 한다. 처음에 1페이지를 받아오면 총 문제의 개수를 알려주는데 한번에 50개씩 보내줘서 총 문제의 개수를 50으로 나누고 +1해서 모든 페이지의 문제를 읽어올 수 있도록 변경했다.
2. UserProblemMappingService
public class UserProblemMappingService {
private final UserProblemMappingRepository userProblemMappingRepository;
private final ProblemService problemService;
//user객체랑 problem객체가 연관되도록 매핑(매핑이 기존에 있는지 검사는 로직으로 중복 방지)
public void saveUserProblemMapping(JsonObject jsonObject, User user) {
JsonArray jsonArray = jsonObject.get("items").getAsJsonArray();
// 받은 문제 ID들 추출
Set<Long> problemIds = new HashSet<>();
for(int i = 0; i < jsonArray.size(); i++){
JsonObject jsonProblem = jsonArray.get(i).getAsJsonObject();
problemIds.add(jsonProblem.get("problemId").getAsLong());
}
// User가 이미 푼 문제들 가져오기
List<UserProblemMapping> existingMappings = userProblemMappingRepository.findByUser(user);
Set<Long> existingProblemIds = existingMappings.stream()
.map(mapping -> mapping.getProblem().getProblemId())
.collect(Collectors.toSet());
//새로운 푼 문제들 추출
Set<Long> newProblemIds = problemIds.stream()
.filter(problemId -> !existingProblemIds.contains(problemId))
.collect(Collectors.toSet());
//새로운 문제들 DB에서 한번에 가져오기
Map<Long, Problem> problemMap = problemService.findByIds(newProblemIds).stream()
.collect(Collectors.toMap(Problem::getProblemId, problem -> problem));
List<UserProblemMapping> newMappings = new ArrayList<>();
for (Long problemId : newProblemIds){
Problem problem = problemMap.get(problemId);
if (problem != null){
UserProblemMapping userProblemMapping = new UserProblemMapping();
userProblemMapping.setProblem(problem);
userProblemMapping.setUser(user);
newMappings.add(userProblemMapping);
}
}
if (!newMappings.isEmpty()){
userProblemMappingRepository.saveAll(newMappings);
}
}
}
아직 자바 코드에 익숙치 않아서 나중에 까먹지 않으려고 주석을 좀 달았다.
- 사용자가 푼 문제들을 가져오기
- 사용자가 새로 푼문제가 있는지 확인하기
- 새로 푼문제들만 Mappingtalbe로 들어가도록 리파지토리에 요청
3. UserProblemMappingRepository
List<UserProblemMapping> findByUser(User user);
user객체를 받아와서 user랑 관련된 문제를 List로 반환 사실 Set<Long>으로 반환시켜서 service에서 형변환을 안하게 시키고 싶었는데 JPA에서 set형 반환을 하지 않는다고하다.
4. saveProblems
public void saveProblems(JsonArray jsonProblems) {
List<Problem> problemsToSave = new ArrayList<>();
List<ProblemTagMapping> mappingsToSave = new ArrayList<>();
for (int i = 0; i < jsonProblems.size(); i++) {
if (!jsonProblems.get(i).isJsonNull()) {
JsonObject jsonProblem = jsonProblems.get(i).getAsJsonObject();
Long problemId = jsonProblem.get("problemId").getAsLong();
// 문제의 중복 여부 확인
if (!problemRepository.existsByProblemId(problemId)) {
Problem problem = new Problem();
problem.setProblemId(problemId);
problem.setLevel(jsonProblem.get("level").getAsInt());
problem.setAcceptedUserCount(jsonProblem.get("acceptedUserCount").getAsLong());
JsonArray titles = jsonProblem.getAsJsonArray("titles");
for (int j = 0; j < titles.size(); j++) {
JsonObject title = titles.get(j).getAsJsonObject();
if (title.get("language").getAsString().equals("ko")) {
problem.setTitle(title.get("title").getAsString());
break;
} else {
problem.setTitle(title.get("title").getAsString());
}
}
problemsToSave.add(problem);
// 문제의 태그들 처리
JsonArray tags = jsonProblem.getAsJsonArray("tags");
for (int n = 0; n < tags.size(); n++) {
JsonObject tag = tags.get(n).getAsJsonObject();
Long tagId = tag.get("bojTagId").getAsLong();
ProblemTag problemTag = problemTagService.findById(tagId);
if (problemTag != null) {
ProblemTagMapping problemTagMapping = new ProblemTagMapping();
problemTagMapping.setProblem(problem);
problemTagMapping.setProblemTag(problemTag);
mappingsToSave.add(problemTagMapping);
}
}
}
}
}
// 문제와 매핑을 한 번에 저장
problemRepository.saveAll(problemsToSave);
problemTagMappingService.saveAll(mappingsToSave);
}
문제 id가 없다면 문제를 저장하고 그 문제에 대한 Tag를 저장한다.
직면한 문제
1. user랑 problem 매핑하기
2. 문제를 update안해서 user가 푼문제에는 있는데 문제에는 user가 푼 문제가 없을수 있다.
3. uesr가 들어오면 이름으로 save해주는데 이로인해 table에 같은 이름을 가진 user가 2개가 들어가는 사고 발생
4. user랑 problemmapping는 시간이 11.1초나 걸림
거의 api호출 시간이 문제
5. 같은 문제가 똑같이 들어가는 문제가 생김
6. id값이 너무 빠르게 증가하는거 같음 그래서 id값이 있으면 update하는 형식으로 변경 10.3초로 줄음
7. problem이랑 tag랑 매핑하는 코드를 problem이 있다면 진행하지 않는 것으로 변경해서 속도를 올림 tag가 수정되는 경우는 거의 없다고 봐도 무방하니까
(사실 체점이 될 문제는 태그가 없긴한데 테그가 없는 문제까지지 추천하려면 문제 전부를 업데이트 해야하는데 solvedapi를 사용하는 나는 최소한으로 api를 호출하고 싶어서 문제 업데이트는 내가 수동으로 db지우고 하는 식으로 만드려고 한다. 주기적 업데이트가 아닌 필요에 의한 업데이트, 이미 있는 문제로만 추천해도 충분하지 않을까? 물로 내생각)
8. 호출제한
9. truncate table tableName db지울때 사용하니까 id 값도 초기화되서 기분이 좋다.
10. user를 problem이랑 mapping할때 11.9초나 걸리는 문제 -> 문제를 db에서 매번 검사하는 로직 -> 한번에 문제들을 다 가져오고 검사하도록하는 로직으로 변경 했더니 16초가 걸림 흠...
-> user가 푼문제를 저장하고 user가 푼문제가 새로 생기면 problemuesrMaping에 추가할까 생각중
이래도 11.3초가 걸림 이걸로 측정해보니까
0.136초가 걸리는데 역시 받아오는 시간이 문제인듯 ㅠㅠ
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println("saveUserProblemMapping execution time: " + duration + " milliseconds");
11. findByUser할때 id로 찾지 않고 user로 찾는게 객체 지향적으로 좋다함
'Project : 백준 문제 추천 서비스 > 개발일지' 카테고리의 다른 글
[백준 문제 추천/개발 일지] (5) 추천 시스템 구성 + 코드 리팩토링 (1) | 2024.07.01 |
---|---|
[백준 문제 추천/개발 일지] (3) 문제 DB에 저장하기 + tag랑 Mapping하기 (1) | 2024.06.11 |
[백준 문제 추천/개발 일지] (2) 문제 Tag DB에 저장하기 + 회고 (0) | 2024.06.04 |
[백준 문제 추천/개발 일지] (1) 개발 이유 + sovled.ac API 사용하기 (0) | 2024.06.01 |
Coding, Software, Computer Science 내가 공부한 것들 잘 이해했는지, 설명할 수 있는지 적는 공간