본문 바로가기
내일배움캠프/축구팀 관리 프로젝트

축구팀 관리 프로젝트 38일차 - soft delete된 팀 멤버가 조회되는 오류

by 코드스니펫 2024. 2. 19.
반응형

축구팀 관리 프로젝트 38일차 - soft delete된 팀 멤버가 조회되는 오류

페이지 접속 오류 발생

 

페이지 전반적으로 기능 테스트 중에 경기 일정에서 전술설정 들어가면 접속 안되는 오류를 발견했습니다.

 

 

페이지 접속 오류

 

문제 발생

원래는 화면이 나와야 하지만 빈 페이지가 나와서 당황했습니다.

빈화면

 

바로 로컬에서 테스트해봤습니다. 테스트 해보니 아래 오류 발생하였습니다.

 

Uncaught TypeError: Cannot read properties of null (reading 'user')
    at index.tsx:1146:1
    at Array.map (<anonymous>)
    at index.tsx:1144:1
    at Array.map (<anonymous>)
    at Formation (index.tsx:1142:1)
    at renderWithHooks (react-dom.development.js:16305:1)
    at updateFunctionComponent (react-dom.development.js:19588:1)
    at beginWork (react-dom.development.js:21601:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)

 

프론트 오류

 

다른 계정으로 다시 이 화면 들어가니 다른 화면은 멀쩡하게 잘 나왔습니다. klinsmann@gaeggul.com 이 계정에서만 오류가 나고 있었습니다. 

 

원인 찾기

로컬 swagger로 api 확인해봤습니다.

swagger 확인

 

구단주 계정으로 로그인 후 전술화면에 쓰이는 api를 하나씩 확인해봤습니다. 먼저 구단주 검증은 잘 되었습니다.

 

구단주 검증 정상

 

팀별 경기별 포메이션 조회도 동작은 잘했습니다. 그러나 결과 데이터는 없었습니다. 원래는 나와야 하는 데이터여서 여기서 부터 코드를 확인해보았습니다.

 

팀별 포메이션 조회

 

 

formation.service.ts

    /**
     * 팀별 포메이션 조회
     * @param  teamId
     * @param  matchId
     * @returns
     */
    async getMatchFormation(teamId: number,matchId: number,id?: number) {
        const whereCondition = {
            team_id: teamId,
            match_id: matchId,
          };
        
          // position 변수가 제공되면 where 조건에 추가
          if (id) {
            whereCondition['member_id'] = id;
          }
        
          const matchFormation = await this.matchFormationRepository.find({
            where: whereCondition,
            relations: {
              member: true,
            },
          });

        if (!matchFormation) {
            throw new NotFoundException('팀별 포메이션 정보가 없습니다.');
        }

        
        // 각 member에 대해 user 정보를 불러옵니다.
        for (const formation of matchFormation) {
            if (formation.member) {
                const memberInfo = await this.memberRepository.findOne({
                    where:{
                        id:formation.member.id
                    },relations:{
                        user:true
                    }
                });

                formation.member.user = memberInfo.user; // 여기서 user 정보를 member 객체에 할당
            }
        }

        return matchFormation;
    }

 

match_formations 테이블에 데이터 확인(팀 id:1, 경기 id: 49, 2월 24일자 경기 ) 데이터 없었습니다. 다른 일정 데이터도 동일했습니다.

 

members 테이블도 연관되어 있길래 조회해 보았습니다. 팀1로 조회한 members 테이블 결과를 보다가 이상한 점을 발견할 수 있었습니다. user_id 40번이 여러개 들어있었습니다. 원래 members 테이블은 축구팀의 팀원 정보를 저장하는 용도라 사용자는 한팀에 한 row의 데이터만 가지고 있을 수 있습니다. 하지만 모종의 이유로 한 계정이 여러 members 테이블에 저장되는 바람에 조회가 안되던 것이었습니다.

 

당연히 이 이유겠지 싶었는데..

id created_at updated_at deleted_at is_staff join_date user_id team_id
31 Tue Feb 13 2024 07:51:02 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:44:13 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:44:13 GMT+0900 (한국 표준시) 0 Tue Feb 13 2024 07:51:02 GMT+0900 (한국 표준시) 40 1
42 Wed Feb 14 2024 02:47:18 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:55:05 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:55:05 GMT+0900 (한국 표준시) 0 Wed Feb 14 2024 02:47:18 GMT+0900 (한국 표준시) 40 1
43 Wed Feb 14 2024 02:57:00 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:57:35 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:57:35 GMT+0900 (한국 표준시) 0 Wed Feb 14 2024 02:57:00 GMT+0900 (한국 표준시) 40 1
44 Wed Feb 14 2024 02:58:08 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:58:28 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:58:28 GMT+0900 (한국 표준시) 0 Wed Feb 14 2024 02:58:08 GMT+0900 (한국 표준시) 40 1
45 Wed Feb 14 2024 02:58:54 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:59:09 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:59:09 GMT+0900 (한국 표준시) 0 Wed Feb 14 2024 02:58:54 GMT+0900 (한국 표준시) 40 1
46 Wed Feb 14 2024 02:59:35 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:59:51 GMT+0900 (한국 표준시) Wed Feb 14 2024 02:59:51 GMT+0900 (한국 표준시) 0 Wed Feb 14 2024 02:59:35 GMT+0900 (한국 표준시) 40 1
47 Wed Feb 14 2024 03:00:09 GMT+0900 (한국 표준시) Wed Feb 14 2024 03:00:24 GMT+0900 (한국 표준시) Wed Feb 14 2024 03:00:24 GMT+0900 (한국 표준시) 0 Wed Feb 14 2024 03:00:09 GMT+0900 (한국 표준시) 40 1
48 Wed Feb 14 2024 03:00:37 GMT+0900 (한국 표준시) Wed Feb 14 2024 03:00:49 GMT+0900 (한국 표준시) Wed Feb 14 2024 03:00:49 GMT+0900 (한국 표준시) 0 Wed Feb 14 2024 03:00:37 GMT+0900 (한국 표준시) 40 1
49 Wed Feb 14 2024 03:01:33 GMT+0900 (한국 표준시) Wed Feb 14 2024 03:01:48 GMT+0900 (한국 표준시) Wed Feb 14 2024 03:01:48 GMT+0900 (한국 표준시) 0 Wed Feb 14 2024 03:01:33 GMT+0900 (한국 표준시) 40 1
50 Wed Feb 14 2024 03:03:05 GMT+0900 (한국 표준시) Wed Feb 14 2024 03:03:22 GMT+0900 (한국 표준시) Wed Feb 14 2024 03:03:22 GMT+0900 (한국 표준시) 0 Wed Feb 14 2024 03:03:05 GMT+0900 (한국 표준시) 40 1

 

