티스토리 뷰

728x90

안녕하세요! 열심히 서비스를 만들었지만,, 트러블슈팅도 많이 해봤지만,, 노션에만 정리하고 정작 블로그에 포스팅한 트러블 슈팅이 없다는 것을 깨닫고 하나씩 작성해보려합니다.

 

처음엔 단순한 매칭 실패처럼 보였는데, 깊이 파고들다 보니 null 처리 누락, DB 접근 예외, 컬렉션 수정 중 예외, 구조 분리 등

제법 복잡하고 중요한 문제들이 얽혀 있더라고요.

 

이번 글에서는 그 과정을 어떻게 추적하고, 해결했는지를 공유해보려 합니다.


문제 상황

매칭 요청을 보냈는데, 클라이언트에서는 응답이 오지 않고, 소켓 서버(3000번 포트)에서도 matching-started가 emit되지 않는 현상이 발생했습니다.

딱 봐도 어딘가 중간에 끊긴 것 같았죠.

 

그래서 API 서버 로그와 Docker 로그를 확인해봤습니다.

org.springframework.dao.InvalidDataAccessApiUsageException: The given id must not be null!
nested exception is java.lang.IllegalArgumentException: The given id must not be null!

딱 보니 findById(null)이 호출된 상황이더라고요.

JPA에서 ID가 null이면 IllegalArgumentException을 바로 던지기 때문에,

이게 전체 흐름을 막아버린 겁니다.


 

원인 1 - gameStyleIdList 내부에 null이 포함되어 있었음

 

매칭 요청에서 사용자의 게임 스타일 ID 리스트는 아래와 같이 전달됩니다.

{
  "gameStyleIdList": [1, null, null]
}

이 상태로 서버에 넘어오면, 다음 로직에서 문제가 터집니다:

gameStyleRepository.findById(null); // 여기서 예외 발생

Spring Data JPA는 findById()에 null을 넘기면

런타임에서 IllegalArgumentException을 발생시키고,

이 예외가 처리되지 않으면 전체 요청이 실패하게 됩니다.

 

 

해결 1 - null 필터링 처리

 

프론트엔드에서 gameStyleIdList를 구성할 때,

null 값은 미리 걸러주도록 필터링을 추가했습니다.

request.gameStyleIdList = request.gameStyleIdList.filter(item => item !== null);

이걸로 프론트 → 백으로 넘어오는 데이터 정합성이 어느 정도 확보됐고,

IllegalArgumentException은 더 이상 발생하지 않았습니다.


또 다른 문제

 

GameStyle 수정 API를 호출했더니 이번엔 서버에서 다음과 같은 예외가 터졌습니다:

java.util.ConcurrentModificationException

 

원인 2 - Stream 반복 중 컬렉션 수정

기존에는 기존 게임 스타일을 지우기 위해 다음과 같은 코드를 쓰고 있었습니다.

member.getMemberGameStyleList().stream()
    .filter(mgs -> !requestGameStyleList.contains(mgs.getGameStyle()))
    .forEach(mgs -> {
        mgs.removeMember(member); // 양방향 연관관계 제거
        memberGameStyleRepository.delete(mgs);
    });

여기서 문제는 Stream으로 순회하면서 동시에 컬렉션을 수정하고 있다는 점입니다.

Java는 이런 상황에서 fail-fast 전략을 적용하고 있어서

ConcurrentModificationException이 발생하게 됩니다.

 

해결 2 - 제거 대상을 임시 리스트에 모아 한 번에 처리

삭제할 대상들을 먼저 toRemove 리스트에 모아두고, 그 리스트만 순회하면서 제거하는 방식으로 변경했습니다.

List<MemberGameStyle> toRemove = new ArrayList<>();

for (MemberGameStyle mgs : member.getMemberGameStyleList()) {
    if (!requestGameStyleList.contains(mgs.getGameStyle())) {
        toRemove.add(mgs);
    }
}

for (MemberGameStyle mgs : toRemove) {
    mgs.removeMember(member);
    memberGameStyleRepository.delete(mgs);
}

 

이제 컬렉션을 안전하게 수정할 수 있게 됐고, 예외도 발생하지 않게 되었습니다.

 


위의 수정 과정에서 느낀 건, MemberService 안에서 GameStyle 처리 로직이 너무 많은 책임을 가지고 있다는 점이었습니다.

그래서 MemberGameStyleService라는 전담 클래스로 분리했습니다.

변경 전

profileService.addMemberGameStyles()

 

변경 후

memberGameStyleService.updateGameStyle()

로직도 단계별로 명확하게 나눴습니다:

 

findRequestGameStyle() 요청으로 들어온 ID 리스트 → GameStyle Entity 변환
findCurrentMemberGameStyleList() 현재 DB에 존재하는 GameStyle 리스트 조회
removeUnnecessaryGameStyles() 현재에는 있지만 요청에는 없는 스타일 제거
addNewGameStyles() 요청에는 있지만 현재에 없는 스타일 추가

 

728x90
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함