[졸프]answerCount 데이터 불일치 트러블 슈팅

ssun·2025년 7월 9일

졸업프로젝트

목록 보기
8/21
post-thumbnail

질문 상세 조회 화면

질문 검색 결과 화면

질문 리스트 조회 화면

현재 해당 질문에 실제 댓글은 3개가 달려있지만, 질문 검색 결과 화면에서는 5개로 나오고 있다. 질문 상세 조회 화면, 질문 리스트 조회 화면에서는 정상적으로 출력된다.

관련된 코드를 보자

1. QuestionResponseDto.java

package qnb.question.dto;

import qnb.book.dto.BookResponseDto;
import qnb.question.entity.Question;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter

public class QuestionResponseDto {
    private final BookResponseDto book;    // 도서
    private final Long userId;             // 질문 작성자 ID
    private final Integer questionId;      // 질문 ID
    private final String userNickname;     // 질문 작성자 닉네임
    private final String profileUrl;       //사용자 프로필
    private final String questionContent;  // 질문 내용
    private final Integer answerCount;     //답변 수
    private final Integer likeCount;       // 좋아요 수
    private final Integer scrapCount;      // 스크랩 수
    private final LocalDateTime createdAt; // 생성일시

    public QuestionResponseDto(
            BookResponseDto book, Long userId, Integer questionId,
            String userNickname, String profileUrl, String questionContent,
            Integer answerCount, Integer likeCount, Integer scrapCount,
            LocalDateTime createdAt) {

        this.book = book;
        this.userId = userId;
        this.questionId = questionId;
        this.userNickname = userNickname;
        this.profileUrl = profileUrl;
        this.questionContent = questionContent;
        this.answerCount = answerCount;
        this.likeCount = likeCount;
        this.scrapCount = scrapCount;
        this.createdAt = createdAt;
    }

    public static QuestionResponseDto from(Question question, int answerCount) {
        return new QuestionResponseDto(
                BookResponseDto.from(question.getBook()),
                question.getUser().getUserId(),
                question.getQuestionId(),
                question.getUser().getUserNickname(),
                question.getUser().getProfileUrl(),
                question.getQuestionContent(),
                answerCount,
                question.getLikeCount(),
                question.getScrapCount(),
                question.getCreatedAt()
        );
    }
}

여기서 실시간은 답변 수를 받기위해서 question.getAnswerCount()가 아니라 answerCount를 사용하고 있다.

2. QuestionService.java 중 질문 상세 조회 메소드

//질문 상세 조회
    public QuestionDetailResponseDto getQuestionDetail(Long questionId, String sort) {
        Question question = questionRepository.findById(Math.toIntExact(questionId))
                .orElseThrow(QuestionNotFoundException::new);

        List<Answer> answers = answerRepository.findByQuestion_QuestionId(questionId);

        // 상태 우선순위 정의
        Map<String, Integer> stateOrder = Map.of(
                "BEFORE", 0,
                "READING", 1,
                "AFTER", 2
        );

        // 정렬 기준: 인기순 or 상태 + 최신순
        Comparator<Answer> comparator = "popular".equals(sort)
                ? Comparator.comparing(Answer::getLikeCount, Comparator.nullsLast(Integer::compareTo)).reversed()
                : Comparator
                .comparing((Answer a) -> {
                    String state = a.getAnswerState();
                    return state != null ? stateOrder.getOrDefault(state.toUpperCase(), 99) : 99;
                })
                .thenComparing(Answer::getCreatedAt, Comparator.nullsLast(LocalDateTime::compareTo));

        // 질문 정보 DTO로 변환
        QuestionResponseDto questionDto = QuestionResponseDto.from(question, answers.size());

        // 사용자 기준으로 묶기 (정렬 적용)
        Map<Long, List<Answer>> grouped = answers.stream()
                .filter(answer -> answer.getUser() != null)
                .sorted(comparator)
                .collect(Collectors.groupingBy(
                        answer -> answer.getUser().getUserId(),
                        LinkedHashMap::new,
                        Collectors.toList()
                ));

        List<AnswersByUserDto> answersByUser = new ArrayList<>();
        for (Map.Entry<Long, List<Answer>> entry : grouped.entrySet()) {
            Long userId = entry.getKey();

            User user = userRepository.findById(userId)
                    .orElseThrow(UserNotFoundException::new);

            List<AnswerResponseDto> answerDtos = entry.getValue().stream()
                    .sorted(
                            Comparator
                                    .comparing((Answer a) -> {
                                        String state = a.getAnswerState();
                                        return state != null ? stateOrder.getOrDefault(state.toUpperCase(), 99) : 99;
                                    })
                                    .thenComparing(Answer::getCreatedAt, Comparator.nullsLast(LocalDateTime::compareTo))
                    )
                    .map(answer -> AnswerResponseDto.from(
                            answer,
                            user.getUserId().toString(),
                            user.getUserNickname() != null ? user.getUserNickname() : "알 수 없음",
                            user.getProfileUrl() != null ? user.getProfileUrl() : ""
                    ))
                    .collect(Collectors.toList());

            answersByUser.add(new AnswersByUserDto(user, answerDtos));
        }

        return new QuestionDetailResponseDto(questionDto, answersByUser);
    }

