TIL-트러블 슈팅(Scheduler)_25.04.03-4/04.07

kb·2025년 4월 3일

Spring

목록 보기
13/21
post-thumbnail

이슈 1: User 생성이 안됨.

  • 코드에는 문제가 없는데, 아래와 같은 에러 메시지가 나옴
2025-04-03T19:20:43.910+09:00  WARN 67696 --- [schedule] [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation]

해결 방법

1. GPT에게 질문

이 에러는 왜 나는 거야?
2025-04-03T19:20:43.910+09:00  WARN 67696 --- [schedule] [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation]

2. 답변

HttpMediaTypeNotAcceptableException: No acceptable representation
은 주로 **클라이언트가 요청한 응답 형식(미디어 타입)**에 대해 서버가 "어떻게 응답해야 할지 모를 때" 발생합니다.

3. 상황 확인

-> UserResponseDto는 Getter를 굳이 쓰지 않아서 Getter를 안 넣음.

  • GPT에게 다시 질문
    UserResponseDto에 Getter를 안 넣었어. UserResponseDto에서 Getter로 받아오는 속성이 없는데 꼭 필요한 거야? 이유는?
  • GPT 답변
결론부터 말하자면: 네, @Getter는 꼭 필요합니다.
왜냐하면, Spring에서 @RestController가 리턴한 객체(UserResponseDto)를 **JSON으로 변환하는 과정(Jackson 직렬화)**에서 객체의 값을 읽기 위해 Getter 메서드를 호출하기 때문입니다.

4. 결론

  • 코드를 짤 때 명시적으로 ResponseDto에서 값을 받아오지 않지만, ResponseDto를 Json으로 변환하는 과정에서 Getter로 ResponseDto를 조회한다. 그러므로 ResponseDto에도 Getter가 필수!

이슈 2. User Id로 조회가 안됨

  • 아래와 같은 에러 메시지가 나옴
2025-04-03T19:59:05.088+09:00 ERROR 38584 --- [schedule] [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessApiUsageException: The given id must not be null] with root cause
  • ID가 Null이면 안된다는 말. Postman에서 생성하고 조회도 했는데, 왜 Id가 Null이지?
  • 전체 조회 시에는 생성한 유저 정보가 나옴.

해결방법

1. GPT에게 질문

이 에러가 난 이유는? (위의 코드 복사)

2. 답변

에러 메시지 The given id must not be null은 Spring Data JPA에서 ID 값이 null인 상태로 DB를 조회하려고 했기 때문에 발생한 에러예요.
🧠 에러 원인 요약
findById(null) 또는 deleteById(null) 같은 메서드가 호출되었고,
이때 넘겨준 ID 값이 null이었기 때문에 JPA가 "null ID로 조회할 수 없어!" 라고 오류를 낸 거예요.
🔍 주로 발생하는 케이스
1. @RequestBody 또는 @PathVariable로 ID를 받는데, 실제 요청에서 ID가 누락된 경우
2. 객체를 저장한 직후, 그 객체의 ID가 null인 상태인데 getId() 해서 다시 조회하려고 한 경우
3. 테스트나 컨트롤러 호출 시, DTO나 Entity에 ID를 설정하지 않고 JPA에 전달한 경우

3. 상황 확인

  • 주로 발생하는 케이스 2번인 것 같다. 객체를 저장한 직후 조회하려는 경우가 아닐까?
  • 추가 질문 & 답변
User user = new User(requestDto);  // ID는 아직 null
userRepository.save(user);         // 저장했지만...
Long id = user.getId();            // ❌ 여기서 getId() 하면 아직 null일 수 있음!

  • 생성 시 save 대신 saveAndFlush를 하거나, 저장된 Entity를 반환하고 반환 받은 Entity를 사용하기
  • 근데, 후자의 경우, 지금도 이미 이렇게 하고 있는데 오류가 난 것을 보면 userRepository에 id가 안 넘어가는 상황인 것 같은데..
  • 아.. 진짜 바보다. 에러 코드를 자세히 살펴보니, Service() 단에서 에러가 났고, Service 단에 id가 안 넘어갔다. 그럼 전 단계인 Controller로 가보니, 메서드에 입력 값을 (Long id)로 넣어놨다... (@PathVariable Long id)로 해야 URL에 적은 id 값을 받는데... 이런... 그래도 잘 찾았다.

