이전 글에서 위치 데이터를 업데이트 했다.

그래서 이번에는 전력 사용량을 업데이트 해보려고 한다.
이유는 다음과 같다.
따라서 이러한 근거로 전력 사용량 데이터를 업데이트 하기로 했다.

로 이루어져 있다.
모든 데이터를 저장할 건데, metro와 city를 활용해 LocationKey를 받아올 생각이다.

지난 글에 Excel에서부터 받아온 데이터들이다.
이 데이터에서 city/country 데이터는 각각 metro/city 데이터에 대응한다.
하지만 예외가 있는데,


다음과 같이 띄어쓰기로 들어오는 데이터들은 띄어쓰기를 붙여줘야 한다.
@Getter
@NoArgsConstructor
@Entity
public class Electricity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long electricity_key;
private String electricityYear;
private String electricityMonth;
private int electricityPopulation;
private double electricityAverageUsage;
private int electricityAverageBill;
private Long locationKey;
// 빌더 패턴을 위한 생성자
@Builder
public Electricity(String electricityYear, String electricityMonth, int electricityPopulation, double electricityAverageUsage, int electricityAverageBill, Long locationKey) {
this.electricityYear = electricityYear;
this.electricityMonth = electricityMonth;
this.electricityPopulation = electricityPopulation;
this.electricityAverageUsage = electricityAverageUsage;
this.electricityAverageBill = electricityAverageBill;
this.locationKey = locationKey;
}
}
만들어 뒀던 레퍼런스를 가지고 만들었다.
특이점이라고 하면 locationKey를 가진다는 것이다.
여기서 이전 코드와 차이점이 있다면 snake ->carmel 로 바꿨다.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'update_controller' defined in file []: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'location_CSV' defined in file []: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'locationRepository' defined in com.projectharpseal.APIcall.repository.LocationRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.Optional com.projectharpseal.APIcall.repository.LocationRepository.findBylocation_cityAndlocation_country(java.lang.String,java.lang.String); Reason: Failed to create query for method public abstract java.util.Optional com.projectharpseal.APIcall.repository.LocationRepository.findBylocation_cityAndlocation_country(java.lang.String,java.lang.String); No property 'location' found for type 'Location'; Did you mean 'location_x','location_y'
정말 긴 오류 메시지이다.
gpt에게 물어보니 naming case에 관련된 문제니 전부 camelcase로 바꾸면 해결될 것이라고 한다.
궁금해서 찾아보니


