본문 바로가기
개발일지/Java & Springboot

[SpringBoot] 스프링부트에서 공공데이터 OpenApi 사용하기

by 최호희 2024. 10. 25.

프로젝트를 진행하는데 공공데이터 OpenAPI를 활용할 일이 생겼다.

올해 초에도 공공데이터 OpenAPI를 사용해서 프로젝트를 한 경험이 있는데 요번에 또 사용하게 되었다.

전의 기억을 더듬어 다시 사용해보고 이를 기록하고자한다!

 

아래의 데이터를 사용

https://data.gg.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=ZFR12Y2JA4AKZVZXTHIE32159755&infSeq=2&order=&loc=&searchWord=%EC%84%A0%ED%95%9C%EC%98%81%ED%96%A5%EB%A0%A5

 

경기도 선한영향력가게 현황 | 데이터셋 상세 Open API | 경기데이터드림

선한 영향력 가게에서 제공하는 경기도 내 선한 영향력 가게 목록입니다. 선한 영향력 가게는 자발적으로 결식아동을 지원하는 가게 목록으로 음식점 외에도 아이들에게 도움이 될 수 있는 다

data.gg.go.kr

 

공공데이터는 대표적으로 공공데이터 포털에서 많이 줍줍할 수 있으니 참고하길 바랍니다 ^__^

https://www.data.go.kr/

 

공공데이터 포털

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr


 

데이터에 따라 인증키를 요청해야하는 경우도 있고 없는 경우도 있는데,

이번에 사용할 공공데이터는 인증키를 발급받아야한다.

인증키를 야무치게 받았으면 잘 메모해둔다.

OPEN API 정보

 

인증키를 발급 받고 위의 표에서 사용할 데이터들만 골라본다.

나는 상호명, 업종명, 시군명, 정제도로명주소, 정제지번주소, 상세주소, 영업시간, 위도, 경도 데이터만 받아올 생각이다.

TMI: 위도 경도를 사용해서 카카오맵에서 마커로 띄울 것이기때문!!

준비가 되었다면 Springboot에서 위의 데이터들을 DB에 저장하기 위한 작업을 해볼 것이다.

 

엔티티

- 공공데이터의 출력명과 동일하게 작성하는 것을 추천한다.

- Getter와 Setter도 열어준다.

Store.java

@Entity
@Getter@Setter
public class Store {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String CMPNM_NM;    // 상호명
    private String INDUTYPE_NM; // 업종명
    private String SIGUN_NM;    // 시군명
    private String REFINE_ROADNM_ADDR;  // 정제도로명주소
    private String REFINE_LOTNO_ADDR;   // 정제지번주소
    private String DETAIL_ADDR; // 상세주소
    private String BSN_TM_NM;   // 영업시간
    private Double REFINE_WGS84_LAT;    // 위도
    private Double REFINE_WGS84_LOGT;   // 경도
}

 

레포지토리

- 나는 Jpa를 사용하기 때문에 JpaRepository를 extends해줬다.

StoreRepository.java

public interface StoreRepository extends JpaRepository<Store, Long> {
}

 

서비스 계층

- service 계층에서는 공공데이터를 DB에 저장하는 로직을 구현한다.

@Service
@RequiredArgsConstructor
public class StoreService {

    private final StoreRepository goodInfluenceRepository;
    private static final Logger logger = LoggerFactory.getLogger(StoreService.class);

    @Transactional
    public void saveAllStore(List<Store> StoreList) {
        for (Store store : StoreList) {
            validateStore(store);
        }
        goodInfluenceRepository.saveAll(StoreList);
        logger.info("가게 정보 {}건 저장됨", StoreList.size());
    }

    private void validateStore(Store store) {
        // 필수 필드 체크
        if (store.getCMPNM_NM() == null || store.getCMPNM_NM().isEmpty()) {
            logger.error("상호명은 필수입니다: {}", store);
            throw new IllegalArgumentException("상호명은 필수입니다.");
        }
        if (store.getINDUTYPE_NM() == null || store.getINDUTYPE_NM().isEmpty()) {
            logger.error("업종명은 필수입니다: {}", store);
            throw new IllegalArgumentException("업종명은 필수입니다.");
        }
    }
}

 

컨트롤러 계층

- 공공데이터 Open API를 json 형식으로 파싱하고 DB에 저장한다.


@RestController
@RequiredArgsConstructor
public class StoreController {

    private final StoreService goodInfluenceStoreService;
    private static final Logger logger = LoggerFactory.getLogger(StoreController.class);

    @GetMapping("/api/Store")
    public String callStoreApi() {
        StringBuilder result = new StringBuilder();
        // API URL 하드코딩
        String urlStr = "https://openapi.gg.go.kr/GGGOODINFLSTOREST?KEY=[발급받은 인증키]&Type=json&pIndex=1&pSize=1000";

        HttpURLConnection urlConnection = null;
        BufferedReader br = null;
        try {
            // API 요청 설정
            URL url = new URL(urlStr);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("GET");
            urlConnection.setConnectTimeout(10000); // 10초 연결 타임아웃
            urlConnection.setReadTimeout(10000); // 10초 읽기 타임아웃

            // 응답을 BufferedReader로 읽기
            br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
            String returnLine;
            while ((returnLine = br.readLine()) != null) {
                result.append(returnLine).append("\n");
            }

            // JSON 파싱
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode rootNode = objectMapper.readTree(result.toString());
            JsonNode itemsNode = rootNode.path("GGGOODINFLSTOREST").get(1).path("row");
            logger.info("Items count: " + itemsNode.size());

            List<Store> storeList = new ArrayList<>();
            for (JsonNode itemNode : itemsNode) {
                Store store = new Store();

                // 각 필드에 값 설정
                store.setCMPNM_NM(itemNode.path("CMPNM_NM").asText());
                store.setINDUTYPE_NM(itemNode.path("INDUTYPE_NM").asText());
                store.setSIGUN_NM(itemNode.path("SIGUN_NM").asText());
                store.setREFINE_ROADNM_ADDR(itemNode.path("REFINE_ROADNM_ADDR").asText());
                store.setREFINE_LOTNO_ADDR(itemNode.path("REFINE_LOTNO_ADDR").asText());
                store.setDETAIL_ADDR(itemNode.path("DETAIL_ADDR").asText());
                store.setBSN_TM_NM(itemNode.path("BSN_TM_NM").asText());
                store.setREFINE_WGS84_LAT(itemNode.path("REFINE_WGS84_LAT").asDouble());
                store.setREFINE_WGS84_LOGT(itemNode.path("REFINE_WGS84_LOGT").asDouble());

                logger.info("파싱된 가게: " + store);
                storeList.add(store);
            }

            // 데이터베이스에 저장
            goodInfluenceStoreService.saveAllStore(storeList);
            logger.info("가게 정보가 성공적으로 저장되었습니다.");

        } catch (IOException e) {
            logger.error("API 호출 중 오류가 발생했습니다: " + e.getMessage());
            return "API 호출 중 오류가 발생했습니다: " + e.getMessage();
        } finally {
            // 자원 정리
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    logger.error("BufferedReader 닫기 중 오류 발생: " + e.getMessage());
                }
            }
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }
        return "데이터가 성공적으로 저장되었습니다.";
    }
}

 

이렇게 구현을 완료한 뒤 

/api/Store 엔드포인트로 Get 요청을 하면 DB에 1000개의 데이터가 저장되는 것을 확인 할 수 있다!!