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

축구팀 관리 프로젝트 26일차 - 경기장 조회 페이지네이션 적용

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

축구팀 관리 프로젝트 26일차 - 경기장 조회 페이지네이션 적용

 

경기장 목록 조회화면

 

전날 경기장 데이터를 새로 Mysql에 저장하는 작업을 했습니다. 전국 경기장 데이터가 담긴 csv 파일을 파싱해서 Mysql에 넣었는데 데이터량이 필터를 했음에도 10,000건이 넘어갔습니다. 그래서 경기장 조회 화면에 페이지네이션을 적용하였습니다.

 

 

경기장 조회 화면 페이지네이션 적용

 

이전 경기장 조회 화면은 다음과 같았습니다. 기존에 있던 자료에는 경기장 목록이 50건 이하였습니다. 하지만 전날 작업한 csv 파일 안에 있는 경기장 목록은 3만건 정도가 있었습니다. 

 

이전 경기장 조회 화면

 

 

 

* 파싱한 전국 경기장 데이터가 담긴 csv 파일

전국 경기장 csv 파일 내용 중에서

 

경기장 정보를 담는 테이블이 location과 soccer_fields가 있는데 아래 깃허브 프로젝트에 있는 파일로 csv 파싱 작업을 하고 나니 테이블에 만건이 넘어가게 되었습니다. 그 상태로 경기장 조회하게되니 만건 넘는 경기장을 한번에 조회하는 문제가 발생했습니다.

 

 

GitHub - lemon-table/csv_to_mysql_stadium

Contribute to lemon-table/csv_to_mysql_stadium development by creating an account on GitHub.

github.com

 

 

테이블 전체 row count 쿼리 결과

 

SELECT 
    SUM(soccer_fields) AS soccer_fields, 
    SUM(location_cnt) AS location_cnt
FROM (
    SELECT 
        COUNT(*) AS soccer_fields, 
        0 AS location_cnt 
    FROM 
        test17.soccer_fields

    UNION ALL

    SELECT 
        0 AS soccer_fields, 
        COUNT(*) AS location_cnt 
    FROM 
        test17.location
) AS cnt;

 

 

경기장 조회 페이지네이션 적용

경기장 조회화면에서 디폴트로 전체 조회되어 조회 속도가 느려지는 점을 잡기 위해 화면에 페이지네이션을 적용하기로 했습니다. 

 

 

Pagination이란 (페이징 처리) - Offset vs Cursor

1. Pagination이란 Pagination이란 검색 결과를 가져올 때 데이터를 쪼개 번호를 매겨 일부만 가져오는 기법이다 1.1 Pagination을 사용하는 이유 사용자가 애플리케이션을 사용 중 게시판, 상품 목록 등을

betterdev.tistory.com

 

 

먼저 경기장 조회 API 부터 수정했습니다. 컨트롤러 에서 화면 입력 받은 페이지정보를 서비스단으로 보내주고,

// soccerfield.controller.ts
    
	/**
     * 경기장 전체 조회
     * @param req
     * @returns
     */
    @ApiBearerAuth()
    @UseGuards(JwtAuthGuard)
    @Get('page')
    async findAllStadium(@Request() req, @Query() dto: PaginateFieldDto) {
        const userId = req.user.id;

        const data = await this.soccerfieldService.findAllStadium(userId, dto, dto.name);

        return data;
    }

 

 

서비스 페이지에서 해당 페이지 정보를 바탕으로 페이지별 조회 결과를 return하는 작업을 해줍니다.

 // soccerfield.service.ts
 
 	/**
     * 경기장 전체 조회
     * @returns
     */
    async findAllStadium(userId: number, dto: PaginateFieldDto, name?: string) {

        const options: FindManyOptions<SoccerField> = {
            relations: {
                locationfield: true,
            }
        };

        if (name) {
            options.where = {  field_name: Like(`%${name}%`)  };
        }
        

        if (!options) {
            throw new NotFoundException('등록된 경기장 목록이 없습니다.');
        }

        //return soccerField;
        return await this.commonService.paginate(dto, this.soccerFieldRepository, options, 'soccerfield');
    }

 

 

백엔드는 위와 같이 수정하고 프론트엔드 수정을 위해 리엑트에서는 아래와 같이 처음 페이지 로드시 page를 1로 고정하여 조회를 하도록 하였고, 

// pages/match/index.tsx


  useEffect(() => {
    const accessToken = localStorage.getItem("accessToken");

    const findAllSoccerField = async (page: number = 1) => {
      try {

        let apiUrl = `${process.env.REACT_APP_SERVER_HOST}:${
          process.env.REACT_APP_SERVER_PORT || 3000
        }/api/soccerfield/page/?page=${page}`;
  
        // 검색어가 있는 경우 검색 쿼리 추가
        if (searchQuery.trim() !== "") {
          apiUrl += `&name=${searchQuery}`;
        }

        const response = await axios.get(apiUrl,
          {
            headers: {
              Authorization: `Bearer ${accessToken}`, // Bearer 토큰 추가
            },
            withCredentials: true,
          }
        );

        const fieldData = response.data.data;

        setField(fieldData); // creatorId가 존재하면 구단주로 간주
        setTotal(response.data.total);

        setLoading(false); // 데이터 로딩 완료
      } catch (error) {
        console.error("데이터 불러오기 실패:", error);
        setLoading(false); // 데이터 로딩 실패
        setTotal(0);
      }
    };

    findAllSoccerField(); // 데이터를 불러오는 함수 호출
  }, []);

 

 

페이지 변동시 아래 이벤트가 동작하도록 하였습니다.

// pages/match/index.tsx

const changePage = async (page: number) => {
    try {
      const accessToken = localStorage.getItem("accessToken");

      const response = await axios.get(
        `${process.env.REACT_APP_SERVER_HOST}:${
          process.env.REACT_APP_SERVER_PORT || 3000
        }/api/soccerfield/page/?page=${page || 1}&name=${searchQuery}`,
        {
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
          withCredentials: true,
        }
      );
      
      const fieldData = response.data.data;

      setField(fieldData); // creatorId가 존재하면 구단주로 간주
      setTotal(response.data.total);

    } catch (error) {
      console.error("멤버 정보를 불러오는 데 실패했습니다.", error);
    }
  };

 

 

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

 

 

내일배움캠프 Node트랙 심화 프로젝트 역할 및 진행사항

내일배움캠프 Node트랙 심화 프로젝트 역할 및 진행사항 이번 프로젝트는 팀 프로젝트로 Node트랙 심화 프로젝트를 진행하게 되었습니다. 프로젝트를 시작하며 팀에서 맡은 역할과 현재 진행사항

lemonlog.tistory.com

 

 

내일배움캠프 NestJS 프로젝트 코드리뷰 - 온라인 공연 예매 서비스

내일배움캠프 NestJS 프로젝트 코드리뷰 - 온라인 공연 예매 서비스 내일배움캠프를 진행한지도 벌써 3개월 정도로 접어들고 있습니다. 이 글에서는 내일배움캠프에서 필자가 진행한 NestJS 개인

lemonlog.tistory.com