![[백준 문제 추천/개발 일지] (3) 문제 DB에 저장하기 + tag랑 Mapping하기](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FemFhFx%2FbtsHUNxbK32%2FXrAobcJ49PaqIdg8nRyAm0%2Fimg.png)
백준 문제 추천
db를 만지다가 애를 먹었다. ManytoMany를 사용하는 바보같은 코딩을 하다가 정규화를 해야한다는걸 늦게나마 자각하고 다시한번 설계의 중요성을 느꼈다. jpa에 여러가지 기능이 있는거 같은데 일단 시간이 없으니 나중에 리펙토링할 때 한번 사용해보자고 생각하며 마무리했다.
sovled.ac api로 Problem 받아 DB에 저장하기
간단 로직 설명
1. GET /solved/problem
@GetMapping("/problem")
public void fetchAndSaveProblem() throws IOException, InterruptedException {
int start = 1000;
int end = 31980;
int batchSize = 50;
for (int i = start; i <= end; i += batchSize) {
List<Integer> problems = new ArrayList<>();
for (int j = i; j < i + batchSize && j <= end; j++) {
problems.add(j);
}
String uri = SolvedAPI.getProblemByArray(problems);
HttpResponse<String> response = SolvedAPI.solvedacAPIRequest(uri);
JsonArray JsonProblems = parser.parse(response.body()).getAsJsonArray();
problemService.saveProblems(JsonProblems);
}
}
현재 sovled.ac상에 문제가 31980개가 있다. 한번 요청으로 몇개까지 문제를 한번에 받아올수 있는지 확인은 안해봤으나 50개의 문제를 요청하는건 들어줘서 50씩 나눠서 요청하기로 생각
but 15분에 256요청만 sovled.ac로 보낼 수 있기 때문에 이는 나중에 따로 로직을 만들 생각이다.
2. DB에 문제를 저장하는 메소드
//문제들을 저장하는 메소드
public void saveProblems(JsonArray JsonProblems){
//문제의 정보를 Problem리파지토리를 통해서 저장
for (int i = 0; i < JsonProblems.size(); i++){
if (!JsonProblems.get(i).isJsonNull()){
JsonObject jsonProblem = JsonProblems.get(i).getAsJsonObject();
Problem problem = new Problem();
problem.setProblemId(jsonProblem.get("problemId").getAsLong());
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());
}
}
problemRepository.save(problem);
//문제의 Tag들을 problemTagMappingTable에 저장
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();
//problemTagService에서 tagid가 있는지 검사 있어야 저장함
ProblemTag problemTag = problemTagService.findById(tagId);
if (problemTag != null) {
ProblemTagMapping problemTagMapping = new ProblemTagMapping();
problemTagMapping.setProblem(problem);
problemTagMapping.setProblemTag(problemTag);
problemTagMappingService.saveProblemTagMapping(problemTagMapping);
}
}
}
}
}
문제의 정보를 저장하면서 mappingtable에도 저장하는 메소드
메소드를 나눠서 진행할까 생각하다가 jsonArray의 양이 매우 많기 때문에 메소드를 많이 생성하면 좋지 않을꺼 같아서 하나의 메소드로 만들었다. 나중에 병렬처리로 속도를 올려볼 예정
Mapping관련된 테이블들은 다 Mapping폴더를 생성해서 관리할 예정이다.
직면한 문제
1. JsonArray, JsonObject차이로 인한 response방식의 개편
problem을 여러개 받아올때 tag처럼 JsonObject안에 JsonArray로 담아주는 줄 알았는데 처음부터 JsonArray로 보내줬다.
따라서 기존SolvedAPI를 받아오는 메소드를 변경
public static HttpResponse<String> solvedacAPIRequest(String uri) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.header("x-solvedac-language", "ko")
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
return response;
}
return JsonObject를 하는 것에서 response를 직접 보내줘서 컨트롤러에서 받아 알아서 처리하도록 해 재사용성을 늘렸다.
2. sql postgre문법 사용시 schema사용법
postgre에서는 schema를 사용 안하는줄 알았는데 사용할 수가 있었다.
Database - > schema - > table 순이였다.
내가 data들을 schema안에 넣어뒀기 떄문에 schema사용하는 sql문을 사용해야 한다.
ALTER TABLE algoshuffer.problems
ADD COLUMN accepted_user_count INTEGER;
이런 방식을 사용해서 table을 수정해야 할 때 수정 했다.
3. ManyToMany -> OneToMany, ManyToOne
처음에는 Problem이랑 ProblemTag을 ManyToMany로 연결하려고 했는데,
사용하기 까다롭기도 하고 정규화를 진행하기 위해서 이구조를 Mappingtable을 집어 넣어서 변경
CREATE TABLE problem_tag_mapping (
id SERIAL PRIMARY KEY,
problem_id BIGINT,
boj_tag_id BIGINT,
FOREIGN KEY (problem_id) REFERENCES Problem (problem_id),
FOREIGN KEY (boj_tag_id) REFERENCES ProblemTag (boj_tag_id)
);
Mappingtalbe을 이렇게 만들었다. 아래는 Entitiy를 수정한 모
@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "problems")
public class Problem {
@Id
@Column(name = "problem_id")
private Long problemId;
@Column(name = "title", nullable = true)
private String title;
@Column(name = "level", nullable = true)
private int level;
@Column(name = "accepted_user_count")
private Long acceptedUserCount;
@OneToMany(mappedBy = "problem")
private Set<ProblemTagMapping> problemTagMappings = new HashSet<>();
}
@Entity
@Getter
@Setter
@Table(name = "problem_tag")
@ToString
public class ProblemTag {
@Id
@Column(name = "boj_tag_id")
private Long bojTagId;
@Column(name = "display_name")
private String displayName;
@Column(name = "display_name_sort")
private String displayNameShort;
@Column(name = "key")
private String key;
@OneToMany(mappedBy = "problemTag")
private Set<ProblemTagMapping> problemTagMappings = new HashSet<>();
}
4. title이 null로 들어오는 문제
내가 response data를 제대로 확인하지 않아서 생긴 문제
처음에는 ko로 된 한국어로 된 제목만 받아올수 있게 만들었는데 한국어 제목이 없는 문제가 많았다.
그래서 DB가 null로 가득 차고 title이 notnull로 되어 있었기 때문에 에러가 나는 상황
그래서 ko가 있다면 한글 이름으로 저장하도록 했고 만약 ko가 없으면 그냥 있는 제목으로 저장하도록 수정
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());
}
}
'Project : 백준 문제 추천 서비스 > 개발일지' 카테고리의 다른 글
[백준 문제 추천/개발 일지] (5) 추천 시스템 구성 + 코드 리팩토링 (1) | 2024.07.01 |
---|---|
[백준 문제 추천/개발 일지] (4) Mapping table 중복 문제 + api호출 요청을 줄이기 위한 노력? (0) | 2024.06.13 |
[백준 문제 추천/개발 일지] (2) 문제 Tag DB에 저장하기 + 회고 (0) | 2024.06.04 |
[백준 문제 추천/개발 일지] (1) 개발 이유 + sovled.ac API 사용하기 (0) | 2024.06.01 |
Coding, Software, Computer Science 내가 공부한 것들 잘 이해했는지, 설명할 수 있는지 적는 공간