그러면서 테이블을 다시 보니까 deleted_at에 다 값이 들어 있었습니다. 삭제된 데이터였습니다. 

 

 

진짜 원인은

진짜 문제 해결점을 찾은 듯 했습니다.  다시 정리하는 문제 원인은 member에서 deleted_at 에 값 들어간 인원 (팀 탈퇴한 인원)이 같이 조회되는 바람에 오류 발생하였던 것이었습니다.

 

 

수정한 코드

원인을 발견하고 나서 바로 코드를 수정하기 시작했습니다. 막상 위 코드를 수정하려 보니 members 테이블에 조건 거는게 쉽지 않았습니다. 그래서 쿼리 빌더를 사용하여 다른테이블 접근이 용이하도록 했습니다. 그렇게 수정한 코드는 다음과 같습니다.

수정한 코드

 

    /**
     * 팀별 포메이션 조회
     * @param  teamId
     * @param  matchId
     * @returns
     */
    async getMatchFormation(teamId: number,matchId: number,id?: number) {
        const whereCondition = {
            team_id: teamId,
            match_id: matchId,
          };
        
        // position 변수가 제공되면 where 조건에 추가
        if (id) {
        whereCondition['member_id'] = id;
        }

        const matchFormation = await this.matchFormationRepository
        .createQueryBuilder("matchFormation")
        .innerJoinAndSelect("matchFormation.member", "member", "member.deleted_at IS NULL")
        .leftJoinAndSelect("member.user", "user") // 여기서는 LEFT JOIN을 사용하여 user 정보는 있으면 가져오고 없으면 무시합니다.
        .where("matchFormation.team_id = :teamId", { teamId })
        .andWhere("matchFormation.match_id = :matchId", { matchId })
        .andWhere(id ? "matchFormation.member_id = :id" : "1=1", { id })
        .getMany();

        return matchFormation;
    }

 

 

앞서 말했듯 변경된 코드에는 쿼리빌더를 사용하였습니다.

 

수정 코드 설명

find 메소드는 간단한 조회에 적합하지만, 조건이 복잡해지거나 특정 조건(member.deleted_at IS NULL)을 만족하는 관계를 가진 엔티티를 조회할 때는 구현하기에 어려움이 있었습니다. 이 때 쿼리 빌더를 사용하면서 SQL 쿼리와 유사한 방식으로 보다 세밀하고 복잡한 쿼리를 작성할 수 있었습니다.

 

쿼리 빌더를 사용하니 innerJoinAndSelect와 같은 메소드를 통해 조인 시 특정 조건을 적용할 수 있었습니다. 이를 통해 삭제되지 않은(deleted_at가 null인) 멤버만을 조회할 수 있으며, 이런 경우 innerJoinAndSelect는 해당 조건에 부합하는 관계만 결과에 포함할 수 있었습니다.

 

또한, find 메소드를 사용할 때는 relations 옵션을 사용하면 때로는 불필요한 데이터까지 가져오는 경우가 있었습니다. 이때, 쿼리 빌더를 사용하니 필요한 필드만 선택(select)하여 성능을 최적화할 수 있었습니다.

 

수정 후 정상 동작

위 코드로 수정하고 다시 정상적으로 동작하는 것을 볼 수 있었습니다.

다시 조회됨

 

▼ 이전 진행한 프로젝트들 ▼

 

 

축구팀 관리 프로젝트 37일차 - 목표 테스트 커버리지 80% 중 56% 달성

축구팀 관리 프로젝트 37일차 - 목표 테스트 커버리지 80% 중 56% 달성 현재 match service 단 테스트 파일 작성중입니다. 커버리지 80%를 목표로 하고 있는 데 아직 56%까지 작업했습니다. match service 테

lemonlog.tistory.com

 

 

축구팀 관리 프로젝트 36일차 - jwt 모의함수 구현 오류

축구팀 관리 프로젝트 36일차 - jwt 모의함수 구현 오류 테스트 코드 작성 중 발견한 jwt 모의함수 구현 오류를 어떻게 해결했는지 소개하겠습니다. jwt 모의함수 구현 오류 문제발견 문제는 아래

lemonlog.tistory.com