MVP 개발이 완료되면서 도전 기능 개발에 도입하며 개발 완료에 박차를 가하는 시점,
또 다시 리팩토링이 필요하게 되었다.

 

기존 카테고리 엔티티 변경

❓카테고리 변경이 필요해진 이유

기존에는 대분류를 통해 어느 직무에 해당하는 문제인지만 구분해왔었다.
백엔드 문제인가, 프론트엔드 문제인가 하는

그런데 분야의 카테고리별 정답률 계산이라는 도전 기능이 추가되면서
소분류의 카테고리를 추가할 필요가 생겼다.

백엔드 문제에서도 네트워크 분야인지, 데이터 베이스 분야인지 구분하는 카테고리 말이다.

 

✅ 카테고리 변경 방법

카테고리 관리에 주로 사용되는 계층형 엔티티를 도입했다.

  • JPA 기반
  • 부모와 자식 관계 추가
//대분류
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private QuizCategory parent;

//소분류
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<QuizCategory> children = new ArrayList<>();

대분류와 소분류로만 나누면 되고 더 깊고 자세하게 나눌 필요는 없었기 때문에 depth는 고려하지 않았다.

 

✅ 변경된 카테고리 기반 기능 구현

1) 프론트에 전송하기 위한 대분류 카테고리 조회

    @Transactional(readOnly = true)
    public List<String> getParentQuizCategoryList() {
        return quizCategoryRepository.findByParentIdIsNull() //대분류만 찾아오도록 변경
            .stream()
            .map(QuizCategory::getCategoryType)
            .toList();
    }

부모는 parent가 null이므로 이에 해당하는 데이터만 조회하도록 하였다.

 

2) 소분류 카테고리별 정답률 계산

  1. 유저 Id 기반, 해당 유저의 구독 정보에 해당하는 대분류 카테고리 조회
  2. 대분류 카테고리에 따른 소분류 카테고리 조회
  3. 각 소분류에 해당하는 유저가 푼 문제 로그 조회
  4. 조회된 데이터들 중 정답인 데이터 필터링
  5. 정답률 계산
  public CategoryUserAnswerRateResponse getUserQuizAnswerCorrectRate(AuthUser authUser) {

        //유효성 검증 로직은 생략
        
        Long userId = user.getId();

        //유저 Id에 따른 구독 정보의 대분류 카테고리 조회
        QuizCategory parentCategory = quizCategoryRepository.findQuizCategoryByUserId(userId);

        //소분류 조회 -> getChildren()에서 실제 childCategories를 조회해오기 때문에 아래에서 이를 사용할 때 N+1 문제가 발생하지 않음
        List<QuizCategory> childCategories = parentCategory.getChildren();

        Map<String, Double> rates = new HashMap<>();
        //유저가 푼 문제들 중, 소분류에 속하는 로그 다 가져와
        for (QuizCategory child : childCategories) {
            List<UserQuizAnswer> answers = userQuizAnswerRepository.findByUserIdAndQuizCategoryId(
                userId, child.getId());

            if (answers.isEmpty()) {
                rates.put(child.getCategoryType(), 0.0);
                continue;
            }

            long totalAnswers = answers.size();
            long correctAnswers = answers.stream()
                .filter(UserQuizAnswer::getIsCorrect) // 정답인 경우 필터링
                .count();

            double answerRate = (double) correctAnswers / totalAnswers * 100;
            rates.put(child.getCategoryType(), answerRate);
        }

        return CategoryUserAnswerRateResponse.builder()
            .correctRates(rates)
            .build();
    }

 

✅ 프론트 적용

팀원이 구현해주신 프론트 적용 결과!

매우 잘 나온다 헤헷

+ Recent posts