이전 CSV 파일 업로드 를 보셨겠지만, <List<CsvResponseDto>> 안에 데이터를 넣는 걸 보셨겠지만, 데이터를 찍어보게 되면 아래 구조와 같습니다.

{
{dosi:"서울",sigungu:"용산구",lon:126.9675222,lat:37.53609444},
{dosi:"서울",sigungu:"..",lon:..,lat:..}
..
..
..
+294 개 기반 데이터 
}

데이터를 저장하고 나서 생각한건 결국 어떤 도시에 시군구 에 걸맞는 위도 경도 를 찾을려면 일일이 데이터를 순회 하면서 찾아야할까 ? 라는 생각이 번뜩 들었습니다 .

데이터가 총 294 개라 시간 복잡도 상으로도 단시간 내 해결 할 수 있었을 것 같았지만, 조금 더 빠르게 찾는 방법은 없을까 라는 고민이 들었습니다. 이러한 이유로 Map<String,List<CsvResponseDto>> 와 같이 데이터를 갖추게 되면

Key 을 통해 보다 더 빨리 찾을 수 있겠다 라는 생각으로 기능 구현을 하였습니다.

아래는 본 기능에 적용한 코드 입니다.

  1. 맨 처음 CsvFactory 를 만들었습니다. @Component를 넣어준 이유는 본 클래스가 어플리케이션이 실행 될때 빈으로 등록해주기 위해서 사용하였습니다.
@Component
@RequiredArgsConstructor
public class CsvFactory {

  //csv reader기
  private final CsvReader csvReader;
  // 대한민국 시별 위도 경도 csv파일 경로
  @Value("${csv.sgg_lat_lon}")
  private String SSG_LAT_LON_PATH;

  private static final Logger LOG
      = Logger.getLogger(String.valueOf(CsvFactory.class));

  @EventListener(ApplicationReadyEvent.class)
  public void onApplicationEvent() {
    LOG.info("starting to create csv");
    try {
      csvReader.reader(SSG_LAT_LON_PATH);
    } catch (IOException e) {
      throw new BusinessException(e, "csv err", ErrorCode.CSV_PARSER_ERROR);
    }
  }
}
  1. CsvReader - Csv 데이터를 읽은 이후 SaveDto 안에 데이터를 넣는 클래스 입니다.
@Component
@RequiredArgsConstructor
@Slf4j
public class CsvReader {

  private final CsvSaveDto csvSaveDto;

  public void reader(String path) throws IOException {
    //해당 파일을 읽어온다
    BufferedReader br = Files.newBufferedReader(Paths.get(path));
    try (br) {
      // 첫 번째 줄 (헤더) 건너뛰기
      br.readLine();
      String line;
      while ((line = br.readLine()) != null) {
        csvSaveDto.save(line); //각 행마다 데이터를 저장한다
      }
      log.info("csv 파일 적재 완료 {}", csvSaveDto);
      log.info("csv 파일 사이즈 {}", csvSaveDto.getCsvParserSize());
    } catch (IOException e) {
      throw new BusinessException(e, "csv file error", ErrorCode.CSV_PARSER_ERROR);
    }
  }
}
  1. CsvSaveDto - Csv데이터를 적재하는 클래스
package com.wanted.global.util.csv;

import com.wanted.domain.restaurant.dto.location.response.CsvResponseDto;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.Getter;
import org.springframework.stereotype.Component;

/**
 * csv데이터 적재소
 */
@Getter
@Component
public class CsvSaveDto {

  // 데이터를 담아놓는 곳
  private Map<String, List<CsvResponseDto>> csvParserCollection = new LinkedHashMap<>();

  public void save(String line) {
    String[] lines = line.split(",");
    List<CsvResponseDto> responseDtoList = new ArrayList<>();
    CsvResponseDto csvResponseDto = CsvResponseDto.builder()
        .doSi(lines[0])
        .siGunGu(lines[1])
        .lon(lines[2])
        .lat(lines[3])
        .build();
    responseDtoList.add(csvResponseDto);
    // map에 새로운 키가 등장하면 새롭게 추가하는 메서드
    this.csvParserCollection.computeIfAbsent(lines[0], k -> new ArrayList<>()).add(csvResponseDto);
  }

  public int getCsvParserSize() {
    return this.csvParserCollection.size();
  }

  /**
   * getMatcherDosiAndSigungu: 원하는 지역 및 시군구 를 통해서 CsvResponseDto를 반환 한다.
   *
   * @param dosi    도,시
   * @param sigungu 시군구
   * @return CsvResponseDto  -> fields:  lat ,lon sigungu,dosi
   */
  public CsvResponseDto getMatcherDosiAndSigungu(String dosi, String sigungu) {
    List<CsvResponseDto> csvResponseDtoList = this.csvParserCollection.get(dosi);
    return csvResponseDtoList.stream()
        .filter(dto -> sigungu.equals(dto.getSiGunGu()))
        .findFirst()
        .orElseThrow();
  }

  /**
   * 전체 시군구를 반환한다.
   *
   * @return CsvResponseDto  -> fields:  lat ,lon sigungu,dosi
   */
  public List<CsvResponseDto> getAllDosiAndSigungu(){
    List<CsvResponseDto> allResponseDtoList =
        csvParserCollection.values().stream()
            .flatMap(List::stream)
            .collect(Collectors.toList());

    return allResponseDtoList;
  }
}
  1. save 메서드를 통해 들어오는 데이터를 List안에 담아 놓은 이후 인스턴스 필드인 csvParserCollection 내 데이터를 저장하는 로직 입니다.
  2. getMatcherDosiAndSigungu 각 도시, 시군구를 매개변수로 받아, csvParserCollection 에서 Key 값으로 도시 에 대한 데이터를 뽑은 이후 → 그 도시 내 필요로하는 시군구 에 대한 값을 리턴 받는 메서드입니다.
  3. getAllDosiAndSigungu 메서드 네이밍 과 같이 전체 시군구를 반환할때 사용하는 메서드 입니다.