과제 간 트러블 슈팅(1)

제이 용·2025년 12월 22일

“왜?”

날짜 검색에서 LocalDate vs LocalDateTime

왜 LocalDate로 입력을 받았을까?

  • 사용자는 날짜만 신경 쓰지, 시:분:초까지 직접 입력할 필요가 없음

  • UI/UX 관점에서 날짜 입력이 훨씬 직관적임


그런데 응답은 왜 LocalDateTime일까?

  • DB에는 생성 시점이 LocalDateTime으로 저장됨

  • 응답 DTO는 정확한 생성 시각을 보여주는 게 맞음

입력은 단순하게(LocalDate), 출력은 정확하게(LocalDateTime)

역할에 맞게 타입을 분리하는 것이 설계적으로 더 좋다.


@DateTimeFormat + atStartOfDay / LocalTime.MAX를 쓰는 이유

  • @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
  • LocalDate startDate;

왜 이런 변환 로직이 필요한가?

  • LocalDate는 시간 정보가 없음

  • DB 컬럼은 LocalDateTime

    • 그래서:

    • 시작일 → 00:00:00

    • 종료일 → 23:59:59.999999999


코드가 지저분한데 다른 방법은?

  • 이 방식이 의도가 가장 명확하다고 함.

  • QueryDSL/JPA에서 시간 경계 버그를 가장 확실히 방지

“지저분해 보여도, 명시적인 코드가 가장 안전하다”

QueryDSL에서 BooleanExpression이 null인데도 동작하는 이유

private BooleanExpression titleContains(String keyword) {
    return keyword == null ? null : todo.title.contains(keyword);
}

null 반환인데 왜 검색 조건이 적용될까?

  • QueryDSL의 where()는 null 조건을 자동으로 무시한다.

  • 조건이 있으면 AND로 결합하고, 없으면 제외한다고 한다.

query.where(
    titleContains(keyword),
    createdAtBetween(start, end)
);

null-safe 동적 쿼리를 위한 의도된 설계

  • 장점

    • if-else 지옥 방지

    • 조건 조합 폭발 방지

    • 가독성, 유지보수성 ↑


fetchOne() vs fetch() 차이 (강의내용 중복)

왜 fetchOne을 함부로 쓰면 안 될까?

fetchOne()
  • 결과가 1개일 거라 확신할 때만

  • 2개 이상이면 예외 발생

fetch()
  • 리스트 조회

  • 결과 개수와 무관

“단건 조회가 보장될 때만 fetchOne”


Optional을 쓰는 이유 & orElseThrow 자동완성

Optional을 왜 쓰는가?

  • null 반환 → 실수로 NPE 발생

  • Optional → “없을 수도 있음”을 타입으로 강제

userRepository.findById(id)
    .orElseThrow(...)

Optional 안 쓰면 orElseThrow 자동완성이 안 뜬 이유?

  • orElseThrow()는 Optional 전용 메서드

  • 엔티티 자체에는 존재하지 않음

Optional은 단순 편의가 아니라

null 가능성을 코드 레벨에서 드러내는 장치


N+1 문제가 발생한 이유와 해결 방식

왜 N+1이 발생했을까?

  • 연관 엔티티가 LAZY 로딩

  • 반복 접근 시 쿼리가 추가로 실행됨

해결 방법은?

fetch join
  • QueryDSL에서 join + fetch

  • 필요한 필드만 Projections로 조회

“조회용 쿼리는 엔티티 조회가 아니라 DTO 조회가 더 적합한 경우가 많다”


SecurityConfig에 permitAll이 있는데 JWT 필터에서 또 체크하는 이유

SecurityConfig면 끝 아닌가?

.anyRequest().authenticated()
  • 이건 인가(Authorization) 단계

  • JwtFilter는 그보다 앞단의 인증(Authentication) 필터

그래서 필터에서도 분기 처리가 필요한 이유

  • 필터는 무조건 실행됨

  • /auth/** 요청에서도 JWT 검사하면 오류 발생

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
    return request.getRequestURI().startsWith("/auth");
}

필터 책임: 인증 정보 생성

SecurityConfig 책임: 접근 허용/차단


UsernamePasswordAuthenticationFilter 앞에 JWT 필터를 두는 이유

이 필터는 뭐 하는 놈인가?

  • 폼 로그인 기반

  • ID/PW 인증 처리

왜 JWT 필터가 앞에 있어야 할까?

  • JWT는 이미 인증된 사용자

  • UsernamePasswordAuthenticationFilter까지 갈 필요 없음

JWT 인증이 먼저 끝나야

SecurityContext가 채워지고

이후 인가 로직이 정상 동작


오늘의 핵심 정리

“Spring / JPA / Security에서의 설정과 패턴은

‘될 때까지 맞추는 코드’가 아니라

왜 이 레이어에서 이 책임을 가지는지를 이해하는 게 중요하다!!!”

4개의 댓글

comment-user-thumbnail
2025년 12월 22일

재미따 ㅎㅎ 뭔가 저도 했던 고민들인데 제이용님은 잘 정리하여 작성하셨고 저는 그러지 못했네요 ㅠㅠ 반성합니다.

1개의 답글
comment-user-thumbnail
2025년 12월 22일

왜 이 계정이지 ㅠ

답글 달기
comment-user-thumbnail
2025년 12월 22일

혹시 여기 "불펌" 가능한가요?

답글 달기