🌿Elasticsearch와 Spring 연동
Elasticsearch Client를 Dev Tools에서 Spring Application으로 변경하는 작업을 해보도록 하자.
Oracle을 Sql Developer에서 Java Application으로 넘어와 JDBC에서 입출력을 했듯이, 이번에는 Elasicsearch를 Spring에서 조작하는 것이다.
WSL 가상 머신의 특징
- http://localhost:9200 > elasticsearch
- http://localhost:5601 > kibana
WSL 안에서 ES가 돌아가고 있는 상태이다. 자바(스프링)에서 ES에 접근하여 CRUD 작업을 하려고 한다.
로컬 호스트는 컴퓨터로써 자기 자신을 가리킨다. PC에서 9200 또는 5601을 사용하는 프로그램을 찾으면 되는데, 이는 로컬 호스트이므로 브라우저가 다른 데 가는 게 아니라 자기 자신을 찾는 것이었다.
그런데 우리가 만든 프로그램에서는 로컬 호스트라는 IP 주소로 엘라스틱서치를 찾으면 찾지 못한다. WSL이 독립적인 컴퓨터이기 때문에 내부적으로 인터넷 주소가 따로 있다. 그래서 WSL에 할당된 진짜 주소를 알아내야만 연결을 할 수 있다.
$ ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//'
이 코드를 ubuntu에 입력하면 WSL의 진짜 주소를 알아낼 수 있다.
이때 보여주는 WSL의 진짜 주소는 사람마다 다르다.
스프링 테스트용 인덱스 추가
# 스프링 테스트용 인덱스
PUT spring
{
"mappings": {
"properties": {
"message": {
"type": "text"
}
}
}
}
POST _bulk
{ "index" : { "_index" : "spring", "_id" : "1" } }
{"message":"The quick brown fox"}
{ "index" : { "_index" : "spring", "_id" : "2" } }
{"message":"The quick brown fox jumps over the lazy dog"}
{ "index" : { "_index" : "spring", "_id" : "3" } }
{"message":"The quick brown fox jumps over the quick dog"}
{ "index" : { "_index" : "spring", "_id" : "4" } }
{"message":"Brown fox brown dog"}
{ "index" : { "_index" : "spring", "_id" : "5" } }
{"message":"Lazy jumping dog"}
{ "index" : { "_index" : "spring", "_id" : "6" } }
{"message":"지붕 위의 갈색 닭"}
{ "index" : { "_index" : "spring", "_id" : "7" } }
{"message":"지붕 위의 갈색 닭 그리고 밑에 검은색 강아지"}
{ "index" : { "_index" : "spring", "_id" : "8" } }
{"message":"지붕 위의 갈색 우는 닭 그리고 밑에 갈색 게으른 강아지"}
{ "index" : { "_index" : "spring", "_id" : "9" } }
{"message":"갈색 게으른 바보 강아지 옆에 빨간색 닭"}
{ "index" : { "_index" : "spring", "_id" : "10" } }
{"message":"졸고 있는 갈색 강아지"}
GET spring/_search
프로젝트 설정
1. pom.xml
java 버전을 11로 변경하고, Spring framework 버전을 5.0.7.RELEASE로 변경한다.
그리고 엘라스틱서치 의존성과 jackson-databind를 추가하도록 한다.
<!-- jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
<!-- elasticsearch-rest-high-level-client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.15</version>
</dependency>
elasticsearch-rest-high-level-client라고 되어 있다. 자바에서 엘라스틱서치에 접근하는 방식이 4~5개 정도 있는데, 이게 그중 가장 많이 쓰이는 방식으로, 가장 편하게 구현되어 있다.
- 엘라스틱서치
- 키바나
- 로그스테시
이 3개가 가장 대표적인 Stack인데, 반드시 모든 프로그램 버전이 동일해야 한다.
엘라스틱버전이 7.17.15라면, 그와 관련된 프로그램 API인 키바나와 로그스테시도 7.17.15이어야 한다.
2.web.xml
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<servlet-name>appServlet</servlet-name>
</filter-mapping>
한글을 사용하기 위해 web.xml에 인코딩 필터를 추가한다.
파일 생성
com.test.controller
- TestController.java
views
- list.jsp
- add.jsp
<context:component-scan base-package="com.test.controller" />
패키지를 생성하고, servlet-context.xml에서 패키지를 스캔해 주도록 한다.
🌿전체 코드
작업과정
1. TestController.java: 기본 설계를 진행한다.
2. list.jsp, add.jsp: 기본 화면 구성을 만든다.
3. TestController.java: 엘라스틱서치 연결(대화) 객체를 생성한다.
4. TestController.java: 접근하려는 인덱스(spring)를 참조하고, query를 날린다.
5. TestController.java: 세팅 정보와 결합하고 실제 검색 요청을 한다.
TestController.java
기본 설계
package com.test.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class TestController {
//목록보기
@GetMapping(value = "/list.do")
public String list(Model model) {
return "list";
}
//추가하기(폼)
@GetMapping(value = "/add.do")
public String add(Model model) {
return "add";
}
//추가하기(처리)
@PostMapping(value = "/addok.do")
public String addok(Model model) {
return "addok";
}
}
목록보기, 추가하기(폼), 추가하기(처리)로 기본적인 설계를 진행해 주었다.
엘라스틱서치 대화(연결) 객체 생성
//목록보기
@SuppressWarnings({ "deprecation", "unused" })
@GetMapping(value = "/list.do")
public String list(Model model) {
try {
//엘라스틱서치 연결 정보 객체
//HttpHost host = new HttpHost("엘라스틱서치IP", 포트번호, "프로토콜");
HttpHost host = new HttpHost("172.24.129.47", 9200, "http");
RestClientBuilder builder = RestClient.builder(host);
//client > 엘라스틱서치와의 대화 객체
RestHighLevelClient client = new RestHighLevelClient(builder);
} catch (Exception e) {
e.printStackTrace();
}
return "list";
}
외부 입출력이기 때문에 try-catch문이 반드시 필요하다.
builder는 어디에 있는 엘라스틱서치에 접속할 것인지에 대한 연결 정보를 가지고 있는 객체이다. 여기에 엘라스틱서치 IP 주소를 넣게 된다.
주소를 바로 넣을 수는 없고, Httphost로 넣게 된다. 이렇게 만들어지는 client는 엘라스틱서치와의 대화 객체이다.
세팅 정보와 결합하고 실제 검색 요청 진행
//목록보기
@SuppressWarnings({ "deprecation", "unused" })
@GetMapping(value = "/list.do")
public String list(Model model) {
try {
//엘라스틱서치 연결 정보 객체
//HttpHost host = new HttpHost("엘라스틱서치IP", 포트번호, "프로토콜");
HttpHost host = new HttpHost("172.24.129.47", 9200, "http");
RestClientBuilder builder = RestClient.builder(host);
//client > 엘라스틱서치와의 대화 객체
RestHighLevelClient client = new RestHighLevelClient(builder);
//접근하려는 인덱스 참조
SearchRequest searchRequest = new SearchRequest("spring");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().size(100); //옵션
//*** 엘라스틱서치 검색 기능
//GET spring/_search
//{
// "query": {
// "match_all": {}
// }
//}
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);
//실제 검색 요청
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits();
for (SearchHit hit : searchHits) {
System.out.println(hit);
}
//사용 종료 > 엘라스틱서치 접속 종료
client.close();
} catch (Exception e) {
e.printStackTrace();
}
return "list";
}
세팅 정보를 가지고 어떤 인덱스에서 query를 날릴지 searchRequest와 결합하는 설정을 했다.
검색을 하고 searchRequest로 요청을 했으니 searchResponse로 결과를 받도록 한다.
hits를 만들고 하나 하나의 요소를 SearchHit로 받을 수 있도록 향상된 for문을 사용했다.
사용이 끝난 뒤에는 연결을 client로 했기 때문에 client를 close()해주면 된다.
에러 발생: ElasticsearchException[java.util.concurrent.ExecutionException: java.net.ConnectException: Timeout connecting
$ sudo systemctl stop elasticsearch
그런데 지금 엘라스틱 서치를 실행하면 에러가 발생한다. ES는 기본적으로 브라우저만을 제외하고 로컬 호스트에서만 실행될 수 있도록 설정이 되어 있기 때문이다. 따라서 외부에서 실행을 해도 허용을 하도록 세팅을 해 주어야 한다.
$ sudo vi /etc/elasticsearch/elasticsearch.yml
엘라스틱서치를 잠깐 멈추고, 위 코드를 작성하여 yml로 들어간다.
i를 눌러서 편집 모드로 바꾸고, network.host: "0.0.0.0" 을 작성한다.
cluster.initial_master_nodes를 찾아 node-2를 지우고 node-1만 남겨둔다.
작성을 마친 뒤에는 esc > :wq를 입력하여 나간다.
list.jsp
기본 화면 구성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
#list th:nth-child(1) { width: 150px; }
#list th:nth-child(2) { width: auto }
#list th:nth-child(3) { width: 120px; }
</style>
</head>
<body>
<!-- list.jsp -->
<h1>Elasticsearch <small>List</small></h1>
<div class="seperate">
<span></span>
<button type="button" class="add" onclick="location.href='/elasticsearch/add.do';">문서추가하기</button>
</div>
<table id="list">
<tr>
<th>문서 아이디</th>
<th>메시지</th>
<th>정확도(스코어)</th>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</table>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script>
</script>
</body>
</html>
add.jsp
기본 화면 구성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
#list th:nth-child(1) { width: 150px; }
#list th:nth-child(2) { width: auto }
#list th:nth-child(3) { width: 120px; }
</style>
</head>
<body>
<!-- add.jsp -->
<h1>Elasticsearch <small>Add</small></h1>
<form method="POST" action="/elasticsearch/addok.do">
<table class="vertical">
<tr>
<th>문서 아이디</th>
<td><input type="text" name="id" class="short" required autofocus></td>
</tr>
<tr>
<th>메시지</th>
<td><input type="text" name="message" class="full" required></td>
</tr>
</table>
<div class="seperate">
<button type="button" class="list" onclick="location.href='/elasticsearch/list.do';">돌아가기</button>
<button type="submit" class="add">추가하기</button>
</div>
</form>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script>
</script>
</body>
</html>