[TIL] 2025-02-27_Spring 심화 트러블슈팅

Yuri·2025년 2월 27일

TIL

목록 보기
55/59

Spring 심화 주차 개인 과제를 구현하며 겪은 문제점과 해결방법, 새로 알게된 점을 기록합니다.

Lv1. 코드개선

1. 코드 개선 퀴즈 - Early Return

유효성 검사를 선행하여 검사를 통과하지 못한 경우 encodePassword를 실행하지 않고 예외를 던진다.

2. 불필요한 if-else 피하기

if-else { if } 구조로 불필요하게 중첩된 조건문을 삭제한다.

3. Validation


@Valid, @Pattern 어노테이션을 사용하여 요청 데이터에 대한 검증을 Controller 에서 수행

Lv2. N+1 문제

Todo 엔티티와 User 엔티티의 연관관계가 N:1 (@ManyToOne) 으로 맺어져 있고 지연로딩(FetchType.LAZY) 설정이 되어있다.

지연로딩으로 Todo의 목록을 조회할 때 User 객체를 즉시 가져오지 않고, proxy 객체로 가지고 있다가 User가 사용될 때 조회쿼리를 실행한다. 👉 "N+1 문제" : Todo 목록 조회 쿼리 (1) + 목록의 User를 조회(N) 만큼 수행하게 된다.

이를 해결하기 위해 Todo 목록 조회 쿼리에서 fetch join, @EntityGraph 으로 해결한다.

fetch join

    @Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ORDER BY t.modifiedAt DESC")
    Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);

    @Query("SELECT t FROM Todo t " +
            "LEFT JOIN FETCH t.user " +
            "WHERE t.id = :todoId")
    Optional<Todo> findByIdWithUser(@Param("todoId") Long todoId);

@EntityGraph

    @EntityGraph(attributePaths = {"user"}, type = EntityGraph.EntityGraphType.FETCH)
    @Query("SELECT t FROM Todo t LEFT JOIN t.user u ORDER BY t.modifiedAt DESC")
    Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);

    @EntityGraph(attributePaths = {"user"}, type = EntityGraph.EntityGraphType.FETCH)
    @Query("SELECT t FROM Todo t " +
            "LEFT JOIN t.user " +
            "WHERE t.id = :todoId")
    Optional<Todo> findByIdWithUser(@Param("todoId") Long todoId);

Lv3. 테스트코드 연습

테스트 코드 연습 - 1


passwordEncoder의 matches 메서드는 1번째 인자로 rawPassword, 2번째 인자로 encodePassword를 받는다.

테스트 코드 연습 - 2 1번 케이스

  • 예외타입 불일치: NPE → InvalidRequestException
  • 예외 발생 시 에러 메세지 불일치: "Manager not found" → "Todo not found"

테스트 코드 연습 - 2 2번 케이스

  • 예외타입 불일치: ServerException → InvalidRequestException

테스트 코드 연습 - 2 3번 케이스

todo의 user가 null 일때, getId()로 값을 가져오는 경우 null.getId() 이므로 예외가 발생한다.

Lv4. API 로깅

Interceptor 와 AOP를 활용한 API 로깅

📋 요구사항
어드민 사용자만 접근할 수 있는 특정 API에는 접근할 때마다 접근 로그를 기록해야 한다.

✍️ 로깅 구현 방법

  • Interceptor
    • 요청 정보를 사전 처리합니다.
    • 어드민 권한 여부를 확인하여 인증되지 않은 사용자의 경우 예외를 발생
    • 인증 성공 시, 요청 시각과 URL을 로깅

✔️ HandlerInterceptor 의 구현체를 생성하여 Overriding

  • preHandler (전처리)

        default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            return true;
        }

    true를 반환하면 요청을 계속 진행하고, false를 반환하면 요청 처리를 중단한다.

  • postHandle (후처리)

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }
    
  • afterCompletion (요청 완료 후)

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }

  • AOP

    • @Around 어노테이션을 사용하여 어드민 API 메서드 실행 전후에 요청/응답 데이터를 로깅합니다.
    • 요청 본문과 응답 본문은 JSON 형식으로 기록
    • 로깅은 Logger 클래스를 활용하여 기록

    🕹️ 실행시점 제어 어노테이션

    어노테이션실행 시점주요 활용
    @Before메서드 실행 이전입력 값 검증, 로깅, 인증
    @After메서드 실행 이후 (예외 포함)리소스 해제, 트랜잭션 관리
    @AfterReturning메서드가 정상 실행된 후반환값 로깅 및 조작
    @AfterThrowing예외 발생 시예외 로깅 및 에러 처리
    @Around메서드 실행 전후 모두실행 시간 측정, 로깅, 응답 변조

Lv5. 위 제시된 기능 이외 '내'가 정의한 문제와 해결과정

  1. 🔫 패키지 구조 변경하기
  2. 🔫 에러 메세지 개선

Lv6. 테스트 커버리지

최대한 풀커버하기 위해 노력했지만 테스트코드 작성이 어려워 생각보다 진도가 빨리빨리 나가지 않아 일부 도메인에서만 테스트를 진행하였습니다. 현업에서도 TDD를 지향하지만 도입하기 힘들어하는 지 이해가 되었습니다. 🫠


강의 노트

🧑‍🏫 과제 해설 강의 메모

🔑 secret key

터미널에서 아래 명령어를 입력하여 랜덤 키 생성

openssl rand -hex 32

🤮 코드 스멜 피하기

쓸데없이 사용되는 코드가 있는지 확인하기

@EntityGraph

@EntityGraph 를 사용하는 경우 @Query를 사용하여 직접 sql를 작성하지 않아도 된다.
jpa method 로 자동 생성된 쿼리를 사용할 수 있다.

profile
안녕하세요 :)

0개의 댓글