4. 결론

  • 이런 실수는 충분히 할 수 있다. 에러 코드를 잘 읽어보고 디버깅 하자!

이슈 3

  • Schedule Update 시 입력한 username이 달라도 업데이트가 됨(Lv2). 대신 이름은 그대로
  • 아마 Schedule의 속성의 username이 schedule.getUser().getUsername()으로 불러와서, schedule에 저장된 유저(user_id로 매칭) 이 user가 변경되지 않으니까 이름도 변경되지 않는 것이 아닐까.
  • 그렇다면, RequestDto도 이제 분리해줘야하지 않을까? 회원가입과 로그인 과정을 넣어서, 로그인을 통과한 유저만 본인이 작성한 글을 수정하거나 삭제할 수 있도록 해야할 것 같다. Filter로 chain을 만들어서, (User/Schedule) update, delete는 이 chain을 통과한 유저만 할 수 있도록. 이걸 구현해보자.
  • 로그인 했음에도 세션 유지가 안 되어 생성/조회 모든 기능이 안됨

해결방법

1. GPT에게 질문

user controller 로직을 아래와 같이 구현했는데, postman을 이용해 login 호출 이후 RUD 요청을 처리하려고 할 때 로그인해달라는 메시지가 나와. 세션이 유지되지 않는 것 같은데 무슨 문제인지 확인해줄 수 있어?

2. 답변

  • JSESSIONID를 추가해야한다는데.. 근데 강의자료엔 아래와 같이 나와 있다.
  • Sevlet으로 HttpSession을 생성하면 JESSIONID가 상성된다는데..
  • 위와 같은 원인도 있다고 한다. 설정을 확인하고 어떻게 해야하는지 추가 질문
  • build.gradle에서 starter-security를 사용중인지 확인하고 stateless 부분을 바꿔야 한다는데...

3. 상황 확인

  • 여러 차례 핑퐁을 거친 결과... LoginFilter에서 session.getAttribute("sessionKey" = null) 이렇게 받고 있었는데, 이게 아니라, Const.LOGIN_USER로 받아줘야 한다. 강의노트에서 "sessionKey"로 되어 있어 그대로 썼더니 여기서 null이 발생한 듯. UserController 클래스에서 session.setAttribute(Const.LOGIN_USER, loginUser)로 받고 있으므로, getAttribute에서도 Const.LOGIN_USER로 똑같이 받았어야 함! 아마도 강의노트에는 setAttribute에 ("sessionKey", 삐리리) 이렇게 되어 있어 정상적으로 동작한 것이 아닐까 하는 추측. 확인해보자.
  • 엥? 강의노트에도 Const.LOGIN_USER로 setAttribute/getAttribute 하고 있다. sessionKey 어디에서 나온지 아는 분? 어쩌면 GPT가 알려줬을지도...

4. 결론

  • Cookie와 Session을 설정하는 방법을 제대로 이해할 필요가 있겠다. 문법을 이해하지 못하니 코드 줄마다 어떤 역할을 해주는 건지 모르고, 더 심각한 것은 zero부터 새롭게 짜야할 때 생각이 날리 만무하다는 점이다.

이슈 4

  • Postman 테스트 시 로그인이 안 돼서 유저 생성을 여러 번 했는데, 그 이후 로그인도 안됨 -> 중복된 id, pw가 여러 개여서 그런 듯! 이건 예외처리를 해줘야할 것 같다.

해결방법

1. GPT에게 질문

중복로그인 문제는 singup 시 DB에 같은 email이 있는지 조회하도록 Service, Repository에 코드를 추가.