// 질문 정보 DTO로 변환
QuestionResponseDto questionDto = QuestionResponseDto.from(question, answers.size());
- 이 부분에서 실시간으로 받기위해서 answers.size()를 사용하고 있다.

3. SearchService.java에서 질문 검색 결과 부분

//2. 질문 검색 결과
        else if (type.equals("QUESTION")) {
            //키워드 없을 때
            if (keyword == null || keyword.trim().isEmpty()) {
                QuestionPageResponseDto recentResult = questionService.getRecentQuestions(safePage, safeSize);

                List<QuestionSearchOneDto> resultList = recentResult.getQuestions().stream()
                        .map(q -> new QuestionSearchOneDto(
                                q.getQuestionId().longValue(),
                                q.getQuestionContent(),
                                new BookSimpleDto(  // BookResponseDto → BookSimpleDto 변환
                                        q.getBook().getBookId(),
                                        q.getBook().getTitle(),
                                        q.getBook().getImageUrl(),
                                        q.getBook().getAuthor(),
                                        q.getBook().getPublisher(),
                                        q.getBook().getPublishedYear()
                                ),
                                q.getAnswerCount(),
                                q.getLikeCount(),
                                q.getScrapCount(),
                                q.getUserNickname(),
                                q.getProfileUrl()
                        ))
                        .toList();

                return new QuestionSearchResponseDto(
                        resultList,
                        recentResult.getPageInfoDto()
                );
            }

            //키워드 존재할 때
            else {
                Page<Question> questions = questionRepository.searchQuestions(keyword, pageable);

                return new QuestionSearchResponseDto(
                        questions.getContent().stream()
                                .map(q -> new QuestionSearchOneDto(
                                        q.getQuestionId().longValue(),
                                        q.getQuestionContent(),
                                        new BookSimpleDto(
                                                q.getBook().getBookId(),
                                                q.getBook().getTitle(),
                                                q.getBook().getImageUrl(),
                                                q.getBook().getAuthor(),
                                                q.getBook().getPublisher(),
                                                q.getBook().getPublishedYear()

                                        ),
                                        q.getAnswerCount(),
                                        q.getLikeCount(),
                                        q.getScrapCount(),
                                        q.getUser().getUserNickname(),
                                        q.getUser().getProfileUrl()
                                ))
                                .toList(),
                        new PageInfoDto(
                                safePage,
                                questions.getTotalPages(),
                                (int) questions.getTotalElements())
                );
            }
        }

여기서는 q.getAnswerCount()을 사용하고 있다.

4. QuestionService.java 중에 최신 질문 조회 메소드

// 최신 질문 조회 메소드
    public QuestionPageResponseDto getRecentQuestions(int page, int size) {
        // page가 1보다 작으면 1로 보정
        int safePage = Math.max(page, 1);
        int safeSize = Math.min(Math.max(size, 1), 50);

        Pageable pageable = PageRequest.of(
                safePage - 1, //스프링은 0부터 시작하니까
                safeSize,
                Sort.by(Sort.Direction.DESC, "createdAt") //최신순 정렬
        );

        Page<Question> questionPage = questionRepository.findAll(pageable);

        //결과가 비었을 경우 처리
        if (questionPage.isEmpty()) {
            //빈 리스트로 반환
            List<QuestionResponseDto> questions = Collections.emptyList();

            PageInfoDto pageInfoDto = new PageInfoDto(
                    safePage,  // 클라이언트가 요청한 페이지 그대로 사용
                    questionPage.getTotalPages(),
                    questionPage.getTotalElements()
            );

            return new QuestionPageResponseDto(questions, pageInfoDto);
        }

        List<QuestionResponseDto> questions = questionPage.getContent().stream()
                .map(question -> QuestionResponseDto.from(question, 0))
                // answerCount는 0으로 고정
                .collect(Collectors.toList());

        PageInfoDto pageInfoDto = new PageInfoDto(
                // Spring의 0-based index → 1-based로 변환
                questionPage.getNumber() + 1,
                questionPage.getTotalPages(),
                questionPage.getTotalElements()
        );
        return new QuestionPageResponseDto(questions, pageInfoDto);
    }

