"If you do nothing, nothing will happen."

프로그래밍

Springboot 와 Elasticsearch 연동하기

tedhong 2023. 6. 2. 17:40

Springboot 에서 Elasticsearch 를 연동하는 방법에 대해 알아봅니다.

 

버전

  • Java : OpenJDK 17
  • Springboot : 3.1.0

필수 의존성

  • spring-boot-starter-data-elasticsearch
  • spring-boot-starter-web
  • lombok

클래스

 

  • 프로퍼티
    • host 와 port 를 application.properties 파일에 저장합니다.
# ElasticSearch
elasticsearch.host = localhost
elasticsearch.port = 9200

 

 

  • Config
    • Elasticsearch Client 를 생성하는 설정 클래스입니다.
    • application.properties 에서 host 와 port 를 가져와 RestClient 객체를 생성합니다.
    • ElasticSearchConfig.java

@Configuration
@EnableElasticsearchRepositories // elasticsearch repository 허용
public class ElasticSearchConfig{
    @Value("${elasticsearch.dev.host}")
    String host;
    @Value("${elasticsearch.port}")
    int port;

    @Bean
    public RestClient getRestClient() {
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("", ""));
        return RestClient.builder(new HttpHost(host, port))
                .setHttpClientConfigCallback(httpClientBuilder -> {
                    httpClientBuilder.disableAuthCaching();
                    httpClientBuilder.setDefaultHeaders(List.of(
                            new BasicHeader(
                                    HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)));
                    httpClientBuilder.addInterceptorLast((HttpResponseInterceptor)
                            (response, context) ->
                                    response.addHeader("X-Elastic-Product", "Elasticsearch"));
                    return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                }).build();
    }

    @Bean
    public ElasticsearchTransport getElasticsearchTransport() {
        return new RestClientTransport(getRestClient(), new JacksonJsonpMapper());
    }

    @Bean
    public ElasticsearchClient getElasticsearchClient() {
        return new ElasticsearchClient(getElasticsearchTransport());
    }
}

 

 

  • Repository
    • ElasticsearchRepository 를 상속받는 인터페이스 입니다.
      이 인터페이스를 통해 CRUD, 검색, 페이징, 정렬등의 작업을 할 수 있습니다.
      상속시 필요한 인자 2가지는 Document 타입과 ID 의 타입 입니다.
    • SearchResultRepo.java
public interface SearchResultRepo extends ElasticsearchRepository<SearchResultDocument, Integer> {
}

 

 

  • Document
    • 검색 결과 Document 클래스입니다.
      @Document 어노테이션에 정의된 indexName 으로 document (= database) 의 이름이 정의 되고
      각 @Field 의 타입과 변수명으로 field (=column) 이 정의됩니다.
    • SearchResultDocument.java
@Getter
@Builder
@AllArgsConstructor
@Document(indexName = "search_result")
public class SearchResultDocument {
    @Id
    @Field(type = FieldType.Integer)
    private int id;
    @Field(type = FieldType.Text)
    private String keywords;
    @Field(type = FieldType.Text)
    private String resultData;

    //    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMdd")
    @Field(type = FieldType.Date, format = {DateFormat.date_hour_minute_second_millis, DateFormat.epoch_millis})
    private Date createDate;

}

 

 

  • Service
    • Service 클래스입니다.
    • Elasticsearch 의 기능 이용을 위해 SearchResultRepo, ElasticsearchOperations 객체를 사용합니다.
      각 객체의 의존성은 Service 가 생성 될 때 주입 됩니다.
    • SearchResultService.java
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class SearchResultService {

    private final SearchResultRepo searchResultRepo;
    private final ElasticsearchOperations elasticsearchOperations;

    public Page<SearchResultDocument> findAll(){
        return (Page<SearchResultDocument>) searchResultRepo.findAll();
    }

    public SearchResultDocument saveResult(SearchResultDocument doc){
        return searchResultRepo.save(doc);
    }
    public Optional<SearchResultDocument> getResultById(int id){
        return searchResultRepo.findById(id);
    }

    public void deleteResult(int id){
        searchResultRepo.deleteById(id);
    }

    public void deleteResult(SearchResultDocument doc){
        searchResultRepo.delete(doc);
    }

    public List<SearchResultDocument> searchResult(String keyword) {
        IndexCoordinates indexCoordinates = IndexCoordinates.of("search_result");
        Criteria criteria = new Criteria("keywords").contains(keyword)
                .or(new Criteria("resultData").contains(keyword));
        CriteriaQuery query = new CriteriaQuery(criteria);
        SearchHits<SearchResultDocument> searchHits = elasticsearchOperations.search(query, SearchResultDocument.class, indexCoordinates);
        return searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
    }

}

 

 

  • Controller
    • Service 객체를 이용해 비즈니스 로직을 처리하는 Controller 클래스 입니다.
    • RestController 로 지정되어 RESTful 하게 사용할 수 있습니다.
    • SearchResultController.java

 

 

 

 

 

@RequiredArgsConstructor
@RequestMapping("/")
@RestController
public class SearchResultController {
    private final SearchResultService searchResultService;

    @PostMapping("/save")
    public ResponseEntity<SearchResultDocument> createSearchResult(
            @RequestParam("keywords") String keywords,
            @RequestParam("resultData") String resultData
    ) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
        SearchResultDocument searchResult = null;
        try {
            searchResult = SearchResultDocument.builder()
                    .keywords(keywords)
                    .resultData(resultData)
                    .createDate(format.parse(format.format(new Date())))
                    .build();
        } catch (ParseException e) {
            System.out.println(e.toString());
        }

        SearchResultDocument createdSearchResult = searchResultService.saveResult(searchResult);
        return new ResponseEntity<>(createdSearchResult, HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    public ResponseEntity<SearchResultDocument> getSearchResultById(@PathVariable("id") int id) {
        return searchResultService.getResultById(id)
                .map(result -> new ResponseEntity<>(result, HttpStatus.OK))
                .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    @GetMapping("/findall")
    public ResponseEntity<Page<SearchResultDocument>> getAllSearchResults() {
        Page<SearchResultDocument> searchResults = searchResultService.findAll();
        return new ResponseEntity<>(searchResults, HttpStatus.OK);
    }

    @DeleteMapping("/delete/{id}")
    public ResponseEntity<Void> deleteSearchResult(@PathVariable("id") int id) {
        searchResultService.deleteResult(id);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

    @GetMapping("/search")
    public ResponseEntity<List<SearchResultDocument>> searchByKeyword(@RequestParam("keyword") String keyword) {
        List<SearchResultDocument> searchResults = searchResultService.searchResult(keyword);
        return new ResponseEntity<>(searchResults, HttpStatus.OK);
    }


}

예제 코드

github : TedHong/springboot-elasticsearch: Springboot 에서 Elasticsearch 연동하기 (github.com)