이전 CSV 파일 업로드 를 보셨겠지만, <List<CsvResponseDto>> 안에 데이터를 넣는 걸 보셨겠지만, 데이터를 찍어보게 되면 아래 구조와 같습니다.
{
{dosi:"서울",sigungu:"용산구",lon:126.9675222,lat:37.53609444},
{dosi:"서울",sigungu:"..",lon:..,lat:..}
..
..
..
+294 개 기반 데이터
}
데이터를 저장하고 나서 생각한건 결국 어떤 도시에 시군구 에 걸맞는 위도 경도 를 찾을려면 일일이 데이터를 순회 하면서 찾아야할까 ? 라는 생각이 번뜩 들었습니다 .
데이터가 총 294 개라 시간 복잡도 상으로도 단시간 내 해결 할 수 있었을 것 같았지만, 조금 더 빠르게 찾는 방법은 없을까 라는 고민이 들었습니다. 이러한 이유로 Map<String,List<CsvResponseDto>> 와 같이 데이터를 갖추게 되면
Key 을 통해 보다 더 빨리 찾을 수 있겠다 라는 생각으로 기능 구현을 하였습니다.
아래는 본 기능에 적용한 코드 입니다.
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);
}
}
}
@EventListener(ApplicationReadyEvent.class) 로 인해 해당 어케리이션이 실행 될 때 메서드가 실행 됩니다.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);
}
}
}
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;
}
}
save 메서드를 통해 들어오는 데이터를 List안에 담아 놓은 이후 인스턴스 필드인 csvParserCollection 내 데이터를 저장하는 로직 입니다.getMatcherDosiAndSigungu 각 도시, 시군구를 매개변수로 받아, csvParserCollection 에서 Key 값으로 도시 에 대한 데이터를 뽑은 이후 → 그 도시 내 필요로하는 시군구 에 대한 값을 리턴 받는 메서드입니다.getAllDosiAndSigungu 메서드 네이밍 과 같이 전체 시군구를 반환할때 사용하는 메서드 입니다.