diff --git a/src/main/java/contest/collectingbox/global/exception/ErrorCode.java b/src/main/java/contest/collectingbox/global/exception/ErrorCode.java index fc84795..a618277 100644 --- a/src/main/java/contest/collectingbox/global/exception/ErrorCode.java +++ b/src/main/java/contest/collectingbox/global/exception/ErrorCode.java @@ -27,7 +27,8 @@ public enum ErrorCode { NOT_FOUND_TAG(NOT_FOUND, "해당 이름과 일치하는 수거함 태그가 없습니다."), // 500 - UNEXPECTED_ERROR_EXTERNAL_API(INTERNAL_SERVER_ERROR, "외부 API 호출 시 알 수 없는 예외가 발생했습니다."); + UNEXPECTED_ERROR_EXTERNAL_API(INTERNAL_SERVER_ERROR, "외부 API 호출 시 알 수 없는 예외가 발생했습니다."), + ERROR_PARSING_JSON_KAKAO_API(INTERNAL_SERVER_ERROR, "Error parsing JSON from Kakao API"); private final HttpStatus httpStatus; private final String message; diff --git a/src/main/java/contest/collectingbox/module/publicdata/application/PublicDataService.java b/src/main/java/contest/collectingbox/module/publicdata/application/PublicDataService.java index 4590667..952f660 100644 --- a/src/main/java/contest/collectingbox/module/publicdata/application/PublicDataService.java +++ b/src/main/java/contest/collectingbox/module/publicdata/application/PublicDataService.java @@ -2,6 +2,7 @@ import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvValidationException; +import contest.collectingbox.module.collectingbox.domain.CollectingBox; import contest.collectingbox.module.collectingbox.domain.Tag; import contest.collectingbox.module.collectingbox.domain.repository.CollectingBoxRepository; import contest.collectingbox.module.location.domain.repository.DongInfoRepository; @@ -19,10 +20,7 @@ import java.io.IOException; import java.io.InputStreamReader; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; @Slf4j @Component @@ -51,7 +49,7 @@ private List getEntities(List r @Transactional public long loadPublicData(List requests) { - long loadedDataCount = 0; + List boxes = new ArrayList<>(); for (LoadPublicDataRequest request : requests) { log.info("======= {} - {} =======", request.getSigungu(), request.getTag().getLabel()); @@ -74,15 +72,17 @@ public long loadPublicData(List requests) { // 카카오 주소 검색 API 응답 출력 log.info("query = {}, response = {}", query, addressInfo); - // insert DB + // 수거함 객체 저장 if (addressInfo != null && addressInfo.isSigunguEquals(sigungu)) { - collectingBoxRepository.save(addressInfo.toCollectingBox(dongInfoRepository)); - loadedDataCount++; + boxes.add(addressInfo.toCollectingBox(dongInfoRepository)); } } } - return loadedDataCount; + // bulk insert + collectingBoxRepository.saveAll(boxes); + + return boxes.size(); } @Transactional diff --git a/src/main/java/contest/collectingbox/module/publicdata/domain/AddressInfoMapper.java b/src/main/java/contest/collectingbox/module/publicdata/domain/AddressInfoMapper.java index d7ff0f1..9d17aba 100644 --- a/src/main/java/contest/collectingbox/module/publicdata/domain/AddressInfoMapper.java +++ b/src/main/java/contest/collectingbox/module/publicdata/domain/AddressInfoMapper.java @@ -10,7 +10,7 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class AddressInfoMapper { - public static AddressInfoDto jsonObjectToAddressInfoDto(JSONObject document, Tag tag) { + public static AddressInfoDto mapToAddressInfo(JSONObject document, Tag tag) { JSONObject address = document.getJSONObject("address"); JSONObject roadAddress = document.getJSONObject("road_address"); diff --git a/src/main/java/contest/collectingbox/module/publicdata/domain/KakaoApiManager.java b/src/main/java/contest/collectingbox/module/publicdata/domain/KakaoApiManager.java index 99f9783..a5306ba 100644 --- a/src/main/java/contest/collectingbox/module/publicdata/domain/KakaoApiManager.java +++ b/src/main/java/contest/collectingbox/module/publicdata/domain/KakaoApiManager.java @@ -1,5 +1,6 @@ package contest.collectingbox.module.publicdata.domain; +import contest.collectingbox.global.exception.CollectingBoxException; import contest.collectingbox.module.collectingbox.domain.Tag; import contest.collectingbox.module.publicdata.dto.AddressInfoDto; import lombok.RequiredArgsConstructor; @@ -8,13 +9,18 @@ import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; +import static contest.collectingbox.global.exception.ErrorCode.ERROR_PARSING_JSON_KAKAO_API; +import static contest.collectingbox.global.exception.ErrorCode.UNEXPECTED_ERROR_EXTERNAL_API; import static java.nio.charset.StandardCharsets.UTF_8; @Component @@ -31,8 +37,8 @@ public AddressInfoDto fetchAddressInfo(String query, Tag tag) { ResponseEntity response = callKakaoAPI(query); // 카카오 API 예외 처리 - if (response.getStatusCode() != HttpStatus.OK) { - throw new RuntimeException("Kakao API failed status = " + response.getStatusCode()); + if (response.getStatusCode().isError()) { + throw new CollectingBoxException(UNEXPECTED_ERROR_EXTERNAL_API); } // 주소 정보 추출 @@ -49,16 +55,16 @@ public AddressInfoDto fetchAddressInfo(String query, Tag tag) { return null; } - return AddressInfoMapper.jsonObjectToAddressInfoDto(document, tag); + return AddressInfoMapper.mapToAddressInfo(document, tag); } catch (JSONException e) { - throw new RuntimeException("Error parsing JSON from Kakao API", e); + throw new CollectingBoxException(ERROR_PARSING_JSON_KAKAO_API); } } private ResponseEntity callKakaoAPI(String query) { RestTemplate restTemplate = new RestTemplate(); HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.set("Authorization", "KakaoAK " + apiKey); + httpHeaders.set("Authorization", "KakaoAK %s".formatted(apiKey)); HttpEntity httpEntity = new HttpEntity<>(httpHeaders); URI targetUrl = UriComponentsBuilder.fromUriString(API_URL).queryParam("query", query) .build().encode(UTF_8).toUri(); diff --git a/src/main/java/contest/collectingbox/module/publicdata/domain/OpenDataApiManager.java b/src/main/java/contest/collectingbox/module/publicdata/domain/OpenDataApiManager.java index 390d7df..b01e031 100644 --- a/src/main/java/contest/collectingbox/module/publicdata/domain/OpenDataApiManager.java +++ b/src/main/java/contest/collectingbox/module/publicdata/domain/OpenDataApiManager.java @@ -7,13 +7,17 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; import static contest.collectingbox.global.exception.ErrorCode.UNEXPECTED_ERROR_EXTERNAL_API; +import static java.nio.charset.StandardCharsets.UTF_8; @Component public class OpenDataApiManager { - private static final String API_URL = "https://api.odcloud.kr/api%s?serviceKey=%s&perPage=%d"; + private static final String API_URL = "https://api.odcloud.kr/api%s"; @Value("${public-data.api.key}") private String apiKey; @@ -36,8 +40,11 @@ public JSONArray fetchOpenData(String callAddress, int perPage) { return new JSONObject(response.getBody()).getJSONArray("data"); } - private String getUrl(String callAddress, int perPage) { - return String.format(API_URL, callAddress, apiKey, perPage); + private URI getUrl(String callAddress, int perPage) { + return UriComponentsBuilder.fromUriString(String.format(API_URL, callAddress)) + .queryParam("serviceKey", apiKey) + .queryParam("perPage", perPage) + .build().encode(UTF_8).toUri(); } private boolean isError(ResponseEntity response) { diff --git a/src/main/java/contest/collectingbox/module/publicdata/dto/LoadPublicDataRequest.java b/src/main/java/contest/collectingbox/module/publicdata/dto/LoadPublicDataRequest.java index 114c317..979b4ed 100644 --- a/src/main/java/contest/collectingbox/module/publicdata/dto/LoadPublicDataRequest.java +++ b/src/main/java/contest/collectingbox/module/publicdata/dto/LoadPublicDataRequest.java @@ -9,8 +9,4 @@ public class LoadPublicDataRequest { private String sigungu; private Tag tag; private String callAddress; - - public String getUrlWithPerPage(String apiKey, int perPage) { - return String.format("https://api.odcloud.kr/api%s?serviceKey=%s&perPage=%d", callAddress, apiKey, perPage); - } }