hibernate는 java의 기본 명명법을 따르고, java는 camelcase를 쓴다는 것이다.
snake 케이스로 짜고 있었는데, 싹다 camel케이스로 바꿨다...
public interface ElectricityRepository extends JpaRepository<Electricity, Long> {
}
}
public void parseApiCall() {
for (String code : KEPCO_Code) {
Mono<String> apiCallResult = publicService.makeApiCall(
"KEPCO_URL",
"KEPCO_Path",
"KEPCO_Key",
"year=" + LY.getYear(),
"month=" + LY.getMonth(),
"metroCd=" + code,
"apiKey="
);
}
}
기본적인 API 호출 코드이다.
하지만 여기에 추가되는 코드가 2개 있다.
바로 날짜 코드와 metroCd 인데,
//Date_return Dto
@Getter
@Setter
public class Date_Return{
private String year;
private String month;
private String day;
public Date_Return(String year, String month, String day) {
this.year = year;
this.month = month;
this.day = day;
}
}
//Date_return method
public static Date_Return calculateLastYearMonth(){
LocalDate today = LocalDate.now();
LocalDate LY = today.minusYears(1);
String Year = LY.format(DateTimeFormatter.ofPattern("yyyy"));
String Month = LY.format(DateTimeFormatter.ofPattern("MM"));
String Day = "Null";
return new Date_Return(Year, Month, Day);
}
//metroCd
private static final String[] KEPCO_Code = {"11", "26", "27", "28", "29", "30", "31", "36", "41", "43", "44", "45", "46", "47", "48", "50", "51"};
변경점은 다음과 같다.
특히나 properties에 있는 데이터를 넣은 이유가 무엇인고 하니
따라서 위의 단점들 때문에 코드에서 냄새가 나니 1줄 그냥 하드코딩 하고 복잡한 코드를 지워버렸다.
apiCallResult.subscribe(response -> {
try {
JsonNode rootNode = objectMapper.readTree(response);
String metro = rootNode.path("metro").asText();
String city = rootNode.path("city").asText().replace(" ", "");
Optional<Location> LocationValue = locationRepository.findByLocationCityAndLocationCountry(city, metro);
if (LocationValue.isPresent()) {
Location location = LocationValue.get();
// Electricity 엔티티 생성 및 데이터베이스 저장
Electricity electricity = Electricity.builder()
.electricityYear(rootNode.path("year").toString())
.electricityMonth(rootNode.path("month").toString())
.electricityPopulation(rootNode.path("houseCnt").asInt())
.electricityAverageUsage(rootNode.path("powerUsage").asDouble())
.electricityAverageBill(rootNode.path("bill").asInt())
.locationKey(location.getLocationKey())
.build();
electricityRepository.save(electricity);
} else {
logger.warn("metro, city에서 오류 발생: {} , {}", metro, city);
}
} catch (Exception e) {
logger.error("Error parsing JSON response", e);
}
}
다음 코드는 데이터를 파싱하는 코드이다.
레퍼런스 : https://projectreactor.io/docs/core/release/reference/#mono
이거 찾느라 정말 힘들었다..
다름이 아닌 Mono의 데이터를 파싱하는 방법인데,
Mono는 .subscribe를 통해 데이터를 받아내야 한다.
자세한 내용은 추후 다루도록 하고, 일단 코드를 보자.(솔직히 아직 생각의 정리가 덜 되었다)
레퍼런스 : https://www.baeldung.com/jackson-json-node-tree-model
JsonNode rootNode = objectMapper.readTree(response);
이후 ObjectMapper.readTree를 통해 response 값을 json 객체로 변환시키고
String metro = rootNode.path("metro").asText();
String city = rootNode.path("city").asText().replace(" ", "");
Optional<Location> LocationValue = locationRepository.findByLocationCityAndLocationCountry(city, metro);
metro, city 값과 같은 location의 key값을 찾는다.
이를 통해 location과 Electricity가 연결된다.
if (LocationValue.isPresent()) {
Location location = LocationValue.get();
} else {
logger.warn("metro, city에서 오류 발생: {} , {}", metro, city);
}
if~ else로 예외 처리를 하면 끝!
@RestController
public class update_controller {
private final Location_CSV locationCsv;
private final KEPCO kepco;
public update_controller(Location_CSV locationCsv, KEPCO kepco) {
this.locationCsv = locationCsv;
this.kepco = kepco;
}
@PostMapping("/update_Location")
public void data(@RequestParam("file") MultipartFile file) throws IOException {
locationCsv.ExcelData(file);
}
@GetMapping("/update_KEPCO")
public void KEPCO_update(){
kepco.parseApiCall();
}
그리고 기존 controller를 update 파일로 한꺼번에 관리하기로 했다.
이제 코드를 실행시켜보자.

api 호출도 정상적으로 되고, 코드도 실행이 됐는데...
어쩐지 DB에 저장이 되지 않는다.
뭔가 이상해서 logger를 찍어봤는데

어쩐지 데이터가 null이 뜬다..

분명히 url은 정상이 맞다.
살짝 이전에 해봤던 React 비동기 처리의 PTSD가 살짝 나서

.block으로 동기적 호출로 바꿔줬더니

그런건 없고 다시 터졌다.
이전 프로젝트를 곰곰히 생각해봤다.
React를 사용할때 발생했던 문제와 일치한다.
문제의 핵심은
정확한 요청과 값을 확인했음에도 바로 사용하려니까 null값이 뜬다.
이는 요청을 보낸 직후 return 값을 null로 줬고,
그로 인해서 요청의 값이 돌아오기 전에 null로 미리 연산을 해버리는 것이라 생각했다.
따라서
return webClient.get()
.uri(URI.create(fullUrl))
.retrieve()
.bodyToMono(String.class)
.flatMap(data -> {
if (data == null || data.isEmpty()) {
return Mono.empty();
} else {
return Mono.just(data);
}
})
.doOnError(e -> logger.error("Error during API call", e));
webClient에 flatMat을 활용하였다.
레퍼런스 : https://manish-dixit.medium.com/reactive-tools-map-flatmap-flatmapmany-subscribe-514460b4ccd9
이후 데이터를 확인해 보니


성공적으로 들어오는걸 확인할 수 있었다!
(사실 여기서 정말 중요한것은 이게 .flatMap을 통해 데이터가 들어오는건지 파악이 안된다는 것이다. null error3를 보자.)
하지만 metro 값이 전부 오류가 뜨길레 확인을 했다.
String response = apiCallResult.block();
try {
JsonNode rootNode = objectMapper.readTree(response);
logger.info("node data = {}", rootNode);
String metro = rootNode.path("metro").asText();
String city = rootNode.path("city").asText().replace(" ", "");
Optional<Location> LocationValue = locationRepository.findByLocationCityAndLocationCountry(city, metro);
logger.info("metro: {}, city: {}, key: {}", metro, city, LocationValue);
그래서 logger로 전부 확인 해 보니

data가 한번 더 씌워져 있었다.
아까 데이터가 안들어와져서 구조를 뜯어보다가 response 값을 그대로 들고온 게 잘못되었나 보다..
그런데 갑자기

같은 코드에서 잘되던 api가 갑자기 안되기 시작한다.

그리고 시간이 지나니 갑자기 또 된다. 한두번 그런게 아니라 계속 이런다.
정말 이해 할 수 없지만.. 일단은 넘어가자.(진짜 미칠거같다 왜이러는지 모르겠다)
어쨌든 데이터는 들어왔는데,

오류가 난다.
또 한참을 해맨 뒤..
Optional<Location> LocationValue = locationRepository.findByLocationCityAndLocationCountryAndLocationTown(metro, city, town);
코드에 metro/city가 반대로 적혀있었다..
이후에 드디어..!

데이터가 들어왔다!


위에 적었던 flatMap은 아무리 봐도 실질적인 해결방안이 아니라고 생각했다.
그래서 지우고 다시 해봤더니 역시나 그냥 된다.
위에서 api 호출이 안되던것은 null error2와 연관이 있다고 판단을 내렸다.
갑자기 안되는 상황에서 추측할 수 있는것은
가 되겠다. 코드가 전혀 바뀌지 않았음에도 되다 안되다 하는것이라 내 상상력으로는 여기가 한계인거 같다.
하지만 1,3은 말이 안되는 것이
똑같은 코드가 어쩔때는 되다가 갑자기 null이 뜨고 그러는건 말도 안되고(심지어 비동기 처리가 문제였던것이 아니다!)
PostMan으로 따로 호출했을때는 잘만 돌아가기 때문이다.

여전히 이 문제는 원인조차 파악하지 못하지만 어찌 손쓸 방법이 없다.
프레임워크에 문제가 있는것도 아니고
코드에 문제가 있는것도 아니고
그렇다고 호출을 잘못하고 있는것도 아니고
참 난감한 상황이다.
다시한번 느끼지만
남들이 api 호출 실패할때 flatmap 쓰라고 진지하게 말했을거 같다..
끔찍한 상상이고 공부가 더 필요함을 느낀다.
거의 하루 종일 삽질한 후 겨우 완성했는데 너무 감격스러웠다.
잘 되다가 갑자기 터져버리는 api
어떻게 관리해야 될지도 모르겠는 비동기처리
갑자기 naming 때문에 멈춰버리는 코드 등등
정말 말도 안되는 방법으로 계속 막혔던거 같다....

이후 metro 이름 바뀌는거(강원도 -> 강원특별자치도) 등등의 예외처리만 해 주면 끝이 날 거 같다.
일단 오늘은 여기까지..!