Skip to content

Conversation

@daydream-03
Copy link
Collaborator

개요

기술 면접 질문 대시보드 기능을 구현한 PR입니다. 사용자가 기술 면접 질문을 검색하고, 답변을 작성하며, 관리자가 질문을 관리할 수 있는 기능을 제공합니다.

주요 기능

1. 기술 질문 검색 및 조회

  • QueryDSL을 활용한 복합 조건 검색
    • 카테고리별 필터링
    • 태그 기반 필터링
    • 키워드 검색
    • 사용자별 해결 여부 필터링
    • 정렬 기능 (생성일, 카테고리 등)
  • 페이지네이션: Spring Data의 Pageable을 활용한 페이지 처리
  • 질문 상세 조회: 질문 상세 정보 및 사용자 답변 조회

2. 답변 관리 기능

  • 답변 생성: 사용자가 질문에 대한 답변 작성
  • 답변 수정: 본인이 작성한 답변 수정
  • 답변 삭제: 본인이 작성한 답변 삭제
  • 중복 방지: 동일 사용자의 동일 질문에 대한 중복 답변 방지
  • 해결 여부 표시: 사용자별 질문 해결 여부 표시

3. 관리자 기능

  • 역할 기반 접근 제어: ADMIN 역할을 가진 사용자만 관리 기능 접근
  • 질문 관리: 질문 생성, 수정, 삭제
  • 관리자 전용 API: /api/v1/admin/* 엔드포인트

4. 태그 시스템

  • 태그 관리: 질문에 태그를 추가/제거
  • 배치 태그 조회: 성능 최적화를 위한 배치 태그 로딩
  • 태그 자동 생성: findOrCreate 메서드를 통한 태그 자동 생성

5. 인증 및 보안

  • JWT 인증: JWT 토큰 기반 인증 시스템
  • 사용자 인증: 로그인/회원가입 기능
  • 보안 설정: Spring Security를 활용한 엔드포인트 보안

성능 최적화

1. 캐싱 전략

  • Caffeine 캐시: 질문 개수 조회 결과를 1시간 동안 캐싱
  • 캐시 설정: CacheConfig를 통한 중앙화된 캐시 관리

2. 데이터베이스 쿼리 최적화

  • 배치 태그 조회: N+1 문제 해결을 위한 배치 태그 로딩
  • EXISTS 서브쿼리: 태그 존재 여부 확인 시 EXISTS 서브쿼리 활용
  • 양방향 관계 제거: Question-Tag 간 양방향 관계 제거로 순환 참조 방지
  • JWT 인증 최적화: 인증 과정에서의 불필요한 DB 호출 감소

3. 쿼리 성능 개선

  • QueryDSL 활용: 타입 안전한 동적 쿼리 생성
  • 조건부 쿼리: 필요한 조건만 포함하는 효율적인 쿼리 생성
  • 인덱스 활용: 적절한 인덱스를 통한 조회 성능 향상

daydream-03 and others added 30 commits August 3, 2025 16:40
Copy link
Collaborator

@f-lab-moony f-lab-moony left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생 많으셨습니다 ~ 피드백 확인 부탁드려요 ~


@Configuration
@EnableJpaAuditing
public class JpaConfig {} No newline at end of file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

별도의 설정이 없는데 이 클래스 굳이 필요할까요 ?


String jwt = getJwtFromRequest(request);
long jwtExtractTime = System.currentTimeMillis();
log.debug("JWT 추출 시간: {}ms", jwtExtractTime - startTime);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

걸린 시간은 왜 뽑아보는건가요 ?

log.debug("사용자명 추출 시간: {}ms", usernameExtractTime - jwtExtractTime);

// User 엔티티를 먼저 조회 (한 번만 DB 조회)
User user = userRepository.findByUsername(username)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repository가 아니라 Service를 참조하는게 어떨까요 ?

log.debug("User 엔티티 로딩 시간: {}ms", userEntityTime - usernameExtractTime);

// UserDetails는 User 엔티티에서 직접 생성 (DB 조회 없음)
UserDetails userDetails = org.springframework.security.core.userdetails.User.builder()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 패키지명까지 명시한 User와 직접 만드신 User는 어떤 차이가 있나요 ?


private final AnswerService answerService;

@PostMapping("/{questionId}/answers")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

answer 도메인인데 questionId를 pathVariable로 받아야 할 필요가 있나요 ?

JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);

// 🚀 2단계 쿼리 최적화: 태그 조건이 있으면 서브쿼리로 ID만 먼저 조회
if (searchRequest.getTags() != null && !searchRequest.getTags().isEmpty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

태그 조건 유무를 기준으로 메서드를 분리해보면 어떨까요 ?

questions = questions.subList(0, pageable.getPageSize());
}

return new PageImpl<>(questions, pageable, hasNextPage ? pageable.getOffset() + questions.size() + 1 : pageable.getOffset() + questions.size());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

호출부 안에 연산이 들어있으면 가독성이 떨어져요 변수로 추출 해서 이름을 부여해주는게 어떨까요 ?

import jakarta.persistence.PersistenceContext;

@RequiredArgsConstructor
public class QuestionRepositoryImpl implements QuestionRepositoryCustom {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클래스 전체적으로 리팩토링이 필요할 것 같아요

private final UserService userService;
private final QuestionRepositoryImpl questionRepositoryImpl;

public AnswerService(AnswerRepository answerRepository, @Lazy QuestionService questionService, UserService userService, QuestionRepositoryImpl questionRepositoryImpl) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lazy 는 왜 붙은걸까요 ?

Optional<Answer> existingAnswer = answerRepository.findByUserIdAndQuestionId(
currentUser.get().getId(), questionId);

if (existingAnswer.isPresent()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약 생성 요청이 아주 짧은 시간 안에 복수개가 요청이 되면 여기서 어떻게 동작을 하게 될까요 ?

멀티 모듈 구조 전환 및 추천 서비스 분리
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants