본문 바로가기
코딩/Spring Boot

Spring Boot와 Elastic Search 연동

by skyoon 2025. 1. 22.

Git Code 버전 : 0.0.7-SNAPSHOT

Spring Boot와 Elastic Search를 연동 데이터를 저장하고 불러오는 내용을 정리해 보겠다.

  1. build.gradle에 dependencies를 추가한다.
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
  1. application.yml에 접속정보를 추가한다.
spring:
    elasticsearch:
        uris: http://localhost:9200
  1. Elastic Search에 저장할 데이터 entity와 read, write 할 Repository Interface를 생성해 준다.
  • KRXListedData.java
더보기
package io.personal.stock.entity;

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;

import lombok.Data;

@Data
@Document(indexName = "krx_listed_data")
public class KRXListedData {
    @Id
    private String srtnCd; // _id 필드와 매핑

    private String basDt; // 기준일자

    private String isinCd; // 종목코드

    private String mrktCtg; // 시장구분

    private String itmsNm; // 종목명

    private String crno; // 법인등록번호

    private String corpNm; // 법인명
}
  • KRXListedDataRepository.java
더보기
package io.personal.stock.repo;

import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import io.personal.stock.entity.KRXListedData;

public interface KRXListedDataRepository extends ElasticsearchRepository<KRXListedData, String> {
}
  1. Repository를 사용해 Controller를 위한 Service를 구현한다.
  • KRXListedDataService.java
더보기
package io.personal.stock.service;

import java.util.List;

import com.fasterxml.jackson.databind.JsonNode;

import io.personal.stock.entity.KRXListedData;

public interface KRXListedDataService {
    public void save(KRXListedData KRXListedData);

    public void saveAll(JsonNode items) throws Exception;

    public List<KRXListedData> findAll();
}
  • KRXListedDataServiceImpl.java
더보기
package io.personal.stock.service.impl;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.personal.stock.entity.KRXListedData;
import io.personal.stock.repo.KRXListedDataRepository;
import io.personal.stock.service.KRXListedDataService;
import lombok.extern.log4j.Log4j2;

@Log4j2
@Service
public class KRXListedDataServiceImpl implements KRXListedDataService {
    @Autowired
    private final KRXListedDataRepository KRXListRepo;

    public KRXListedDataServiceImpl(KRXListedDataRepository KRXListRepo) {
        this.KRXListRepo = KRXListRepo;
    }

    public void save(KRXListedData KRXListedData) {
        KRXListRepo.save(KRXListedData);
    }

    public void saveAll(JsonNode items) throws Exception {
        List<KRXListedData> listData = StreamSupport.stream(items.spliterator(), false)
                .map(item -> {
                    try {
                        return new ObjectMapper().treeToValue(item, KRXListedData.class);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                })
                .peek(data -> log
                        .debug(() -> String.format("KRXListedData: %s %s", data.getIsinCd(), data.getItmsNm())))
                .collect(Collectors.toList());

        if (!listData.isEmpty()) {
            KRXListRepo.saveAll(listData);
        } else {
            log.info("No data to save");
        }
    }

    public List<KRXListedData> findAll() {
        Iterable<KRXListedData> iterable = KRXListRepo.findAll();

        return StreamSupport.stream(iterable.spliterator(), false)
                .collect(Collectors.toList());
    }
}
  1. 구현할 기능을 테스트할 UI 페이지 Controller를 구현
  • InfoController.java
더보기
package io.personal.stock.controller;

import java.util.HashMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.personal.stock.dto.OpenApiReqParam;
import io.personal.stock.service.ConfigService;
import io.personal.stock.service.KRXListedDataService;
import io.personal.stock.service.OpenApiService;
import lombok.extern.log4j.Log4j2;

@Log4j2
@Controller
public class InfoController {

    @Autowired
    ConfigService configService;

    @Autowired
    OpenApiService openApiService;

    @Autowired
    KRXListedDataService krxListedDataService;

    @GetMapping(value = "/info")
    public String info() {

        return "info";
    }

    @GetMapping(value = "/info/saveCompanyList")
    @ResponseBody
    public ResponseEntity<JsonNode> saveCompanyList(@RequestParam int numOfRows, @RequestParam int pageNo) {
        HashMap<String, String> data = configService.getConfigData("ISIN_CODE");
        LinkedMultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("serviceKey", data.get("AUTH_KEY"));
        params.add("resultType", "json");
        params.add("numOfRows", Integer.toString(numOfRows));
        params.add("pageNo", Integer.toString(pageNo));

        OpenApiReqParam reqParam = new OpenApiReqParam();
        reqParam.setEndPointURL(data.get("CALLBACK_URL"));
        reqParam.setDetailService("/getItemInfo");
        reqParam.setQueryParam(params);

        String response = openApiService.getOpenApiData(reqParam);
        log.info("Get Componey List Response: {}", response);
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode items = null;

        try {
            JsonNode jsonNode = objectMapper.readTree(response);
            items = jsonNode.path("response").path("body").path("items").path("item");
            krxListedDataService.saveAll(items);
        } catch (JsonProcessingException e) {
            log.error("JSON 파싱 오류: {}", e.getMessage());
        } catch (Exception e) {
            log.error("기타 오류: {}", e.toString());
        }

        return ResponseEntity.ok(items);
    }
}
  1. 상장 회사 목록을 가져와 Elastic Search에 저장 기능을 수행할 UI 페이지를 아래와 같이 작성
  • info.html
더보기
<!DOCTYPE html>
<html lang="ko">
    <head th:replace="~{layouts/header :: headerFragment(~{::title})}">
        <title>Config</title>
    </head>
    <body class="sb-nav-fixed">
        <nav th:replace="~{layouts/top :: top-nav}"></nav>
        <div id="layoutSidenav">
            <!-- Left Side Menu-->
            <div th:replace="~{layouts/left-side :: side-nav}"></div>

            <div id="layoutSidenav_content">
                <main>
                    <div class="container-fluid px-4">
                        <h1 class="mt-4">정보</h1>
                        <ol class="breadcrumb mb-4">
                            <li class="breadcrumb-item active">기본 정보 불러오기</li>
                        </ol>
                        <div class="row">
                            <div class="md-12">
                                <div class="card-head">
                                    <input
                                        type="text"
                                        id="ComNumOfRows"
                                        placeholder="가져올 한 페이지 수"
                                        autocomplete="off" />
                                    <input
                                        type="text"
                                        id="ComPageNo"
                                        placeholder="가져올 페이지 번호"
                                        autocomplete="off" />
                                    <button id="getCompanyInfo" class="btn-primary">상장 회사 정보 가져오기</button>
                                </div>
                            </div>
                        </div>

                        <div class="row mt-5"></div>
                    </div>
                    <s:csrfInput />
                </main>
                <footer th:replace="~{layouts/footer :: footerFragment}"></footer>
            </div>
        </div>

        <th:block th:replace="~{layouts/scripts :: scriptFragment}"></th:block>
        <script type="text/javascript">
            $(document).ready(function () {});

            $("#getCompanyInfo").click(function (e) {
                console.log("회사 목록을 가져옵니다.");
                let jsonData = {
                    numOfRows: $("#ComNumOfRows").val(),
                    pageNo: $("#ComPageNo").val(),
                };

                $.ajax({
                    url: "/info/saveCompanyList",
                    type: "GET",
                    data: jsonData,
                    success: function (companies) {
                        alert(companies.count() + " 회사 목록을 가져왔습니다.");
                    },
                    error: function (xhr, status, error) {
                        alert("회사 목록을 가져오는 데 실패했습니다.");
                    },
                });
            });
        </script>
    </body>
</html>
  1. 주가 조회 페이지에서 Elastic Search 데이터를 가져와 UI에 출력하기 위한 API와 UI 페이지를 작성
  • StockController.java
더보기
package io.personal.stock.controller;

// 추가되는 lib
import java.util.List;
import io.personal.stock.entity.KRXListedData;
import io.personal.stock.service.KRXListedDataService;

// 중략

@Log4j2
@Controller
public class StockController {

	// 추가 Service
    @Autowired
    KRXListedDataService krxListedDataService;

	// 중략
	// 추가 method
    @GetMapping(value = "/stock/getCompanyList")
    @ResponseBody
    public List<KRXListedData> getCompanyList() {
        List<KRXListedData> data = krxListedDataService.findAll();

        return data;
    }

}
  • stock.html
더보기
<script type="text/javascript">
		$(document).ready(function () {
            getCompanyList();

            // 중 략
        });
        
        // 중 략
        function getCompanyList() {
                $.ajax({
                    url: "/stock/getCompanyList",
                    type: "GET",
                    success: function (companies) {
                        companies.forEach(function (company) {
                            $("#companiesList").append("<option value=" + company.itmsNm + "></option>");
                        });
                    },
                    error: function (xhr, status, error) {
                        alert("회사 목록을 가져오는 데 실패했습니다.");
                    },
                });
            }
</script>

 

info 페이지에 아래처럼 가져올 갯수와 페이지 번호 (2000개를 가져온다면 1000개식 1번 2번)를 입력하고 상장 회사 정보 가져오기 버튼을 클릭한다.

 

로그를 보면 아래처럼 나오고, 정상적으로 Elastic Search에 입력되면 kibanadp devTool을 사용해서 아래와 같이 조회 된다.

 

주가 조회 페이지에 가서 종목명 입력란에 회사명을 넣으면 일치하는 회사 리스트가 나온다.