2. 답변

    <Service 클래스>
    public UserResponseDto register(UserRequestDto requestDto) {
    // 💡 이메일 중복 체크
    if (userRepository.findUserByEmail(requestDto.getEmail()).isPresent()) {
        throw new ResponseStatusException(HttpStatus.CONFLICT, "이미 등록된 이메일입니다.");
    }
    <Repository 클래스>
    // 이메일로 유저 검색 (중복 체크용)
    Optional<User> findUserByEmail(String email);
    <Entity 클래스>
    email 필드에 @Column(unique=true) 어노테이션 추가

3. 상황 확인

처리하니 중복 로그인 시 Conflict 메시지가 정상적으로 호출 됨!

4. 결론

  • 예외 처리가 정말 중요하구나.. Lv4에서도 로그인 시 이메일과 비밀번호가 일치하지 않을 경우 HTTP Status code 401을 반환하라고 했는데, 이것도 예외처리를 제대로 해줘야 할 것 같다.
  • 예외처리는 박성원 튜터님께서 발제시간에 ExceptionHandler를 포함한 내용을 알려주셨는데 막상 적용하려니 어떻게 해야할지 감이 오지 않는다.
  • 복습과 연습, 삽질.. 반복하자.

이슈 5

  • 유저 1, 유저 2를 생성한 이후 유저 2로 로그인하고, 스케줄 생성 시 유저id를 1로 설정하면 스케줄이 생성된다. 정상적인 케이스라면, 로그인에 성공한 유저만 본인의 유저 id로 게시글을 작성할 수 있어야 한다.

해결 방법

1. 생각해보기

일단, 생각나는 거 막 던져보기

  • ScheduleController의 URL을 Login의 하위로 둔다. > 구조가 복잡해질 것 같다. Schedule Controller 클래스에서 User
  • 로그인한 유저의 세션 정보에서 id값을 받아오고 이 id를 createSchedule 메서드의 입력 id로 넣어준다. > 이게 현실적인 방법일 것 같은데. 필터를 거쳐 Controller로 넘어오니, 넘어온 httpservlet에서 id를 갖고 와 넣어주면 되는 일이 아닐까?

2. GPT한테 물어보기

아래 내용에 대해 피드백 해줄 수 있어?
사고력을 키울 수 있게 이끌어주는 피드백을 주면 좋겠어.
(위의 정리 내용을 전달함)

3. 답변

  • 와 Dr.G 미쳤다. 완전 선생님인데? 일단 세션에서 정보를 갖고 온다는 접근은 잘 한 것 같다! 더 나아가, 사고력을 키우는 질문 1을 보면 사용자의 ID를 Body나 Param으로 넘기지 못하게 해야한다고 한다! 그 이유는, 클라이언트에서 보내는 데이터는 누구나 조작할 수 있기 때문이라고! Postman에서도 id:1, id:2와 같이 맘대로 바꿔서 요청할 수 있다!
  • 그래서 서버는 세션(또는 토큰)에 저장된 값만을 "유일한 신뢰할 수 있는 유저 정보"로 사용해야 한다.
  • 생각했던 두 번째 방법이 옳은 방법이자 유일한 신뢰할 수 있는 방법이었네!
  • 그럼, 세션에서 ID만 꺼내면 될까? No. 세션에서 유저 Entity를 갖고올 수 있으면 Best이지 않을까? 지금은 ID로 조회하지만, 나중에는 username이나, password를 비교하거나 Entity의 필드를 사용해야 하는 경우가 있을 수 있다. 그러니 Entity를 꺼낼 수 있으면 최상일 것 같다.
  • GPT도 UserResponseDto나 User 전체를 세션에 넣어두는게 편하다고! 이유는 생각했던 것과 비슷하다.
  • 그럼, 이제 해야할 일은, Login한 세션에서 유저 정보를 갖고 오는 일! 세션에서 Login을 하면 Repository에서 User를 조회할 수 있었을테고, 그러면 조회한 User 정보를 세션에 담아줄 수 있고, 이 정보를 세션에서 꺼내올 수 있다면 문제는 해결된다.
  • 세션에서 User를 꺼내서, 이 userId와 수정하려는 게시글의 userId가 다르면 권한없음, 같으면 수정을 해주면 된다!

4. 상황 확인

  • 해결! Service 단에서 validate 로직을 추가했다. 스케줄의 수정/삭제, 유저의 수정/삭제 시 session의 유저 id와 request한 id로 조회한 유저 id가 동일한지 검증하고, 동일하지 않으면 Forbidden Error를 Throw하게 했다.
  • 계정 1, 2로 회원가입 -> 1로 로그인 -> 1로 게시글 작성 -> 1로그아웃 -> 2로그인 -> 2로 게시글 수정 시도 -> forbidden / 2로 1유저 정보 수정 시도 -> forbidden 굿!

5. 결론

  • 뭔가 이렇게 동작하면 안될 것 같은데.. 싶은 부분이 걸리면 파고들어갈 필요가 있다. 요구사항에 명시되어 있지 않다고 해도 비즈니스 로직 상 더 합리적이라고 판단되면, 그리고 그것이 배움의 범위를 크게 벗어나지 않으면 시도해보자! 복잡해 보이더라도 길이 있다!
profile
Experience

0개의 댓글