.map(question -> QuestionResponseDto.from(question, 0)) 여기서는 이렇게 코드 사용했다.


DB에서 검색을 해보니 이 부분이 다르다. 현재 답변 개수는 3개이지만 answer_count는 5개다. 아마 삭제 메소드가 제대로 작동이 안되는 것 같다.

deleteAnswer 메소드

//답변 삭제
    @Transactional
    public void deleteAnswer(Long answerId, Long loginUserId) {
        Answer answer = answerRepository.findById(answerId)
                .orElseThrow(AnswerNotFoundException::new); // 404

        if (!answer.getUser().getUserId().equals(loginUserId)) {
            throw new UnauthorizedAccessException(); // 403
        }

        Question question = questionRepository.findById(answer.getQuestion().getQuestionId())
                .orElseThrow(QuestionNotFoundException::new);

        //질문 답변 수 감소
        question.setAnswerCount(Math.max(0, question.getAnswerCount() - 1));
        questionRepository.save(question);

        //응답 반환
        answerRepository.delete(answer);
    }

지금 현재

//질문 답변 수 감소
        question.setAnswerCount(Math.max(0, question.getAnswerCount() - 1));
        questionRepository.save(question); 

이 코드로 0 아래로 안떨어지게 설정해놨다.

SearchService.java에서 질문 검색 결과 부분에서 고쳐야할듯하다.

해당 코드 수정 버전

 //2. 질문 검색 결과
        else if (type.equals("QUESTION")) {
            // 키워드 없을 때
            if (keyword == null || keyword.trim().isEmpty()) {
                QuestionPageResponseDto recentResult = questionService.getRecentQuestions(safePage, safeSize);

                List<QuestionSearchOneDto> resultList = recentResult.getQuestions().stream()
                        .map(q -> {
                            int realAnswerCount = answerRepository.countByQuestion_QuestionId(q.getQuestionId().longValue());

                            return new QuestionSearchOneDto(
                                    q.getQuestionId().longValue(),
                                    q.getQuestionContent(),
                                    new BookSimpleDto(
                                            q.getBook().getBookId(),
                                            q.getBook().getTitle(),
                                            q.getBook().getImageUrl(),
                                            q.getBook().getAuthor(),
                                            q.getBook().getPublisher(),
                                            q.getBook().getPublishedYear()
                                    ),
                                    realAnswerCount, // 실시간 답변 수
                                    q.getLikeCount(),
                                    q.getScrapCount(),
                                    q.getUserNickname(),
                                    q.getProfileUrl()
                            );
                        })
                        .toList();

                return new QuestionSearchResponseDto(
                        resultList,
                        recentResult.getPageInfoDto()
                );
            }

            // 키워드 존재할 때
            else {
                Page<Question> questions = questionRepository.searchQuestions(keyword, pageable);

                List<QuestionSearchOneDto> resultList = questions.getContent().stream()
                        .map(q -> {
                            int realAnswerCount = answerRepository.countByQuestion_QuestionId(q.getQuestionId().longValue());

                            return new QuestionSearchOneDto(
                                    q.getQuestionId().longValue(),
                                    q.getQuestionContent(),
                                    new BookSimpleDto(
                                            q.getBook().getBookId(),
                                            q.getBook().getTitle(),
                                            q.getBook().getImageUrl(),
                                            q.getBook().getAuthor(),
                                            q.getBook().getPublisher(),
                                            q.getBook().getPublishedYear()
                                    ),
                                    realAnswerCount, // 실시간 답변 수
                                    q.getLikeCount(),
                                    q.getScrapCount(),
                                    q.getUser().getUserNickname(),
                                    q.getUser().getProfileUrl()
                            );
                        })
                        .toList();

                return new QuestionSearchResponseDto(
                        resultList,
                        new PageInfoDto(
                                safePage,
                                questions.getTotalPages(),
                                (int) questions.getTotalElements())
                );
            }
        }

이제 정상 작동한다!

profile
안녕하세요! 백다현입니다

0개의 댓글