<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Code Cook</title>
    <link>https://yeon-dev.tistory.com/</link>
    <description>보기 좋고 깔끔한 코드를 요리하기 위해 노력하고 있습니다.</description>
    <language>ko</language>
    <pubDate>Wed, 26 Aug 2026 07:10:53 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Chef.Yeon</managingEditor>
    <image>
      <title>Code Cook</title>
      <url>https://tistory1.daumcdn.net/tistory/5586433/attach/f4ceffd667604bc0a4556b8801459a78</url>
      <link>https://yeon-dev.tistory.com</link>
    </image>
    <item>
      <title>[Spring] Elastic Cloud + Spring Boot 3.x 연동하기</title>
      <link>https://yeon-dev.tistory.com/258</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #f6e199;&quot;&gt;1. 의존성&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;build.gradle&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722803392156&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. API Key 연동&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ElasticSearchConfig&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722803378496&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableElasticsearchRepositories
public class ElasticSearchConfig extends ElasticsearchConfiguration {
    @Value(&quot;${spring.elasticsearch.api-key}&quot;)
    private String apiKey;

    @Value(&quot;${spring.elasticsearch.host}&quot;)
    private String host;

    @Override
    public ClientConfiguration clientConfiguration() {
        HttpHeaders headers = new HttpHeaders();
        headers.add(&quot;Authorization&quot;, &quot;ApiKey &quot; + apiKey);
        return ClientConfiguration.builder()
                .connectedTo(host)
                .usingSsl()
                .withDefaultHeaders(headers)
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2-1. 호스트&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;host는 &lt;b&gt;copy endpoint&lt;/b&gt;를 통해 복사한 내용을 사용한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이때, &lt;b&gt;https:// 부분은 제외&lt;/b&gt;하고&lt;b&gt; 맨 뒤에 ':443'&lt;/b&gt; 포트를 추가해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724397237262&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;예시) 0a6485cf(생략).us-central1.gcp.cloud.es.io:443&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOoyFP/btsISWnFdt3/fLuEiWbKN26KzG2G4g29p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOoyFP/btsISWnFdt3/fLuEiWbKN26KzG2G4g29p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOoyFP/btsISWnFdt3/fLuEiWbKN26KzG2G4g29p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOoyFP%2FbtsISWnFdt3%2FfLuEiWbKN26KzG2G4g29p0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;142&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCOSEH/btsISVhUzgJ/kmEGeBBdKQp6S5SZiYeRAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCOSEH/btsISVhUzgJ/kmEGeBBdKQp6S5SZiYeRAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCOSEH/btsISVhUzgJ/kmEGeBBdKQp6S5SZiYeRAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCOSEH%2FbtsISVhUzgJ%2FkmEGeBBdKQp6S5SZiYeRAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;160&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2-2. API 키 발급&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;API Key는 다음과 같이 발급 받을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCobrl/btsIVEL8Tkv/4qPdze3icNJDgPMoZpxDS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCobrl/btsIVEL8Tkv/4qPdze3icNJDgPMoZpxDS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCobrl/btsIVEL8Tkv/4qPdze3icNJDgPMoZpxDS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCobrl%2FbtsIVEL8Tkv%2F4qPdze3icNJDgPMoZpxDS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;142&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1) 좌측 상단 메뉴 &amp;gt; Management &amp;gt; Stack Management&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsfTNf/btsIS8IgNkd/36iogz61T1RSwM1WkwqAE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsfTNf/btsIS8IgNkd/36iogz61T1RSwM1WkwqAE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsfTNf/btsIS8IgNkd/36iogz61T1RSwM1WkwqAE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsfTNf%2FbtsIS8IgNkd%2F36iogz61T1RSwM1WkwqAE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;246&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2) 좌측 메뉴 Security &amp;gt; API keys &amp;gt; Create API Key &amp;gt; 원하는 Name 입력&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPKplG/btsJdOISidv/ersWKvKw8enKTIh6gGWteK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPKplG/btsJdOISidv/ersWKvKw8enKTIh6gGWteK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPKplG/btsJdOISidv/ersWKvKw8enKTIh6gGWteK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPKplG%2FbtsJdOISidv%2FersWKvKw8enKTIh6gGWteK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;286&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;발급 받은 API Key는 잊어버리지 않도록 따로 저장해두자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;API Key를 환경 변수에 추가하고 애플리케이션을 실행하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #f6e199;&quot;&gt;2. USERNAME-PASSWORD 연동&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;username과 password는 클라우드에서 deployment를 생성하고 발급 받은 것을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약 비밀번호를 잊어버렸다면 다음과 같이 재발급 받을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;username은 기본적으로 &lt;b&gt;elastic&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1absY/btsJePmnaZg/ktwIr5w0i9M2NU02KxxxgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1absY/btsJePmnaZg/ktwIr5w0i9M2NU02KxxxgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1absY/btsJePmnaZg/ktwIr5w0i9M2NU02KxxxgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1absY%2FbtsJePmnaZg%2FktwIr5w0i9M2NU02KxxxgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;316&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;ElasticSearchConfig&lt;/span&gt; &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724397906981&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableElasticsearchRepositories
public class ElasticsearchConfig extends ElasticsearchConfiguration {
    @Value(&quot;${spring.elasticsearch.username}&quot;)
    private String username;

    @Value(&quot;${spring.elasticsearch.password}&quot;)
    private String password;

    @Value(&quot;${spring.elasticsearch.host}&quot;)
    private String host;

    @Override
    public ClientConfiguration clientConfiguration() {
        return ClientConfiguration.builder()
                .connectedTo(host)
                .usingSsl()
                .withBasicAuth(username, password)
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;username과 password를 환경 변수로 등록해주면 된다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #f6e199;&quot;&gt;3. Nori 분석기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약 Nori 분석기를 사용한다면 다음과 같이 플러그인을 추가할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1) 우측 메뉴 Edit &amp;gt; Manage user settings and extensions&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crDewZ/btsJcJPc8PL/ckROrKghdDo8WGJhAbYe21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crDewZ/btsJcJPc8PL/ckROrKghdDo8WGJhAbYe21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crDewZ/btsJcJPc8PL/ckROrKghdDo8WGJhAbYe21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrDewZ%2FbtsJcJPc8PL%2FckROrKghdDo8WGJhAbYe21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;236&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2) analysis-nori 선택&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;815&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG52QE/btsJel7aEs4/GUoUqwKgfyLaQdyKYyrP61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG52QE/btsJel7aEs4/GUoUqwKgfyLaQdyKYyrP61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG52QE/btsJel7aEs4/GUoUqwKgfyLaQdyKYyrP61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG52QE%2FbtsJel7aEs4%2FGUoUqwKgfyLaQdyKYyrP61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;385&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;815&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;analysis-nori를 선택하고 Back 으로 빠져 나온 뒤, 가장 밑의 save를 클릭한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIkYQ9/btsJd9MGH0X/fd1NqMHQrH6IiXKEh5E4V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIkYQ9/btsJd9MGH0X/fd1NqMHQrH6IiXKEh5E4V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIkYQ9/btsJd9MGH0X/fd1NqMHQrH6IiXKEh5E4V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIkYQ9%2FbtsJd9MGH0X%2Ffd1NqMHQrH6IiXKEh5E4V1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;169&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>elasticcloud</category>
      <category>elasticsearch</category>
      <author>Chef.Yeon</author>
      <guid isPermaLink="true">https://yeon-dev.tistory.com/258</guid>
      <comments>https://yeon-dev.tistory.com/258#entry258comment</comments>
      <pubDate>Fri, 23 Aug 2024 16:37:28 +0900</pubDate>
    </item>
    <item>
      <title>[TIL - 20240819] Elasticsearch8.x + Kibana, Docker-compose로 구성, Credential 설정</title>
      <link>https://yeon-dev.tistory.com/257</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBJnFk/btsI6z6MVLn/gZrQFYC1KAHAkkZv96rc90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBJnFk/btsI6z6MVLn/gZrQFYC1KAHAkkZv96rc90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBJnFk/btsI6z6MVLn/gZrQFYC1KAHAkkZv96rc90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBJnFk%2FbtsI6z6MVLn%2FgZrQFYC1KAHAkkZv96rc90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;350&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; 문제점&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/blog/getting-started-with-the-elastic-stack-and-docker-compose&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 사이트&lt;/a&gt;를 참고했다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. Docker-Compose 파일 작성&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;.env&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724006287255&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ELASTIC_PASSWORD=elastic
KIBANA_PASSWORD=kibana_system
STACK_VERSION=8.7.1
CLUSTER_NAME=docker-cluster
LICENSE=basic
ES_PORT=9200
KIBANA_PORT=5601&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;docker-compose.yml&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724006410945&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;3.8&quot;

volumes:
  certs:
    driver: local
  esdata01:
    driver: local
  kibanadata:
    driver: local

networks:
  default:
    name: elastic
    external: false

services:
  setup:
    image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
    volumes:
      - certs:/usr/share/elasticsearch/config/certs
    user: &quot;0&quot;
    command: &amp;gt;
      bash -c '
        if [ x${ELASTIC_PASSWORD} == x ]; then
          echo &quot;Set the ELASTIC_PASSWORD environment variable in the .env file&quot;;
          exit 1;
        elif [ x${KIBANA_PASSWORD} == x ]; then
          echo &quot;Set the KIBANA_PASSWORD environment variable in the .env file&quot;;
          exit 1;
        fi;
        if [ ! -f config/certs/ca.zip ]; then
          echo &quot;Creating CA&quot;;
          bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip;
          unzip config/certs/ca.zip -d config/certs;
        fi;
        if [ ! -f config/certs/certs.zip ]; then
          echo &quot;Creating certs&quot;;
          echo -ne \
          &quot;instances:\n&quot;\
          &quot;  - name: es01\n&quot;\
          &quot;    dns:\n&quot;\
          &quot;      - es01\n&quot;\
          &quot;      - localhost\n&quot;\
          &quot;    ip:\n&quot;\
          &quot;      - 127.0.0.1\n&quot;\
          &quot;  - name: kibana\n&quot;\
          &quot;    dns:\n&quot;\
          &quot;      - kibana\n&quot;\
          &quot;      - localhost\n&quot;\
          &quot;    ip:\n&quot;\
          &quot;      - 127.0.0.1\n&quot;\
          &amp;gt; config/certs/instances.yml;
          bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key;
          unzip config/certs/certs.zip -d config/certs;
        fi;
        echo &quot;Setting file permissions&quot;
        chown -R root:root config/certs;
        find . -type d -exec chmod 750 \{\} \;;
        find . -type f -exec chmod 640 \{\} \;;
        echo &quot;Waiting for Elasticsearch availability&quot;;
        until curl -s --cacert config/certs/ca/ca.crt https://es01:9200 | grep -q &quot;missing authentication credentials&quot;; do sleep 30; done;
        echo &quot;Setting kibana_system password&quot;;
        until curl -s -X POST --cacert config/certs/ca/ca.crt -u &quot;elastic:${ELASTIC_PASSWORD}&quot; -H &quot;Content-Type: application/json&quot; https://es01:9200/_security/user/kibana_system/_password -d &quot;{\&quot;password\&quot;:\&quot;${KIBANA_PASSWORD}\&quot;}&quot; | grep -q &quot;^{}&quot;; do sleep 10; done;
        echo &quot;All done!&quot;;
      '
    healthcheck:
      test: [&quot;CMD-SHELL&quot;, &quot;[ -f config/certs/es01/es01.crt ]&quot;]
      interval: 1s
      timeout: 5s
      retries: 120

  es01:
    depends_on:
      setup:
        condition: service_healthy
    image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
    labels:
      co.elastic.logs/module: elasticsearch
    volumes:
      - certs:/usr/share/elasticsearch/config/certs
      - esdata01:/usr/share/elasticsearch/data
    ports:
      - ${ES_PORT}:9200
    environment:
      - node.name=es01
      - cluster.name=${CLUSTER_NAME}
      - discovery.type=single-node
      - ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
      - bootstrap.memory_lock=true
      - xpack.security.enabled=true
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
      - xpack.security.http.ssl.enabled=true
      - xpack.security.http.ssl.key=certs/es01/es01.key
      - xpack.security.http.ssl.certificate=certs/es01/es01.crt
      - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
      - xpack.security.transport.ssl.enabled=true
      - xpack.security.transport.ssl.key=certs/es01/es01.key
      - xpack.security.transport.ssl.certificate=certs/es01/es01.crt
      - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt
      - xpack.security.transport.ssl.verification_mode=certificate
      - xpack.license.self_generated.type=${LICENSE}
    ulimits:
      memlock:
        soft: -1
        hard: -1
    healthcheck:
      test:
        [
          &quot;CMD-SHELL&quot;,
          &quot;curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'&quot;,
        ]
      interval: 10s
      timeout: 10s
      retries: 120

  kibana:
    depends_on:
      es01:
        condition: service_healthy
    image: docker.elastic.co/kibana/kibana:${STACK_VERSION}
    labels:
      co.elastic.logs/module: kibana
    volumes:
      - certs:/usr/share/kibana/config/certs
      - kibanadata:/usr/share/kibana/data
    ports:
      - ${KIBANA_PORT}:5601
    environment:
      - SERVERNAME=kibana
      - ELASTICSEARCH_HOSTS=https://es01:9200
      - ELASTICSEARCH_USERNAME=kibana_system
      - ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD}
      - ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES=config/certs/ca/ca.crt
    healthcheck:
      test:
        [
          &quot;CMD-SHELL&quot;,
          &quot;curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'&quot;,
        ]
      interval: 10s
      timeout: 10s
      retries: 120&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;docker-compose up을 통해 컨테이너를 실행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. Nori 분석기 설치&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1724006609443&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# es container 확인
$ docker ps 

# es컨테이너 접속
$ docker exec -it {containerId} /bin/bash

$ cd /usr/share/elasticsearch/bin/

$ ./elasticsearch-plugin install analysis-nori&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;docker-compose stop &amp;gt; docker-compose up&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. Elasticsearch Config 설정&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;ElasticsearchConfig&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724006567679&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableElasticsearchRepositories
public class ElasticsearchConfig extends ElasticsearchConfiguration {
    @Value(&quot;${spring.elasticsearch.username}&quot;)
    private String username;

    @Value(&quot;${spring.elasticsearch.password}&quot;)
    private String password;

    @Value(&quot;${spring.elasticsearch.host}&quot;)
    private String host;

    @Override
    public ClientConfiguration clientConfiguration() {
        return ClientConfiguration.builder()
                .connectedTo(host)
                .usingSsl()
                .withBasicAuth(username, password)
                .build();
    }

    @Bean
    public ElasticsearchClient elasticsearchClient(RestClient restClient) {
        ObjectMapper objectMapper =
                new ObjectMapper()
                        .registerModule(new JavaTimeModule())
                        .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        ElasticsearchTransport transport =
                new RestClientTransport(restClient, new JacksonJsonpMapper(objectMapper));
        return new ElasticsearchClient(transport);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;애플리케이션 실행 시, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;SSL 인증서 등록 관련 에러가 발생했다.&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #ee2323;&quot;&gt;PKIX&amp;nbsp;path&amp;nbsp;building&amp;nbsp;failed:&amp;nbsp;sun.security.provider.certpath.SunCertPathBuilderException:&amp;nbsp;unable&amp;nbsp;to&amp;nbsp;find&amp;nbsp;valid&amp;nbsp;certification&amp;nbsp;path&amp;nbsp;to&amp;nbsp;requested&amp;nbsp;target&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;시도&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;인증서 객체를 생성하여 키스토어에 등록&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;docker-compose.yml의 setup 서비스 command를 살펴보면, &lt;b&gt;컨테이너 내에서 SSL/TLS 인증서 및 보안 설정&lt;/b&gt;을 구성하여 Elasticsearch와 Kibana가 통신할 수 있도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기서 SSL/TLS 인증을 위한 CA를 생성하는 부분이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724009772507&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;echo &quot;Creating CA&quot;;
bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip;
unzip config/certs/ca.zip -d config/certs;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #ee2323;&quot;&gt;PKIX&amp;nbsp;path&amp;nbsp;building&amp;nbsp;failed:&amp;nbsp;sun.security.provider.certpath.SunCertPathBuilderException:&amp;nbsp;unable&amp;nbsp;to&amp;nbsp;find&amp;nbsp;valid&amp;nbsp;certification&amp;nbsp;path&amp;nbsp;to&amp;nbsp;requested&amp;nbsp;target&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 오류는 &lt;b&gt;SSL 인증서 검증&lt;/b&gt; 과정에서 발생하는 문제로,&amp;nbsp;애플리케이션이 Elasticsearch 서버의 인증서에 대해 &lt;b&gt;신뢰할 수 있는 인증서 경로(&amp;nbsp;PKIX path building failed)를 찾지 못했을 때&lt;/b&gt; 발생할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이를 해결하기 위해 클라이언트 측에서 해당 &lt;b&gt;인증서를 신뢰하도록 설정&lt;/b&gt;해줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Java Keystore에 해당 인증서를 추가하자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Stack Overflow에서 &lt;b&gt;ca.crt를 디코딩하고 인증서 객체를 생성해 KeyStore에 저장 하는 방법&lt;/b&gt;을 찾았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/75081596/how-to-configure-security-in-elasticsearch-8-5-3-using-starter-data-elasticsearc&quot;&gt;https://stackoverflow.com/questions/75081596/how-to-configure-security-in-elasticsearch-8-5-3-using-starter-data-elasticsearc&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724009786705&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How to configure security in elasticsearch 8.5.3 using Starter Data Elasticsearch 3.0.1 in maven java springboot&quot; data-og-description=&quot;I was able to create an elasticsearch 8.5.3 server as a docker image, but with security completely disabled, and in my springboot application I am using ElasticsearchRepository to perform insert,up...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/75081596/how-to-configure-security-in-elasticsearch-8-5-3-using-starter-data-elasticsearc&quot; data-og-url=&quot;https://stackoverflow.com/questions/75081596/how-to-configure-security-in-elasticsearch-8-5-3-using-starter-data-elasticsearc&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjkAVE/hyWSn3690Y/swmkyuPXLWhHAyzDeS39Qk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/75081596/how-to-configure-security-in-elasticsearch-8-5-3-using-starter-data-elasticsearc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/75081596/how-to-configure-security-in-elasticsearch-8-5-3-using-starter-data-elasticsearc&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjkAVE/hyWSn3690Y/swmkyuPXLWhHAyzDeS39Qk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to configure security in elasticsearch 8.5.3 using Starter Data Elasticsearch 3.0.1 in maven java springboot&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I was able to create an elasticsearch 8.5.3 server as a docker image, but with security completely disabled, and in my springboot application I am using ElasticsearchRepository to perform insert,up...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt; elasticsearch 컨테이너의 &lt;b&gt;/usr/share/elasticsearch/config/certs/ca/ca.crt&lt;/b&gt;를 열면 다음과 같은 내용이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724399077726&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-----BEGIN CERTIFICATE-----
MIIDSTCCAjGgAwIBAgIUR1gPSYQEiqCeWaI7ir1hQf9BZ9EwDQYJKoZIhvcNAQEL
(생략)
OL01woHZE+jHFz4i/31pARjL6Fvq8vsrjXJc2Uo=
-----END CERTIFICATE-----&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;첫 번째 줄(BEGIN)과 마지막 줄(END)를 제외하고 복사하여 application.yml에 추가한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;나는 환경 변수를 만들어 추가했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724399091384&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  elasticsearch:
    host: ${ELASTIC_HOST}
    username: ${ELASTIC_USER}
    password: ${ELASTIC_PASSWORD}
    client:
      certificate: ${ELASTIC_CA}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음과 같은 방식으로 해도 된다. &lt;b&gt;개행이 없어야 하기 때문에 '|' 을 추가하여 여러 줄의 텍스트를 하나의 문자열로 취급&lt;/b&gt;하도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724399107089&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    client:
      certificate: |
        MIIDSTCCAjGgAwIBAgIUR1gPSYQEiqCeWaI7ir1hQf9BZ9EwDQYJKoZIhvcNAQEL
		(생략)
        OL01woHZE+jHFz4i/31pARjL6Fvq8vsrjXJc2Uo=&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;ElasticsearchConfig&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724399121654&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableElasticsearchRepositories
public class ElasticsearchConfig extends ElasticsearchConfiguration {
    @Value(&quot;${spring.elasticsearch.client.certificate}&quot;)
    private String certificateBase64;
    
    @Value(&quot;${spring.elasticsearch.username}&quot;)
    private String username;

    @Value(&quot;${spring.elasticsearch.password}&quot;)
    private String password;

    @Value(&quot;${spring.elasticsearch.host}&quot;)
    private String host;

    @Override
    public ClientConfiguration clientConfiguration() {
        try {
            return ClientConfiguration.builder()
                    .connectedTo(&quot;localhost:9200&quot;)
                    .usingSsl(getSSLContext())
                    .withBasicAuth(username, password)
                    .build();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Bean
    public ElasticsearchClient elasticsearchClient(RestClient restClient) {
        ObjectMapper objectMapper =
                new ObjectMapper()
                        .registerModule(new JavaTimeModule())
                        .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        ElasticsearchTransport transport =
                new RestClientTransport(restClient, new JacksonJsonpMapper(objectMapper));
        return new ElasticsearchClient(transport);
    }

    private SSLContext getSSLContext() throws Exception {
        byte[] decodedCertificate = Base64.getMimeDecoder().decode(certificateBase64);

        CertificateFactory certificateFactory = CertificateFactory.getInstance(&quot;X.509&quot;);
        X509Certificate ca;
        try (InputStream certificateInputStream = new ByteArrayInputStream(decodedCertificate)) {
            ca = (X509Certificate) certificateFactory.generateCertificate(certificateInputStream);
        }

        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null); 
        keyStore.setCertificateEntry(&quot;ca&quot;, ca);

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(keyStore);

        SSLContext sslContext = SSLContext.getInstance(&quot;TLS&quot;);
        sslContext.init(null, tmf.getTrustManagers(), new java.security.SecureRandom());
        return sslContext;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&lt;/b&gt;해결&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;cmd로 인증서 등록&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. ca.crt 로컬에 복사&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;C드라이브 하위에 elasticsearch 폴더를 생성하고,&lt;b&gt; cmd를 관리자 권한&lt;/b&gt;으로 실행했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;(폴더를 생성하지 않고 C드라이브에 복사해도 된다)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724008629740&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker ps

$ docker cp {es container id}:/usr/share/elasticsearch/config/certs/ca/ca.crt /elasticsearch&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;elasticsearch 폴더에&lt;b&gt; ca.crt&lt;/b&gt; 파일이 복사된 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. 인증서 등록&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;자바가 설치된 경로의 &lt;b&gt;lib 폴더&lt;/b&gt;로 이동해서 &lt;b&gt;자바 인증서 저장소에 ca.crt를 등록&lt;/b&gt;해주자&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724009250039&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd {JAVA_HOME}\lib

$ keytool.exe -import -alias {별칭} -keystore &quot;{JAVA_HOME}\lib\security\cacerts&quot; -storepass changeit -file &quot;인증서 경로&quot;

예시: keytool.exe -import -alias es -keystore &quot;{JAVA_HOME}\lib\security\cacerts&quot; -storepass changeit -file &quot;C:\elasticsearch\ca.crt&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;등록한 인증서를 삭제하고 싶다면 다음 명령을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724399855445&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ keytool -delete -alias es -keystore &quot;{JAVA_HOME}\lib\security\cacerts&quot; -storepass changeit&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;참고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://ssow93.tistory.com/73&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ssow93.tistory.com/73&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724009559014&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[에러] PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certificati&quot; data-og-description=&quot;  SSL 인증서 등록 관련 에러 발생 고객사 쪽의 API를 호출을 하고 있었는데, 이 사이트에 https를 적용하면서 정상적으로 호출이 되지 않고 아래와 같은 에러가 발생하였다. 이에 관련해서 조치&quot; data-og-host=&quot;ssow93.tistory.com&quot; data-og-source-url=&quot;https://ssow93.tistory.com/73&quot; data-og-url=&quot;https://ssow93.tistory.com/73&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dLjwPo/hyWOcpFCtl/NEOhKbEckkFHdqMUSCtU3K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/UFW1M/hyWOpic82j/6yU4CEWwxfdR5SceCEmtzK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://ssow93.tistory.com/73&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ssow93.tistory.com/73&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dLjwPo/hyWOcpFCtl/NEOhKbEckkFHdqMUSCtU3K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/UFW1M/hyWOpic82j/6yU4CEWwxfdR5SceCEmtzK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[에러] PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certificati&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  SSL 인증서 등록 관련 에러 발생 고객사 쪽의 API를 호출을 하고 있었는데, 이 사이트에 https를 적용하면서 정상적으로 호출이 되지 않고 아래와 같은 에러가 발생하였다. 이에 관련해서 조치&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ssow93.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL</category>
      <category>elasticsearch</category>
      <category>kibana</category>
      <category>til</category>
      <author>Chef.Yeon</author>
      <guid isPermaLink="true">https://yeon-dev.tistory.com/257</guid>
      <comments>https://yeon-dev.tistory.com/257#entry257comment</comments>
      <pubDate>Mon, 19 Aug 2024 04:39:23 +0900</pubDate>
    </item>
    <item>
      <title>[TIL - 20240817] Spring Boot 3.x + Elasticsearch 8.x jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime`</title>
      <link>https://yeon-dev.tistory.com/256</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh5s2U/btsI5sfM9Xw/GLklF2yVaGClWNXWgs6gTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh5s2U/btsI5sfM9Xw/GLklF2yVaGClWNXWgs6gTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh5s2U/btsI5sfM9Xw/GLklF2yVaGClWNXWgs6gTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh5s2U%2FbtsI5sfM9Xw%2FGLklF2yVaGClWNXWgs6gTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;350&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; 문제점 - LocalDateTime 직렬화/역직렬화&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Elasticsearch 검색을 수행하던 도중 오류가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723822543928&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:8080/api/v1/posts/suggest?keyword=통밀빵&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Nanum Gothic';&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;com.&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;fasterxml.jackson.databind.exc.InvalidDefinitionException:&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;Java&amp;nbsp;8&amp;nbsp;date/time&amp;nbsp;type&amp;nbsp;`java.time.LocalDateTime`&amp;nbsp;not&amp;nbsp;supported&amp;nbsp;by&amp;nbsp;default:&amp;nbsp;add&amp;nbsp;Module&lt;/span&gt;&quot;com.fasterxml.jackson.datatype:jackson-datatype-jsr310&quot;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;to&amp;nbsp;enable&amp;nbsp;handling&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;LocalDateTime의 직렬화/역직렬화 문제가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&lt;/b&gt;해결&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; ElasticsearchConfig에 ElasticsearchClient 빈을 생성하는 코드를 추가하고, JacksonJsonpMapper에 JavaTimeModule을 등록하여 LocalDateTime을 처리할 수 있도록 설정했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;ElasticsearchConfig&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723823377064&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableElasticsearchRepositories
public class ElasticsearchConfig extends ElasticsearchConfiguration {
    @Value(&quot;${spring.elasticsearch.username}&quot;)
    private String username;

    @Value(&quot;${spring.elasticsearch.password}&quot;)
    private String password;

    @Value(&quot;${spring.elasticsearch.host}&quot;)
    private String host;

    @Override
    public ClientConfiguration clientConfiguration() {
        return ClientConfiguration.builder()
                .connectedTo(host)
                .build();
    }

    @Bean
    public ElasticsearchClient elasticsearchClient(RestClient restClient) {
        ObjectMapper objectMapper =
                new ObjectMapper()
                        .registerModule(new JavaTimeModule())
                        .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        ElasticsearchTransport transport =
                new RestClientTransport(restClient, new JacksonJsonpMapper(objectMapper));
        return new ElasticsearchClient(transport);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. ObjectMapper 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Jackson 라이브러리의 ObjectMapper는 JSON 데이터를 Java 객체로 변환하거나, Java 객체를 JSON으로 변환하는 데 사용된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;registerModule(new JavaTimeModule())&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;JavaTimeModule을 등록하여 LocalDateTime과 같은 &lt;b&gt;Java8 날짜/시간 타입을 처리&lt;/b&gt;할 수 있도록 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;날짜/시간이 타임 스탬프가 아닌 ISO-8601 문자열 형식('yyyy-MM-dd'T'HH:mm:ss.SSSZ ')으로 직렬화 되도록 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. ElasticsearchTransport 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;RestClientTransport는 Elasticsearch 서버와의 통신을 담당한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;RestClientTransport를 생성할 때, JacksonJsonpMapper에 objectMapper를 전달하여 LocalDateTime을 지원하도록 설정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. ElasticsearchClient&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;설정한 ElasticsearchTransport를 사용하여 ElasticsearchClient를 생성하고 빈으로 등록한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL</category>
      <category>elasticsearch</category>
      <category>til</category>
      <author>Chef.Yeon</author>
      <guid isPermaLink="true">https://yeon-dev.tistory.com/256</guid>
      <comments>https://yeon-dev.tistory.com/256#entry256comment</comments>
      <pubDate>Sat, 17 Aug 2024 01:44:16 +0900</pubDate>
    </item>
    <item>
      <title>[TIL-20240813-17] Elasticsearch 자동 완성 (2)</title>
      <link>https://yeon-dev.tistory.com/255</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vmkzZ/btsI1oRKlA5/KPQrMIUBCK9tKnukMAOrx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vmkzZ/btsI1oRKlA5/KPQrMIUBCK9tKnukMAOrx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vmkzZ/btsI1oRKlA5/KPQrMIUBCK9tKnukMAOrx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvmkzZ%2FbtsI1oRKlA5%2FKPQrMIUBCK9tKnukMAOrx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;350&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://yeon-dev.tistory.com/254&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yeon-dev.tistory.com/254&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723479602872&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[TIL - 20240810] ElasticSearch 자동 완성 (1)&quot; data-og-description=&quot;https://yeon-dev.tistory.com/253&amp;nbsp;[Spring] Spring Boot3.x Docker Compose로 ElasticSearch 8.x+Kibana 구성 (Local)프로젝트 루트 디렉토리에 docker-compose.yml을 생성한다.&amp;nbsp;1. Docker Compose 파일 작성docker-compose.ymlversion: '3.7'servi&quot; data-og-host=&quot;yeon-dev.tistory.com&quot; data-og-source-url=&quot;https://yeon-dev.tistory.com/254&quot; data-og-url=&quot;https://yeon-dev.tistory.com/254&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxskWB/hyWKBJYeVz/6zvA9VF9XRUi7NjoR0zk00/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/S24jd/hyWOrZ1QjB/jyKCt169jvANjkCQgOVhc0/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/bymqOB/hyWKFexu9N/avKHH2ut7SzSu4vu08jmR0/img.png?width=1100&amp;amp;height=350&amp;amp;face=0_0_1100_350&quot;&gt;&lt;a href=&quot;https://yeon-dev.tistory.com/254&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yeon-dev.tistory.com/254&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxskWB/hyWKBJYeVz/6zvA9VF9XRUi7NjoR0zk00/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/S24jd/hyWOrZ1QjB/jyKCt169jvANjkCQgOVhc0/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/bymqOB/hyWKFexu9N/avKHH2ut7SzSu4vu08jmR0/img.png?width=1100&amp;amp;height=350&amp;amp;face=0_0_1100_350');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[TIL - 20240810] ElasticSearch 자동 완성 (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://yeon-dev.tistory.com/253&amp;nbsp;[Spring] Spring Boot3.x Docker Compose로 ElasticSearch 8.x+Kibana 구성 (Local)프로젝트 루트 디렉토리에 docker-compose.yml을 생성한다.&amp;nbsp;1. Docker Compose 파일 작성docker-compose.ymlversion: '3.7'servi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yeon-dev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; 문제점1 - 검색 결과 우선 순위&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;PostMan으로 요청하면 응답이 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723915391393&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:8080/api/v1/posts/suggest?keyword=맛있는 통밀빵&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1723839010676&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[
    &quot;통밀빵 정말 맛있는 카페&quot;,
    &quot;맛있는 통밀빵 간식 만들기&quot;,
    &quot;맛있는 비건 디저트 카페&quot;,
    &quot;통밀빵 맛있게 먹는 방법&quot;,
    &quot;통밀 식빵 맛있게 먹는 방법&quot;,
    &quot;통밀빵 효능&quot;,
    &quot;감성빵집 통밀빵&quot;,
    &quot;통밀빵 샌드위치&quot;,
    &quot;100% 통밀빵 토스트&quot;,
    &quot;스완베이커리 통밀빵&quot;,
    &quot;파리바게트 통밀빵 다이어트&quot;
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&quot;맛있는 통밀빵 간식 만들기&quot;가 검색어와 일치하는 구문이 있음에도 2번째로 위치한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또한, &quot;맛있는 비건 디저트 카페&quot;는 &quot;통밀 식빵 맛있게 먹는 방법&quot; 보다 우선 순위가 높다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;b&gt; 문제점1 시도&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #f6e199;&quot;&gt;&lt;b&gt;1. Kibana 콘솔&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1723756230973&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /posts/_search
{
  &quot;size&quot;: 5,
  &quot;query&quot;: {
    &quot;bool&quot;: {
      &quot;should&quot;: [
        {
          &quot;match_phrase&quot;: {
            &quot;title&quot;: {
              &quot;query&quot;: &quot;맛있는 통밀빵&quot;,
              &quot;slop&quot;: 1, 
              &quot;boost&quot;: 3.0
            }
          }
        },
        {
          &quot;match&quot;: {
            &quot;title.ngram&quot;: {
              &quot;query&quot;: &quot;맛있는 통밀빵&quot;,
              &quot;operator&quot;: &quot;and&quot;, 
              &quot;boost&quot;: 2.0
            }
          }
        }
      ],
      &quot;minimum_should_match&quot;: 1
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. Bool Query&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여러 쿼리를 결합하는데 사용한다. &lt;/span&gt;상위에 bool 쿼리를 사용하고, 그 안에 다른 쿼리를 넣어 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;must&lt;/b&gt;: 쿼리가 참인 문서 검색&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;must_not&lt;/b&gt;: 쿼리가 거짓인 문서 검색&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;should&lt;/b&gt;: 검색 결과 중 이 쿼리에 해당하는 문서의 점수를 높임&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;filter&lt;/b&gt;: 쿼리가 참인 문서를 검색하지만, 스코어를 계산하지 않음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;위에서는 should를 사용하여 두 개의 조건을 정의하고 있다.&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;minimum_should_match: 1&lt;/b&gt;로 설정하여, 두 조건 중 하나라도 일치하면 문서를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. should&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-1. match_phrase 쿼리&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;match_phrase 쿼리는 검색어의 단어가 모두 존재하고, 순서가 같은 문서를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;slop: 1&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;slop은 단어 사이에 허용되는 단어 개수를 의미한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;slop: 1을 통해 &quot;맛있는 통밀빵&quot;을 입력했을 때, 예를 들어 &lt;b&gt;&quot;맛있는 &lt;span style=&quot;color: #006dd7;&quot;&gt;최고의&lt;/span&gt; 통밀빵&quot;&lt;/b&gt;도 조건을 만족할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;boost: 3.0&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;boost는 조건의 중요도를 의미한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;match_phrase 조건에 높은 boost를 설정하여, 이 조건을 만족하면 가장 높은 우선 순위를 갖도록 했다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-2. match 쿼리&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;N-gram 분석기를 사용하는 titme.ngram 필드에서 검색을 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;operator: &quot;and&quot;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;모든 단어가 문서에 포함되어야 결과에 포함된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;boost: 2.0&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;match_phrase 보다는 낮은 우선 순위를 갖는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. 서비스, 레포지토리 코드&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;PostCustomElasticRepositoryImpl&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723822379008&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class PostCustomElasticRepositoryImpl implements PostCustomElasticRepository {
    private final ElasticsearchClient elasticsearchClient;
    private final String POST_INDEX = &quot;posts&quot;;

    @Override
    public List&amp;lt;PostDocument&amp;gt; findAutoCompleteSuggestion(String keyword, int size) {
        try {
            SearchResponse&amp;lt;PostDocument&amp;gt; response = elasticsearchClient.search(s -&amp;gt; s
                    .index(POST_INDEX)
                    .size(size)
                    .query(q -&amp;gt; q
                            .bool(b -&amp;gt; b
                                    .should(sh -&amp;gt; sh
                                            .matchPhrase(mp -&amp;gt; mp
                                                    .field(&quot;title&quot;)
                                                    .query(keyword)
                                                    .slop(1)
                                                    .boost(3.0f)
                                            )
                                    )
                                    .should(sh -&amp;gt; sh
                                            .match(m -&amp;gt; m
                                                    .field(&quot;title.ngram&quot;)
                                                    .query(keyword)
                                                    .operator(Operator.And)
                                                    .boost(2.0f)
                                            )
                                    ).minimumShouldMatch(&quot;1&quot;)
                            )
                    ), PostDocument.class
            );

            return response.hits().hits().stream()
                    .map(Hit::source)
                    .toList();
        } catch (IOException e) {
            throw new ElasticsearchOperationException(ErrorCode.ES_OPERATION_FAILED);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;PostQueryService&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723822400713&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    public List&amp;lt;String&amp;gt; suggestByKeyword(String keyword, int size) {
        return postElasticRepository.findAutoCompleteSuggestion(keyword, size)
                .stream()
                .map(PostDocument::getTitle)
                .toList();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ES 검색 도중 &lt;b&gt;LocalDateTime 직렬화/역직렬화 문제&lt;/b&gt;가 있었다. 자세한 내용은 다음 링크에서 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://yeon-dev.tistory.com/256&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yeon-dev.tistory.com/256&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723826774402&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[TIL - 20240817] Spring Boot 3.x + Elasticsearch 8.x jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type &amp;#96;jav&quot; data-og-description=&quot; 문제점 - LocalDateTime 직렬화/역직렬화Elasticsearch 검색을 수행하던 도중 오류가 발생했다.http://localhost:8080/api/v1/posts/suggest?keyword=통밀빵&amp;nbsp; com.fasterxml.jackson.databind.exc.InvalidDefinitionException:&amp;nbsp;Java&amp;nbsp;8&amp;nbsp;d&quot; data-og-host=&quot;yeon-dev.tistory.com&quot; data-og-source-url=&quot;https://yeon-dev.tistory.com/256&quot; data-og-url=&quot;https://yeon-dev.tistory.com/256&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cFpUFj/hyWSfY1iQz/Rolg8wFwXEBK32kvyKX8fk/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/cZkLqL/hyWSklIJTE/VyKmlfbkZUAl0D7xYXpokk/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/cgqcq7/hyWSlSs97p/I1kCI6SE0KWEP4EKYWILxK/img.png?width=1100&amp;amp;height=350&amp;amp;face=0_0_1100_350&quot;&gt;&lt;a href=&quot;https://yeon-dev.tistory.com/256&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yeon-dev.tistory.com/256&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cFpUFj/hyWSfY1iQz/Rolg8wFwXEBK32kvyKX8fk/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/cZkLqL/hyWSklIJTE/VyKmlfbkZUAl0D7xYXpokk/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/cgqcq7/hyWSlSs97p/I1kCI6SE0KWEP4EKYWILxK/img.png?width=1100&amp;amp;height=350&amp;amp;face=0_0_1100_350');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[TIL - 20240817] Spring Boot 3.x + Elasticsearch 8.x jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `jav&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; 문제점 - LocalDateTime 직렬화/역직렬화Elasticsearch 검색을 수행하던 도중 오류가 발생했다.http://localhost:8080/api/v1/posts/suggest?keyword=통밀빵&amp;nbsp; com.fasterxml.jackson.databind.exc.InvalidDefinitionException:&amp;nbsp;Java&amp;nbsp;8&amp;nbsp;d&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yeon-dev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;PostMan으로 요청을 보내보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;size는 디폴트 값(=5)이 설정되어 있어서 요청 파라미터에 포함하지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723833313011&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:8080/api/v1/posts/suggest?keyword=맛있는 통밀빵&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB98a0/btsI5Tj4ffE/z1x7825gk9nbXgLoxuieKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB98a0/btsI5Tj4ffE/z1x7825gk9nbXgLoxuieKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB98a0/btsI5Tj4ffE/z1x7825gk9nbXgLoxuieKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB98a0%2FbtsI5Tj4ffE%2Fz1x7825gk9nbXgLoxuieKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;540&quot; height=&quot;171&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&quot;맛있는 비건 디저트 카페&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;두 가지 조건 모두 맞지 않아 결과에 포함되지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;우선 순위&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;검색어와 일치하는 구문이 있는 &quot;맛있는 통밀빵 간식 만들기&quot;가 가장 위에 위치한다. (조건1 충족)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&quot;통밀빵&quot;과 &quot;맛있는&quot;이 포함된 &quot;통밀빵 정말 맛있는 카페&quot;가 그 다음에 위치한다. (조건2 충족)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&quot;통밀빵 맛있게 먹는 방법&quot;&lt;/b&gt;은 결과에 포함되지 않는다. &quot;맛있는&quot;이 아닌 &quot;맛있게&quot;가 포함되어 있기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;검색어와 유사하므로 결과에 포함될 필요&lt;/b&gt;&lt;/span&gt;가 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt; 문제점1 &lt;/b&gt;&lt;/b&gt;해결&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #f6e199;&quot;&gt;&lt;b&gt;1. match 쿼리(Nori 분석기 사용) 추가&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1723913433129&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /posts/_search
{
  &quot;size&quot;: 10, 
  &quot;query&quot;: {
    &quot;bool&quot;: {
      &quot;should&quot;: [
        {
          &quot;match_phrase&quot;: {
            &quot;title&quot;: {
              &quot;query&quot;: &quot;맛있는 통밀빵&quot;,
              &quot;slop&quot;: 1, 
              &quot;boost&quot;: 3.0
            }
          }
        },
        {
          &quot;match&quot;: {
            &quot;title.nori&quot;: {
              &quot;query&quot;: &quot;맛있는 통밀빵&quot;,
              &quot;operator&quot;: &quot;and&quot;, 
              &quot;boost&quot;: 2.0,
              &quot;fuzziness&quot;: 1              
            }
          }
        }
      ],
      &quot;minimum_should_match&quot;: 1
    }
  }
}

결과: 
[
    &quot;맛있는 통밀빵 간식 만들기&quot;,
    &quot;맛있는 최고의 통밀빵 레시피&quot;, 
    &quot;통밀빵 정말 맛있는 카페&quot;, 
    &quot;통밀빵 맛있게 먹는 방법&quot;, 
    &quot;통밀 식빵 맛있게 먹는 방법&quot;
]&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1-1. match 쿼리&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;Nori 분석기를 사용하는 title.nori 필드에서 검색을 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Nori 분석기의 &lt;b&gt;nori_part_of_speech&lt;/b&gt; 필터를 사용해 &quot;맛있는 통밀빵&quot; 에서 &lt;b&gt;'는'&lt;/b&gt;과 같은 조사를 제거하고 분석하기 위함이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;fuzziness: 1&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;fuzziness는 두 문자열 사이의 최소 편집 작업(삽입/제거/변경/인접한 두 문자 교체) 수를 의미한다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;변경 (&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;b&lt;/b&gt;&lt;/span&gt;ox &amp;rarr; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;f&lt;/b&gt;&lt;/span&gt;ox)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;제거 (&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;b&lt;/b&gt;&lt;/span&gt;lack &amp;rarr; lack)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;삽입 (sic &amp;rarr; sic&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;k&lt;/b&gt;&lt;/span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;인접한 두 문자 교체 (&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ac&lt;/b&gt;&lt;/span&gt;t &amp;rarr; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ca&lt;/b&gt;&lt;/span&gt;t)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;fuzziness가 없을 때, 만약 검색어가 &lt;b&gt;&quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;멋&lt;/span&gt;있는 통밀빵&quot;&lt;/b&gt;이라면 아무런 문서도 반환되지 않을 것이다.&lt;br /&gt;&lt;/span&gt;하지만 fuzziness: 1을 통해 한 글자의 오타를 허용하기 때문에, &quot;멋있는 통밀빵&quot; 이라고 검색해도 다음과 같은 결과를 반환한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1723913886091&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[
	&quot;통밀빵 정말 맛있는 카페&quot;,
    &quot;맛있는 통밀빵 간식 만들기&quot;,
    &quot;통밀빵 맛있게 먹는 방법&quot;,
    &quot;맛있는 최고의 통밀빵 레시피&quot;,
    &quot;통밀 식빵 맛있게 먹는 방법&quot;
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #f6e199;&quot;&gt;&lt;b&gt;2. match 쿼리(N-gram 분석기 사용) 추가&lt;/b&gt; &lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1723902372112&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /posts/_search
{
  &quot;size&quot;: 10, 
  &quot;query&quot;: {
    &quot;bool&quot;: {
      &quot;should&quot;: [
        {
          &quot;match_phrase&quot;: {
            &quot;title&quot;: {
              &quot;query&quot;: &quot;맛있는 통밀빵&quot;,
              &quot;slop&quot;: 1, 
              &quot;boost&quot;: 3.0
            }
          }
        },
        {
          &quot;match&quot;: {
            &quot;title.nori&quot;: {
              &quot;query&quot;: &quot;맛있는 통밀빵&quot;,
              &quot;operator&quot;: &quot;and&quot;, 
              &quot;boost&quot;: 2.0,
              &quot;fuzziness&quot;: 1
            }
          }
        },
        {
          &quot;match&quot;: {
            &quot;title.ngram&quot;: {
              &quot;query&quot;: &quot;맛있는 통밀빵&quot;
            }
          }
        }
      ],
      &quot;minimum_should_match&quot;: 1
    }
  }
}

결과: 
[
	&quot;맛있는 통밀빵 간식 만들기&quot;, 
    &quot;맛있는 최고의 통밀빵 레시피&quot;, 
    &quot;통밀빵 정말 맛있는 카페&quot;, 
    &quot;통밀빵 맛있게 먹는 방법&quot;,
    &quot;통밀 식빵 맛있게 먹는 방법&quot;,
    &quot;맛있는 비건 디저트 카페&quot;,
    &quot;통밀빵 효능&quot;,
    &quot;감성빵집 통밀빵&quot;,
    &quot;통밀빵 샌드위치&quot;,
    &quot;100% 통밀빵 토스트&quot;
]&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-1. match 쿼리&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;N-gram 분석기가 적용된 title.ngram 필드에서 부분 문자열을 기반으로 유사한 문서를 찾는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 쿼리문으로 인해 &quot;맛있는 통밀빵&quot;을 입력했을 때 &quot;맛있는 비건 디저트 카페&quot;나 &quot;통밀빵 효능&quot;과 같이 부분 문자열이 일치하는 경우도 결과에 포함된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 때문에 관련이 적은 결과가 포함될 수 있어 앞선 2개의 조건 보다 낮은 우선 순위를 갖도록 했다. (디폴트 1.0)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. 서비스, 레포지토리 코드&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;PostCustomElasticRepositoryImpl&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723902244932&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class PostCustomElasticRepositoryImpl implements PostCustomElasticRepository {
    private final ElasticsearchClient elasticsearchClient;
    private final String POST_INDEX = &quot;posts&quot;;

    @Override
    public List&amp;lt;PostDocument&amp;gt; findAutoCompleteSuggestion(String keyword, int size) {
        try {
            SearchResponse&amp;lt;PostDocument&amp;gt; response = elasticsearchClient.search(s -&amp;gt; s
                    .index(POST_INDEX)
                    .size(size)
                    .query(q -&amp;gt; q
                            .bool(b -&amp;gt; b
                                    .should(sh -&amp;gt; sh
                                            .matchPhrase(mp -&amp;gt; mp
                                                    .field(&quot;title&quot;)
                                                    .query(keyword)
                                                    .slop(1)
                                                    .boost(3.0f)
                                            )
                                    )
                                    .should(sh -&amp;gt; sh
                                            .match(m -&amp;gt; m
                                                    .field(&quot;title.nori&quot;)
                                                    .query(keyword)
                                                    .operator(Operator.And)
                                                    .boost(2.0f)
                                                    .fuzziness(&quot;1&quot;)
                                            )
                                    )
                                    .should(sh -&amp;gt; sh
                                            .match(m -&amp;gt; m
                                                    .field(&quot;title.ngram&quot;)
                                                    .query(keyword)
                                            )
                                    ).minimumShouldMatch(&quot;1&quot;)
                            )
                    ), PostDocument.class
            );

            return response.hits().hits().stream().map(Hit::source).toList();
        } catch (IOException e) {
            throw new ElasticsearchOperationException(ErrorCode.ES_OPERATION_FAILED);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;PostMan으로 요청해보았다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723902789031&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:8080/api/v1/posts/suggest?keyword=맛있는 통밀빵&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lXh59/btsI6hSG5Kd/sDP16WhfpdEofCFkBlvkVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lXh59/btsI6hSG5Kd/sDP16WhfpdEofCFkBlvkVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lXh59/btsI6hSG5Kd/sDP16WhfpdEofCFkBlvkVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlXh59%2FbtsI6hSG5Kd%2FsDP16WhfpdEofCFkBlvkVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;553&quot; height=&quot;304&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. match_phrase 쿼리만 사용된 결과&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723908959783&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[&quot;맛있는 통밀빵 간식 만들기&quot;, &quot;맛있는 최고의 통밀빵 레시피&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. match 쿼리(Nori 분석기 사용) 추가한 결과&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723909036596&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[&quot;맛있는 통밀빵 간식 만들기&quot;, &quot;맛있는 최고의 통밀빵 레시피&quot;,
&quot;통밀빵 정말 맛있는 카페&quot;, &quot;통밀빵 맛있게 먹는 방법&quot;, &quot;통밀 식빵 맛있게 먹는 방법&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. match 쿼리(Ngram 분석기 사용) 추가한 결과&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723909139275&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[&quot;맛있는 통밀빵 간식 만들기&quot;, &quot;맛있는 최고의 통밀빵 레시피&quot;,
&quot;통밀빵 정말 맛있는 카페&quot;, &quot;통밀빵 맛있게 먹는 방법&quot;, &quot;통밀 식빵 맛있게 먹는 방법&quot;,
&quot;맛있는 비건 디저트 카페&quot;, &quot;통밀빵 효능&quot;, &quot;감성빵집 통밀빵&quot;, &quot;통밀빵 샌드위치&quot;, &quot;100% 통밀빵 토스트&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; 문제점2 - 가독성&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 findAutoCompleteSuggestion 메서드는 람다 표현식이 중첩되어 있어서 Spotless를 사용하여 코드 포맷팅을 하면 가독성이 매우 떨어진다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723827124438&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Override
    public List&amp;lt;PostDocument&amp;gt; findAutoCompleteSuggestion(String keyword, int size) {
        try {
            SearchResponse&amp;lt;PostDocument&amp;gt; response =
                    elasticsearchClient.search(
                            s -&amp;gt;
                                    s.index(POST_INDEX)
                                            .size(size)
                                            .query(
                                                    q -&amp;gt;
                                                            q.bool(
                                                                    b -&amp;gt;
                                                                            b.should(
                                                                                            sh -&amp;gt;
                                                                                                    sh
                                                                                                            .matchPhrase(
                                                                                                                    mp -&amp;gt;
                                                                                                                            mp.field(
                                                                                                                                            &quot;title&quot;)
                                                                                                                                    .query(
                                                                                                                                            keyword)
                                                                                                                                    .slop(
                                                                                                                                            1)
                                                                                                                                    .boost(
                                                                                                                                            3.0f)))
                                                                                    .should(
                                                                                            sh -&amp;gt;
                                                                                                    sh
                                                                                                            .match(
                                                                                                                    m -&amp;gt;
                                                                                                                            m.field(
                                                                                                                                            &quot;title.nori&quot;)
                                                                                                                                    .query(
                                                                                                                                            keyword)
                                                                                                                                    .operator(
                                                                                                                                            Operator
                                                                                                                                                    .And)
                                                                                                                                    .boost(
                                                                                                                                            2.0f)
                                                                                                                                    .fuzziness(
                                                                                                                                            &quot;1&quot;)))
                                                                                    .should(
                                                                                            sh -&amp;gt;
                                                                                                    sh
                                                                                                            .match(
                                                                                                                    m -&amp;gt;
                                                                                                                            m.field(
                                                                                                                                            &quot;title.ngram&quot;)
                                                                                                                                    .query(
                                                                                                                                            keyword)))
                                                                                    .minimumShouldMatch(
                                                                                            &quot;1&quot;))),

            return response.hits().hits().stream().map(Hit::source).toList();
        } catch (IOException e) {
            throw new ElasticsearchOperationException(ErrorCode.ES_OPERATION_FAILED);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; 문제점2 해결&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1723831894073&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class PostCustomElasticRepositoryImpl implements PostCustomElasticRepository {
    private final ElasticsearchClient elasticsearchClient;
    private final String POST_INDEX = &quot;posts&quot;;

    @Override
    public List&amp;lt;PostDocument&amp;gt; findAutoCompleteSuggestion(String keyword, int size) {
        try {
            SearchResponse&amp;lt;PostDocument&amp;gt; response =
                    elasticsearchClient.search(
                            s -&amp;gt; autoCompleteSuggestionQuery(s, keyword, size), PostDocument.class);

            return response.hits().hits().stream().map(Hit::source).toList();
        } catch (IOException e) {
            throw new ElasticsearchOperationException(ErrorCode.ES_OPERATION_FAILED);
        }
    }

    private ObjectBuilder&amp;lt;SearchRequest&amp;gt; autoCompleteSuggestionQuery(
            SearchRequest.Builder builder, String keyword, int size) {
        Query matchPhraseQuery = MatchPhraseQuery.of(mp -&amp;gt; mp.field(&quot;title&quot;).query(keyword).slop(1).boost(3.0f))._toQuery();
        Query matchNoriQuery = MatchQuery.of(m -&amp;gt; m.field(&quot;title.nori&quot;).query(keyword).operator(Operator.And).boost(2.0f).fuzziness(&quot;1&quot;))._toQuery();
        Query matchNgramQuery = MatchQuery.of(m -&amp;gt; m.field(&quot;title.ngram&quot;).query(keyword))._toQuery();
        Query boolQuery = BoolQuery.of(b -&amp;gt; b.should(matchPhraseQuery).should(matchNoriQuery).should(matchNgramQuery).minimumShouldMatch(&quot;1&quot;))._toQuery();

        return builder.index(POST_INDEX).size(size).query(boolQuery);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;쿼리 작성 로직을 메서드로 분리하고, MatchPhraseQuery, MatchQuery, BoolQuery를 따로 작성했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이를 결합하여 최종 쿼리를 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Spotless를 적용하면 다음과 같이 포맷팅된다. 이전과 비교했을 때 가독성이 더 좋아졌다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723832703769&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private ObjectBuilder&amp;lt;SearchRequest&amp;gt; autoCompleteSuggestionQuery(
            SearchRequest.Builder builder, String keyword, int size) {
        Query matchPhraseQuery =
                MatchPhraseQuery.of(mp -&amp;gt; mp.field(&quot;title&quot;).query(keyword).slop(1).boost(3.0f))
                        ._toQuery();
        Query matchNoriQuery =
                MatchQuery.of(
                                m -&amp;gt;
                                        m.field(&quot;title.nori&quot;)
                                                .query(keyword)
                                                .operator(Operator.And)
                                                .boost(2.0f)
                                                .fuzziness(&quot;1&quot;))
                        ._toQuery();
        Query matchNgramQuery =
                MatchQuery.of(m -&amp;gt; m.field(&quot;title.ngram&quot;).query(keyword))._toQuery();
        Query boolQuery =
                BoolQuery.of(
                                b -&amp;gt;
                                        b.should(matchPhraseQuery)
                                                .should(matchNoriQuery)
                                                .should(matchNgramQuery)
                                                .minimumShouldMatch(&quot;1&quot;))
                        ._toQuery();

        return builder.index(POST_INDEX).size(size).query(boolQuery);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL</category>
      <category>elasticsearch</category>
      <category>til</category>
      <author>Chef.Yeon</author>
      <guid isPermaLink="true">https://yeon-dev.tistory.com/255</guid>
      <comments>https://yeon-dev.tistory.com/255#entry255comment</comments>
      <pubDate>Tue, 13 Aug 2024 01:56:18 +0900</pubDate>
    </item>
    <item>
      <title>[TIL - 20240810~12] ElasticSearch 자동 완성 (1)</title>
      <link>https://yeon-dev.tistory.com/254</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IA5Hk/btsI1VO3G6o/g9UeoYrSyJL9M1OqLTkPQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IA5Hk/btsI1VO3G6o/g9UeoYrSyJL9M1OqLTkPQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IA5Hk/btsI1VO3G6o/g9UeoYrSyJL9M1OqLTkPQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIA5Hk%2FbtsI1VO3G6o%2Fg9UeoYrSyJL9M1OqLTkPQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;350&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yeon-dev.tistory.com/253&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yeon-dev.tistory.com/253&lt;/a&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723474923155&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring] Spring Boot3.x Docker Compose로 ElasticSearch 8.x+Kibana 구성 (Local)&quot; data-og-description=&quot;프로젝트 루트 디렉토리에 docker-compose.yml을 생성한다.&amp;nbsp;1. Docker Compose 파일 작성docker-compose.ymlversion: '3.7'services: es: image: docker.elastic.co/elasticsearch/elasticsearch:8.7.1 container_name: es environment: - node.name=es-&quot; data-og-host=&quot;yeon-dev.tistory.com&quot; data-og-source-url=&quot;https://yeon-dev.tistory.com/253&quot; data-og-url=&quot;https://yeon-dev.tistory.com/253&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/9KBi7/hyWKAxyTnh/q2SyrWjZtFU5epD4tKf3rk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/trz7x/hyWOhpyFUh/7DoEoalzKkwW4kd0DKhUZk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/lJy5G/hyWKK1e4o1/xTkpISkjKAKiIKF0F3qz60/img.png?width=887&amp;amp;height=482&amp;amp;face=0_0_887_482&quot;&gt;&lt;a href=&quot;https://yeon-dev.tistory.com/253&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yeon-dev.tistory.com/253&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/9KBi7/hyWKAxyTnh/q2SyrWjZtFU5epD4tKf3rk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/trz7x/hyWOhpyFUh/7DoEoalzKkwW4kd0DKhUZk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/lJy5G/hyWKK1e4o1/xTkpISkjKAKiIKF0F3qz60/img.png?width=887&amp;amp;height=482&amp;amp;face=0_0_887_482');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring] Spring Boot3.x Docker Compose로 ElasticSearch 8.x+Kibana 구성 (Local)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 루트 디렉토리에 docker-compose.yml을 생성한다.&amp;nbsp;1. Docker Compose 파일 작성docker-compose.ymlversion: '3.7'services: es: image: docker.elastic.co/elasticsearch/elasticsearch:8.7.1 container_name: es environment: - node.name=es-&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yeon-dev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;커뮤니티 검색 시, 자동 완성 기능을 ElasticSearch로 구현하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;'통밀빵'이라는 검색어 입력 시, 다음과 같이 시작/중간/끝에 '통밀빵'이 포함된 자동 완성 목록이 떠야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO0WO2/btsIZdolUxM/Jp6es4KG5tCa2KMkPlYYBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO0WO2/btsIZdolUxM/Jp6es4KG5tCa2KMkPlYYBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO0WO2/btsIZdolUxM/Jp6es4KG5tCa2KMkPlYYBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO0WO2%2FbtsIZdolUxM%2FJp6es4KG5tCa2KMkPlYYBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;308&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;우선, ElasticSearch에 '통밀빵'이 접두사/중간/접미사에 사용되는 데이터를 2개씩 넣어주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;(&lt;b&gt;&lt;u&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://esbook.kimjmin.net/04-data/4.3-_bulk&quot;&gt;bulk 연산&lt;/a&gt;&lt;/u&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는 PostMan 요청)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;kibana dev tools에서 다음 명령어를 입력해 posts 인덱스의 모든 문서를 검색할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723473795680&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;GET /posts/_search?q=*&amp;amp;pretty&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djskBO/btsIY1hi5hc/8wn24GEkzMUtYphfPinUB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djskBO/btsIY1hi5hc/8wn24GEkzMUtYphfPinUB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djskBO/btsIY1hi5hc/8wn24GEkzMUtYphfPinUB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjskBO%2FbtsIY1hi5hc%2F8wn24GEkzMUtYphfPinUB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;624&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;1. N-gram 분석기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자동완성 기능은 사용자가 입력하는 대로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;실시간&lt;/b&gt;으로 반응해야 한다. 때문에 단어의 일부분만을 가지고 결과를 제공하는 것이 중요하다고 판단하여,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://esbook.kimjmin.net/06-text-analysis/6.6-token-filter/6.6.4-ngram-edge-ngram-shingle&quot;&gt;&lt;b&gt;N-gram 분석기&lt;/b&gt;&lt;/a&gt;&lt;/u&gt;를 사용했다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1-1. Nori 분석기 예시&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Nori 분석기는 한글을 형태소 단위로 분석한다. 다음은 Nori 분석기로 &quot;동해물과 백두산이 마르고 닳도록&quot;을 분석한 결과다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723474271653&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET _analyze
{
  &quot;tokenizer&quot;: {
    &quot;type&quot;: &quot;nori_tokenizer&quot;,
    &quot;decompound_mode&quot;: &quot;mixed&quot;
  },
  &quot;filter&quot;: [&quot;lowercase&quot;, &quot;stop&quot;, &quot;trim&quot;, &quot;nori_part_of_speech&quot;],
  &quot;text&quot;: [&quot;동해물과 백두산이 마르고 닳도록&quot;]
}

Nori 분석 결과: [&quot;동해&quot;, &quot;물&quot;, &quot;백두산&quot;, &quot;백두&quot;, &quot;산&quot;, &quot;마르&quot;, &quot;닳&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;decompound_mode&lt;/b&gt;는 복합어를 분리하여 원본과 함께 저장하는 방식이다. mixed로 설정하여 &quot;백두산&quot;이라는 복합어를 &quot;백두&quot;+&quot;산&quot;으로 분해하면서도 &quot;백두산&quot;을 함께 인덱싱한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사용자가 '두산'이라는 단어를 입력했을 때 Nori 분석기는 다음과 같은 결과를 보여준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723474304766&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /posts/_search
{
  &quot;query&quot;: {
    &quot;match&quot;: {
      &quot;title&quot;: &quot;두산&quot;
    }
  }
}

결과:
{
  &quot;took&quot;: 1,
  &quot;timed_out&quot;: false,
  &quot;_shards&quot;: {
    &quot;total&quot;: 1,
    &quot;successful&quot;: 1,
    &quot;skipped&quot;: 0,
    &quot;failed&quot;: 0
  },
  &quot;hits&quot;: {
    &quot;total&quot;: {
      &quot;value&quot;: 0,
      &quot;relation&quot;: &quot;eq&quot;
    },
    &quot;max_score&quot;: null,
    &quot;hits&quot;: []
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;'백두산'이라는 단어에서 '두산' 이라는 일부분만 사용해서 검색했을 때, 원하는 결과를 얻을 수 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Nori 분석기는 '백두산'을 '백두'+'산'으로 분해하지만, '두산'을 별도로 인식하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1-2. N-gram 분석기 예시&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;반면에, N-gram 분석기는 텍스트를 일정한 길이의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;부분 문자열&lt;/b&gt;로 나누어 인덱싱한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723474326583&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET _analyze
{
  &quot;tokenizer&quot;: {
    &quot;type&quot;: &quot;ngram&quot;,
    &quot;min_gram&quot;: 2,
    &quot;max_gram&quot;: 2,
    &quot;token_chars&quot;: [&quot;letter&quot;, &quot;digit&quot;]
  },
  &quot;text&quot;: [&quot;동해물과 백두산이 마르고 닳도록&quot;]
}

2-gram 분석 결과: [&quot;동해&quot;, &quot;해물&quot;, &quot;물과&quot;, &quot;백두&quot;, &quot;두산&quot;, &quot;산이&quot;, &quot;마르&quot;, &quot;르고&quot;, &quot;닳도&quot;, &quot;도록&quot;]

3-gram 분석 결과: [&quot;동해물&quot;, &quot;해물과&quot;, &quot;백두산&quot;, &quot;두산이&quot;, &quot;마르고&quot;, &quot;닳도록&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;N-gram 분석기를 사용하면, '백두산이'라는 문자열이 '백두', '두산', '산이'와 같은 부분 문자열로 나누어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;때문에 사용자가 '두산'이라는 단어를 검색하면, '백두산'이 포함된 문장을 결과로 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723474393200&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /posts/_search
{
  &quot;query&quot;: {
    &quot;match&quot;: {
      &quot;title.ngram&quot;: &quot;두산&quot;
    }
  }
}

결과:
{
  &quot;took&quot;: 0,
  &quot;timed_out&quot;: false,
  &quot;_shards&quot;: {
    &quot;total&quot;: 1,
    &quot;successful&quot;: 1,
    &quot;skipped&quot;: 0,
    &quot;failed&quot;: 0
  },
  &quot;hits&quot;: {
    &quot;total&quot;: {
      &quot;value&quot;: 1,
      &quot;relation&quot;: &quot;eq&quot;
    },
    &quot;max_score&quot;: 1.5741749,
    &quot;hits&quot;: [
      {
        &quot;_index&quot;: &quot;posts&quot;,
        &quot;_id&quot;: &quot;8&quot;,
        &quot;_score&quot;: 1.5741749,
        &quot;_source&quot;: {
          &quot;id&quot;: 8,
          &quot;title&quot;: &quot;동해물과 백두산이 마르고 닳도록&quot;,
          &quot;content&quot;: &quot;내용8&quot;,
          &quot;createdAt&quot;: &quot;2024-08-12T21:04:31.777&quot;,
          &quot;tags&quot;: [
            &quot;#애국가&quot;
          ]
        }
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #f6e199;&quot;&gt;&lt;b&gt;2. 인덱스 설정 및 매핑 구성&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-1. post-setting.json&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1723473827915&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;analysis&quot;: {
    &quot;tokenizer&quot;: {
      &quot;my-nori-tokenizer&quot;: {
        &quot;type&quot;: &quot;nori_tokenizer&quot;,
        &quot;decompound_mode&quot;: &quot;mixed&quot;
      },
      &quot;my-ngram-tokenizer&quot;: {
        &quot;type&quot;: &quot;ngram&quot;,
        &quot;min_gram&quot;: 2,
        &quot;max_gram&quot;: 3,
        &quot;token_chars&quot;: [&quot;letter&quot;, &quot;digit&quot;]
      }
    },
    &quot;analyzer&quot;: {
      &quot;my-nori-analyzer&quot;: {
        &quot;type&quot;: &quot;custom&quot;,
        &quot;tokenizer&quot;: &quot;my-nori-tokenizer&quot;,
        &quot;filter&quot;: [
          &quot;lowercase&quot;,
          &quot;stop&quot;,
          &quot;trim&quot;,
          &quot;nori_part_of_speech&quot;
        ]
      },
      &quot;my-ngram-analyzer&quot;: {
        &quot;type&quot;: &quot;custom&quot;,
        &quot;tokenizer&quot;: &quot;my-ngram-tokenizer&quot;,
        &quot;filter&quot;: [
          &quot;lowercase&quot;,
          &quot;trim&quot;
        ]
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-2. post-mapping.json&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1723473840463&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;properties&quot;: {
    &quot;id&quot;: {
      &quot;type&quot;: &quot;long&quot;
    },
    &quot;title&quot;: {
      &quot;type&quot;: &quot;text&quot;,
      &quot;fields&quot;: {
        &quot;ngram&quot;: {
          &quot;type&quot;: &quot;text&quot;,
          &quot;analyzer&quot;: &quot;my-ngram-analyzer&quot;
        },
        &quot;nori&quot;: {
          &quot;type&quot;: &quot;text&quot;,
          &quot;analyzer&quot;: &quot;my-nori-analyzer&quot;
        }
      }
    },
    &quot;content&quot;: {
      &quot;type&quot;: &quot;text&quot;,
      &quot;analyzer&quot;: &quot;my-nori-analyzer&quot;
    },
    &quot;createdAt&quot;: {
      &quot;type&quot;: &quot;date&quot;,
      &quot;format&quot;: &quot;yyyy-MM-dd'T'HH:mm:ss.SSS||epoch_millis&quot;
    },
    &quot;imageUrl&quot;: {
      &quot;type&quot;: &quot;text&quot;
    },
    &quot;tags&quot;: {
      &quot;type&quot;: &quot;keyword&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 설정에서는 title 필드에 n-gram과 nori 두 가지 분석기를 적용하여, 다양한 검색 시나리오에 대응할 수 있도록&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;u&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://esbook.kimjmin.net/07-settings-and-mappings/7.3-multi-field&quot;&gt;멀티 필드&lt;/a&gt;&lt;/u&gt;&lt;/b&gt;를 정의했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. 서비스 및 레포지토리 구현&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3-1. 레포지토리 구현&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;PostCustomElasticRepository&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723473898770&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface PostCustomElasticRepository {
    List&amp;lt;PostDocument&amp;gt; findByKeywordInField(String fieldName, String keyword);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;PostCustomElasticRepositoryImpl&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723473912105&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class PostCustomElasticRepositoryImpl implements PostCustomElasticRepository {
    private final ElasticsearchOperations elasticsearchOperations;

    @Override
    public List&amp;lt;PostDocument&amp;gt; findByKeywordInField(String fieldName, String keyword) {

        Criteria criteria = new Criteria(fieldName).matches(keyword);
        CriteriaQuery query = new CriteriaQuery(criteria);

        return elasticsearchOperations
                .search(query, PostDocument.class)
                .map(SearchHit::getContent)
                .stream()
                .toList();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;PostElasticRepository&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723473933204&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public interface PostElasticRepository extends ElasticsearchRepository&amp;lt;PostDocument, Long&amp;gt;, PostCustomElasticRepository {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3-2. 서비스 구현&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1723473795703&quot; class=&quot;groovy&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostQueryService {
    private final PostElasticRepository postElasticRepository;
    
    public List&amp;lt;String&amp;gt; searchByKeyword(String keyword) {
        return postElasticRepository.findByKeywordInField(&quot;title.ngram&quot;, keyword)
                .stream().map(PostDocument::getTitle)
                .toList();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;4. API 요청&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4-1. 통밀빵 검색 예시&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1723473865575&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:8080/api/v1/posts/elastic?keyword=통밀빵&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bliOMi/btsI1xGDltM/1hVne8WAZ5cSFiNcWgS9i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bliOMi/btsI1xGDltM/1hVne8WAZ5cSFiNcWgS9i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bliOMi/btsI1xGDltM/1hVne8WAZ5cSFiNcWgS9i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbliOMi%2FbtsI1xGDltM%2F1hVne8WAZ5cSFiNcWgS9i1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;548&quot; height=&quot;222&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4-2. 오타가 포함된 검색 예시&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1723473872258&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:8080/api/v1/posts/elastic?keyword=텅밀빵&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UEwnD/btsI1oQE4lD/f08WoU2KvbCQmNWrzyA6ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UEwnD/btsI1oQE4lD/f08WoU2KvbCQmNWrzyA6ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UEwnD/btsI1oQE4lD/f08WoU2KvbCQmNWrzyA6ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUEwnD%2FbtsI1oQE4lD%2Ff08WoU2KvbCQmNWrzyA6ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;545&quot; height=&quot;232&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오타가 포함되어도 부분 문자열이 일치하기 때문에 원하는 결과를 확인할 수 있다.&lt;/p&gt;</description>
      <category>TIL</category>
      <category>elasticsearch</category>
      <author>Chef.Yeon</author>
      <guid isPermaLink="true">https://yeon-dev.tistory.com/254</guid>
      <comments>https://yeon-dev.tistory.com/254#entry254comment</comments>
      <pubDate>Mon, 12 Aug 2024 23:54:46 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring Boot3.x Docker Compose로 ElasticSearch 8.x+Kibana 구성 (Local)</title>
      <link>https://yeon-dev.tistory.com/253</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로젝트 루트 디렉토리에 docker-compose.yml을 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;1. Docker Compose 파일 작성&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;docker-compose.yml&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723110501829&quot; class=&quot;shell&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;version: '3.7'

services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.7.1
    container_name: es
    environment:
      - node.name=es-node
      - cluster.name=search-cluster
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
      - xpack.security.enabled=false
      - xpack.security.http.ssl.enabled=false
      - xpack.security.transport.ssl.enabled=false
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es-data:/usr/share/elasticsearch/data
    ports:
      - 9200:9200 # https
      - 9300:9300 #tcp
    networks:
      - es-bridge

  kibana:
    image: docker.elastic.co/kibana/kibana:8.7.1
    container_name: kibana
    environment:
      SERVER_NAME: kibana
      ELASTICSEARCH_HOSTS: http://es:9200
    ports:
      - 5601:5601
    depends_on:
      - es
    networks:
      - es-bridge

volumes:
  es-data:
    driver: local

networks:
  es-bridge:
    driver: bridge&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;version&lt;/b&gt;: Docker Compose 파일 버전 지정&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;services&lt;/b&gt;: Docker Compose에서 정의하는 개별 서비스&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;volumes&lt;/b&gt;: 컨테이너와 호스트 간 데이터 저장/공유를 위한 볼륨 정의&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;networks&lt;/b&gt;: 서비스들이 통신할 네트워크 정의&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;- ElasticSearch 서비스 (es)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;discovery.type=single-node&lt;/b&gt;: Elasticsearch를 단일 노드 모드로 실행&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;bootstrap.memory_lock=true&lt;/b&gt;: &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;메모리 스왑 방지&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;ES_JAVA_OPTS=Xms1g -Xmx1g&lt;/b&gt;: JVM 최소 힙 메모리 크기와 최대 힙 메모리 크기를 1GB로 설정&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;로컬 개발 환경에서 실행하기 위해 다음과 같이 Xpack 보안 기능과 SSL 설정을 비활성화 했다.&lt;/span&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;xpack.security.enabled=false&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;xpack.security.http.ssl.enabled=false&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;xpack.security.transport.ssl.enabled=false&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;ulimits:&lt;/b&gt;&lt;b&gt;memlock&lt;/b&gt;: 메모리 락 한도 설정 (-1은 무제한)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;- Kinaba 서비스&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;ELASTICSEARCH_HOSTS&lt;/b&gt;: 연결한 Elasticsearch 호스트 지정 &lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ElasticSearch 서비스를 es라고 정의해두었으므로 http://es:9200로 연결&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약 서비스 이름이 elasticsearch라면 http://elasticsearch:9200로 연결&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;depends on&lt;/b&gt;: Kibana 서비스가 es 서비스에 종속됨&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;es 서비스 시작된 후에 Kibana 시작&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;- 네트워크 정의&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;networks:es-bridge&lt;/b&gt;: 두 서비스 간의 통신을 위한 브리지 네트워크&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;터미널에서 docker-compose up 명령을 통해 실행하고, 정상적으로 실행되는지 확인해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;http://localhost:9200&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/876bW/btsI0HDakRy/2tGEFRyJ14rjLRvgSfy7X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/876bW/btsI0HDakRy/2tGEFRyJ14rjLRvgSfy7X1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/876bW/btsI0HDakRy/2tGEFRyJ14rjLRvgSfy7X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F876bW%2FbtsI0HDakRy%2F2tGEFRyJ14rjLRvgSfy7X1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;260&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;http://localhost:5601&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;983&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQOFn6/btsI0Iu4kae/7PKfLLZ9BWcCrz4DCia790/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQOFn6/btsI0Iu4kae/7PKfLLZ9BWcCrz4DCia790/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQOFn6/btsI0Iu4kae/7PKfLLZ9BWcCrz4DCia790/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQOFn6%2FbtsI0Iu4kae%2F7PKfLLZ9BWcCrz4DCia790%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;362&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;983&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #f6e199;&quot;&gt;&lt;b&gt;2. Nori 설치&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Nori는 한글 형태소 분석기로 카카오에서 개발하여 Elasticsearch에서 공식 지원한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;cmd를 열어 다음 명령어를 입력해주어 설치하자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723110501834&quot; class=&quot;shell&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;# es container 확인
$ docker ps 

# es컨테이너 접속
$ docker exec -it {containerId} /bin/bash

$ cd /usr/share/elasticsearch/bin/

$ ./elasticsearch-plugin install analysis-nori&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KLtYY/btsIZvX6t9S/8p0dzoggPJJipHyhysxT41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KLtYY/btsIZvX6t9S/8p0dzoggPJJipHyhysxT41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KLtYY/btsIZvX6t9S/8p0dzoggPJJipHyhysxT41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKLtYY%2FbtsIZvX6t9S%2F8p0dzoggPJJipHyhysxT41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;175&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;docker compose down 후, 재실행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;3. ElasticSearch 설정 및 서비스 구현&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;build.gradle&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723110501835&quot; class=&quot;clean&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;application.yml&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723308526315&quot; class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  elasticsearch:
    host: localhost:9200&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;ElasticSearchConfig&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723308526316&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableElasticsearchRepositories
public class ElasticSearchConfig extends ElasticsearchConfiguration {
    @Value(&quot;${spring.elasticsearch.host}&quot;)
    private String host;

    @Override
    public ClientConfiguration clientConfiguration() {
        return ClientConfiguration.builder()
                .connectedTo(host)
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;@EnalbeElasticsearchRepositories&lt;/b&gt;: elastic 레포지토리 지원 활성화&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Post&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723308464325&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post extends TimeStamped {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;post_id&quot;)
    private Long id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    @Builder
    public Post(String username, String title, String content) {
        this.username = username;
        this.title = title;
        this.content = content;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;PostDocument&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723110501835&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Document(indexName = &quot;posts&quot;, writeTypeHint = WriteTypeHint.FALSE)
@Setting(settingPath = &quot;elastic/post-setting.json&quot;)
@Mapping(mappingPath = &quot;elastic/post-mapping.json&quot;)
public class PostDocument {
    @Id
    private Long id;
    private String username;
    private String title;
    private String content;

    @Builder
    public PostDocument(Long id, String username, String title, String content) {
        this.id = id;
        this.username = username;
        this.title = title;
        this.content = content;
    }

    public static PostDocument from(Post post) {
        return PostDocument.builder()
                .id(post.getId())
                .username(post.getUsername())
                .title(post.getTitle())
                .content(post.getContent())
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;@Document&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Elasticsearch 인덱스를 지정하고, 해당 클래스가 Elasticsearch 도큐먼트로 매핑되도록 정의&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;indexName&lt;/b&gt;: Elasticsearch 인덱스 이름 지정 (필수)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;writeTypeHint&lt;/b&gt;: 도큐먼트 타입 힌트작성 여부 (기본값: true)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;기본적으로 Spring Data Elasticsearch는 도큐먼트에 '_class' 필드 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;createIndex&lt;/b&gt;: 인덱스 자동 생성 여부 (기본값: true)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;true로 설정된 경우, Spring Data Elasticsearch가 애플리케이션 시작시 @Document에 의해 생성된 인덱스가 있는지 확인하고 없으면 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;shards, replicas, refreshInterval, versionType&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;@Mapping&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Elasticsearch 인덱스의 매핑을 정의하는 JSON 파일의 경로 지정&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;(&lt;a href=&quot;https://docs.spring.io/spring-data/elasticsearch/reference/elasticsearch/misc.html#elasticsearch.misc.runtime-fields.index-mappings&quot;&gt;런타임 필드로 인덱스 매핑&lt;/a&gt;을 정의하면, 클래스 경로에 해당 JSON 파일의 경로를 설정해야한다.)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;post-mapping.json&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 파일은&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;resources/elastic 폴더 하위에 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;@Setting&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Elasticsearch 인덱스의 설정을 정의하는 JSON 파일 경로 지정&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;post-setting.json&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;파일은&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;resources/elastic 폴더 하위에 있다.&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;post-setting.json&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;인덱스의 분석기, 토크나이저와 같은 설정을 포함할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723110501841&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;{
  &quot;analysis&quot;: {
    &quot;analyzer&quot;: {
      &quot;korean&quot;: {
        &quot;type&quot;: &quot;nori&quot;
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;post-mapping.json&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;필드의 데이터 타입(text, keyword 등), 분석기 설정, 필드 속성 등이 정의된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723311899352&quot; class=&quot;json&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;properties&quot;: {
    &quot;id&quot;: {
      &quot;type&quot;: &quot;long&quot;
    },
    &quot;username&quot;: {
      &quot;type&quot;: &quot;text&quot;,
      &quot;analyzer&quot;: &quot;korean&quot;
    },
    &quot;title&quot;: {
      &quot;type&quot;: &quot;text&quot;,
      &quot;analyzer&quot;: &quot;korean&quot;
    },
    &quot;content&quot;: {
      &quot;type&quot;: &quot;text&quot;,
      &quot;analyzer&quot;: &quot;korean&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;PostSearchController&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723312324786&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;api/v1/posts&quot;)
@RequiredArgsConstructor
public class PostSearchController {
    private final PostSearchService postSearchService;

    @GetMapping(&quot;/search&quot;)
    ResponseEntity&amp;lt;List&amp;lt;PostDocument&amp;gt;&amp;gt; search(@RequestParam String keyword) {
        return ResponseEntity.ok(postSearchService.searchByKeyword(keyword));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;PostService&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723110501846&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class PostService {
    private final PostElasticRepository postElasticRepository;

    public Post add(PostRequestDto request) {
        Post post = Post.builder()
                .username(request.getUsername())
                .title(request.getTitle())
                .content(request.getContent())
                .build();
        postRepository.save(post);
        postElasticRepository.save(PostDocument.from(post));
        return post;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;수정 기능은 postElasticRepository.save를 통해 PostDocument를 다시 저장해주면 해당 id이 document가 수정된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;삭제 기능은 postElasticRepository.deleteById 또는 delete를 사용해 삭제할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;PostSearchService&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723110501847&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostSearchService {
    private final PostElasticRepository postElasticRepository;

    public List&amp;lt;PostDocument&amp;gt; searchByKeyword(String keyword) {
        return postElasticRepository.findByTitle(keyword);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;PostElasticRepository&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723308172703&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public interface PostElasticRepository extends ElasticsearchRepository&amp;lt;PostDocument, Long&amp;gt; {
    List&amp;lt;PostDocument&amp;gt; findByTitle(String title);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ElasticsearchRepository는 Spring Data Elasticsearch에서 지원한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;기본적인 CRUD가 지원되고, 메서드 이름을 기반으로 쿼리를 자동 생성할 수 있다. (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://docs.spring.io/spring-data/elasticsearch/reference/elasticsearch/repositories/elasticsearch-repository-queries.html&quot;&gt;Query mehtods&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;findByTitle은 파라미터가 '제목'일 때 다음과 같은 elasticsearch query가 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723308172705&quot; class=&quot;json&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;query&quot;: {
    &quot;match&quot;: {
      &quot;title&quot;: &quot;제목&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;4. API 테스트&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Postman과 Kibana를 사용하여 Elasticsearch에 저장된 데이터를 확인하고, 데이터를 검색해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;우선애플리케이션을 실행하고,&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;데이터 확인을 위해 Analytics(Kibana이다) &amp;gt; Discover로 이동해 DataView를 만들어준다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MUQIo/btsIXvV0tNo/ZDHFdXVtf79WUk92BDetT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MUQIo/btsIXvV0tNo/ZDHFdXVtf79WUk92BDetT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MUQIo/btsIXvV0tNo/ZDHFdXVtf79WUk92BDetT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMUQIo%2FbtsIXvV0tNo%2FZDHFdXVtf79WUk92BDetT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;268&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약 date 필드가 있으면 Timestamp field를 설정해주자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br3ZdM/btsI1Ela89Y/Mr0HPSDRRk3uy58KtagB41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br3ZdM/btsI1Ela89Y/Mr0HPSDRRk3uy58KtagB41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br3ZdM/btsI1Ela89Y/Mr0HPSDRRk3uy58KtagB41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr3ZdM%2FbtsI1Ela89Y%2FMr0HPSDRRk3uy58KtagB41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;102&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Postman을 사용해 요청을 보내보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z69t3/btsI0S5xpOY/HKKQtOvYAafNS8J2RknoNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z69t3/btsI0S5xpOY/HKKQtOvYAafNS8J2RknoNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z69t3/btsI0S5xpOY/HKKQtOvYAafNS8J2RknoNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ69t3%2FbtsI0S5xpOY%2FHKKQtOvYAafNS8J2RknoNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;192&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DataView에서 Refresh 버튼을 클릭하면, 저장된 데이터를 확인할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BDdGt/btsI1nYgXJ5/aEKvknyKNKfqZU5JlVahn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BDdGt/btsI1nYgXJ5/aEKvknyKNKfqZU5JlVahn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BDdGt/btsI1nYgXJ5/aEKvknyKNKfqZU5JlVahn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBDdGt%2FbtsI1nYgXJ5%2FaEKvknyKNKfqZU5JlVahn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;127&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;PostMan으로 요청을 보내서 Elasticsearch 검색 기능 동작을 확인해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2yQ3s/btsI1dnPsbM/oK3Vs3Aog8PDbHkC4jZaAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2yQ3s/btsI1dnPsbM/oK3Vs3Aog8PDbHkC4jZaAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2yQ3s/btsI1dnPsbM/oK3Vs3Aog8PDbHkC4jZaAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2yQ3s%2FbtsI1dnPsbM%2FoK3Vs3Aog8PDbHkC4jZaAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;353&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>elasticsearch</category>
      <category>kibana</category>
      <author>Chef.Yeon</author>
      <guid isPermaLink="true">https://yeon-dev.tistory.com/253</guid>
      <comments>https://yeon-dev.tistory.com/253#entry253comment</comments>
      <pubDate>Sun, 11 Aug 2024 02:53:53 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring Boot 프로젝트 이름 변경하기</title>
      <link>https://yeon-dev.tistory.com/252</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Spring Boot 프로젝트 이름을 변경해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. File &amp;gt; Project Structure &amp;gt; Name 변경&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bACjxu/btsIWX0Rs6v/npqlBLuSLyIRrRG4cVK3H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bACjxu/btsIWX0Rs6v/npqlBLuSLyIRrRG4cVK3H1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bACjxu/btsIWX0Rs6v/npqlBLuSLyIRrRG4cVK3H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbACjxu%2FbtsIWX0Rs6v%2FnpqlBLuSLyIRrRG4cVK3H1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;297&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Name 부분을 변경할 프로젝트 이름으로 수정해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. settings.gradle&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;755&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8YKqs/btsIYEZOTI9/rpkuKNANKYiHrK85XEigJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8YKqs/btsIYEZOTI9/rpkuKNANKYiHrK85XEigJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8YKqs/btsIYEZOTI9/rpkuKNANKYiHrK85XEigJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8YKqs%2FbtsIYEZOTI9%2FrpkuKNANKYiHrK85XEigJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;309&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;755&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;rootProject.name을 수정해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. 변경 사항 적용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SJadI/btsIWUiPFTC/SnaW03I6J5CMyABrIND1tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SJadI/btsIWUiPFTC/SnaW03I6J5CMyABrIND1tK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SJadI/btsIWUiPFTC/SnaW03I6J5CMyABrIND1tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSJadI%2FbtsIWUiPFTC%2FSnaW03I6J5CMyABrIND1tK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;173&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;변경 사항을 적용하기 위해 코끼리를 클릭한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약 프로젝트가 있는 폴더도 이동한 경우 애플리케이션 실행 시 다음과 같은 문제가 생길 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; Two modules in a project cannot share the same content root. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로젝트 폴더 안에 있는 .idea 폴더와 *.iml 파일을 삭제하고 IntelliJ를 다시 켜주면 된다.(자동 생성)&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>SpringBoot</category>
      <author>Chef.Yeon</author>
      <guid isPermaLink="true">https://yeon-dev.tistory.com/252</guid>
      <comments>https://yeon-dev.tistory.com/252#entry252comment</comments>
      <pubDate>Thu, 8 Aug 2024 03:22:05 +0900</pubDate>
    </item>
    <item>
      <title>[TIL-20240805] ElasticSearch + Spring Boot 연동 오류 해결</title>
      <link>https://yeon-dev.tistory.com/251</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXXAa/btsIVFRPFKu/tVAR3kTyZRXxGcfCcspfJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXXAa/btsIVFRPFKu/tVAR3kTyZRXxGcfCcspfJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXXAa/btsIVFRPFKu/tVAR3kTyZRXxGcfCcspfJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXXAa%2FbtsIVFRPFKu%2FtVAR3kTyZRXxGcfCcspfJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;350&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 문제점1: CreatedAt 필드 타입&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;post-mappings.json&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722806205503&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;properties&quot;: {
    &quot;id&quot;: {
      &quot;type&quot;: &quot;long&quot;
    },
    &quot;title&quot;: {
      &quot;type&quot;: &quot;text&quot;,
      &quot;analyzer&quot;: &quot;korean&quot;
    },
    &quot;content&quot;: {
      &quot;type&quot;: &quot;text&quot;,
      &quot;analyzer&quot;: &quot;korean&quot;
    },
    &quot;createdAt&quot;: {
      &quot;type&quot;: &quot;date&quot;,
      &quot;format&quot;: &quot;yyyy-MM-dd'T'HH:mm:ss.SSSSSS||epoch_millis&quot;
    },
    &quot;imageUrl&quot;: {
      &quot;type&quot;: &quot;text&quot;
    },
    &quot;tags&quot;: {
      &quot;type&quot;: &quot;keyword&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;PostDocument&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722806330461&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Document(indexName = &quot;posts&quot;)
@Setting(settingPath = &quot;elastic/post-setting.json&quot;)
@Mapping(mappingPath = &quot;elastic/post-mapping.json&quot;)
public class PostDocument {
    @Id
    private Long id;
    private String title;
    private String content;
    private LocalDateTime createdAt;
    private String imageUrl;
    private List&amp;lt;String&amp;gt; tags;

    @Builder
    public PostDocument(
            Long id,
            String title,
            String content,
            LocalDateTime createdAt,
            String imageUrl,
            List&amp;lt;String&amp;gt; tags) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.createdAt = createdAt;
        this.imageUrl = imageUrl;
        this.tags = tags;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;PostElasticRepository&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722808151057&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Repository
public interface PostElasticRepository extends ElasticsearchRepository&amp;lt;PostDocument, Long&amp;gt; {
    List&amp;lt;PostDocument&amp;gt; findByTitleContaining(String title);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch를 사용한 데이터 검색이 정상 동작하는지 확인하기 위해 간단히 검색 api를 만들어 테스트했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'http://localhost:8080/api/v1/posts/elastic?keyword=오늘'로 요청을 보냈을 때, 다음과 같은 오류가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; No converter found capable of converting from type [java.lang.Long] to type [java.time.LocalDateTime]&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 문제점1-&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;해결&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kibana를 통해 생성된 posts 인덱스를 확인해보니, createdAt 필드 타입이 &lt;b&gt;date 타입이 아닌 long 타입&lt;/b&gt;으로 생성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kibana &amp;gt; dev tools 를 통해 인덱스를 직접 생성해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1722806676438&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PUT /posts
{
  &quot;settings&quot;: {
    &quot;analysis&quot;: {
      &quot;analyzer&quot;: {
        &quot;english&quot;: {
          &quot;type&quot;: &quot;whitespace&quot;
        },
        &quot;korean&quot;: {
          &quot;type&quot;: &quot;nori&quot;
        }
      }
    }
  },
  &quot;mappings&quot;: {
    &quot;properties&quot;: {
      &quot;id&quot;: {
        &quot;type&quot;: &quot;long&quot;
      },
      &quot;title&quot;: {
        &quot;type&quot;: &quot;text&quot;,
        &quot;analyzer&quot;: &quot;korean&quot;
      },
      &quot;content&quot;: {
        &quot;type&quot;: &quot;text&quot;,
        &quot;analyzer&quot;: &quot;korean&quot;
      },
      &quot;createdAt&quot;: {
        &quot;type&quot;: &quot;date&quot;,
        &quot;format&quot;: &quot;yyyy-MM-dd'T'HH:mm:ss.SSS||epoch_millis&quot;
      },
      &quot;imageUrl&quot;: {
        &quot;type&quot;: &quot;text&quot;
      },
      &quot;tags&quot;: {
        &quot;type&quot;: &quot;keyword&quot;
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 문제점2: _class 필드 역직렬화&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'http://localhost:8080/api/v1/posts/elastic?keyword=날씨' 로 요청을 보냈을 때, 다음과 같은 오류가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323; text-align: start;&quot;&gt;com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field &quot;_class&quot; (class com....post.domain.document.PostDocument), not marked as ignorable (6 known properties: &quot;imageUrl&quot;, &quot;title&quot;, &quot;content&quot;, &quot;id&quot;, &quot;tags&quot;, &quot;createdAt&quot;])&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 문제점2-&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;해결&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JSON 응답을 PostDocument로 역직렬화하는 과정에서 _class라는 인식되지 않은 필드가 존재하여 발생한 문제이다. Spring Data Elasticsearch는 기본적으로 엔터티 클래스의 _class 필드를 추가하여 인덱싱한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;PostDocument&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722807969371&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Document(indexName = &quot;posts&quot;, createIndex = false, writeTypeHint = WriteTypeHint.FALSE)
@Setting(settingPath = &quot;elastic/post-setting.json&quot;)
@Mapping(mappingPath = &quot;elastic/post-mapping.json&quot;)
public class PostDocument {
    @Id
    private Long id;
    private String title;
    private String content;
    private LocalDateTime createdAt;
    private String imageUrl;
    private List&amp;lt;String&amp;gt; tags;

    @Builder
    public PostDocument(
            Long id,
            String title,
            String content,
            LocalDateTime createdAt,
            String imageUrl,
            List&amp;lt;String&amp;gt; tags) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.createdAt = createdAt;
        this.imageUrl = imageUrl;
        this.tags = tags;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;@Document 속성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;indexName: ElasticSearch 인덱스 이름 지정&lt;/li&gt;
&lt;li&gt;createIndex:&amp;nbsp;애플리케이션 시작 시 인덱스를 자동으로 생성할지 여부 지정&lt;/li&gt;
&lt;li&gt;writeTypeHint: 도큐먼트에 타입 힌트를 쓸지 여부 지정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도큐먼트에 _class 필드를 추가하지 않도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 문제점3: LocalDateTime 필드 매핑&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;'http://localhost:8080/api/v1/posts/elastic?keyword=날씨' 로 요청을 보냈을 때, 다음과 같은 오류가 발생했다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; Type&amp;nbsp;LocalDateTime&amp;nbsp;of&amp;nbsp;property&amp;nbsp;PostDocument.createdAt&amp;nbsp;is&amp;nbsp;a&amp;nbsp;TemporalAccessor&amp;nbsp;class&amp;nbsp;but&amp;nbsp;has&amp;nbsp;neither&amp;nbsp;a&amp;nbsp;@Field&amp;nbsp;annotation&amp;nbsp;defining&amp;nbsp;the&amp;nbsp;date&amp;nbsp;type&amp;nbsp;nor&amp;nbsp;a&amp;nbsp;registered&amp;nbsp;converter&amp;nbsp;for&amp;nbsp;reading!&amp;nbsp;It&amp;nbsp;cannot&amp;nbsp;be&amp;nbsp;mapped&amp;nbsp;from&amp;nbsp;a&amp;nbsp;complex&amp;nbsp;object&amp;nbsp;in&amp;nbsp;Elasticsearch! &lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 문제점3-&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;해결&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; LocalDateTime 필드를 Elasticsearch와 올바르게 매핑하기 위해 PostDocument의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;createdAt 필드에 @Field 애노테이션을 추가했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722800021836&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Field(type = FieldType.Date, format = {}, pattern = &quot;yyyy-MM-dd'T'HH:mm:ss.SSS||epoch_millis&quot;)
    private LocalDateTime createdAt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 인덱스도 삭제하고 재생성해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1722808664244&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DELETE /posts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;post를 저장하고, 다음과 같이 kibana를 통해 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI9AVS/btsIUx1fVAo/DKVxPpdGmH1pkI0gxR9QN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI9AVS/btsIUx1fVAo/DKVxPpdGmH1pkI0gxR9QN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI9AVS/btsIUx1fVAo/DKVxPpdGmH1pkI0gxR9QN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI9AVS%2FbtsIUx1fVAo%2FDKVxPpdGmH1pkI0gxR9QN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;294&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left; color: #333333;&quot;&gt;'http://localhost:8080/api/v1/posts/elastic?keyword=날씨' 로 요청을 보냈을 때 정상적으로 응답이 왔다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crvGr6/btsIVMcjSq8/fiHo2lxBk5CSP6dsrOFE61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crvGr6/btsIVMcjSq8/fiHo2lxBk5CSP6dsrOFE61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crvGr6/btsIVMcjSq8/fiHo2lxBk5CSP6dsrOFE61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrvGr6%2FbtsIVMcjSq8%2FfiHo2lxBk5CSP6dsrOFE61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;332&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch와 Spring Boot를 연동하여 데이터를 저장하고 검색하는 과정에서 다양한 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 하나 보내기 정말 어렵다  &lt;/p&gt;</description>
      <category>TIL</category>
      <category>elasticsearch</category>
      <category>til</category>
      <author>Chef.Yeon</author>
      <guid isPermaLink="true">https://yeon-dev.tistory.com/251</guid>
      <comments>https://yeon-dev.tistory.com/251#entry251comment</comments>
      <pubDate>Mon, 5 Aug 2024 07:04:09 +0900</pubDate>
    </item>
    <item>
      <title>[DevOps] 윈도우 ElasticSearch 8.x 다운로드 및 키바나 연동</title>
      <link>https://yeon-dev.tistory.com/250</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ElasticSearch 다운로드 및 실행&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저, 다음 페이지에서 ElasticSearch와 Kibana를 다운 받아 압축을 풀어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/downloads/elasticsearch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ElasticSearch 다운로드&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/downloads/kibana&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kibana 다운로드&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;947&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xYwmd/btsIQ20ScSV/RBH7e6K69k5rQKbIj8T121/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xYwmd/btsIQ20ScSV/RBH7e6K69k5rQKbIj8T121/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xYwmd/btsIQ20ScSV/RBH7e6K69k5rQKbIj8T121/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxYwmd%2FbtsIQ20ScSV%2FRBH7e6K69k5rQKbIj8T121%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;352&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;947&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ElasticSearch 압축을 풀고 bin 폴더 하위의 elasticsearch 배치 파일을 직접 또는 cmd 창을 열어서 실행한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722100266379&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\Users\...\elasticsearch-8.14.3&amp;gt;bin\elasticsearch&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;첫 실행 시&lt;/b&gt; 초기 비밀번호와 키바나 연결을 위한 토큰 값을 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUbU1p/btsIQenk4Br/SmM1kzWBHWpWXU8e8gs491/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUbU1p/btsIQenk4Br/SmM1kzWBHWpWXU8e8gs491/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUbU1p/btsIQenk4Br/SmM1kzWBHWpWXU8e8gs491/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUbU1p%2FbtsIQenk4Br%2FSmM1kzWBHWpWXU8e8gs491%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;218&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;비밀번호 재설정&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;비밀번호 재설정은 필요한 경우에 수행하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1) 새로운 비밀번호 랜덤 발급&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;cmd에서 다음을 입력하면 유저 elastic에 대한 새로운 비밀번호를 다시 발급받을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722104786415&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\Users\...\elasticsearch-8.14.3&amp;gt;bin\elasticsearch-reset-password -u elastic&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Please confirm... [y/N] 에 y를 입력하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLPnDB/btsIPTRmrEG/SdohRzPVX3B9KIIqXt1ve1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLPnDB/btsIPTRmrEG/SdohRzPVX3B9KIIqXt1ve1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLPnDB/btsIPTRmrEG/SdohRzPVX3B9KIIqXt1ve1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLPnDB%2FbtsIPTRmrEG%2FSdohRzPVX3B9KIIqXt1ve1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;116&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left; font-family: 'Nanum Gothic';&quot;&gt;2) 원하는 비밀번호 재설정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; cmd에서 다음을 입력하면 유저 elastic에 대한 새로운 비밀번호를 직접 설정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;나는 이 방법을 사용해서 user와 password를 elastic으로 통일했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722105920051&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\Users\...\elasticsearch-8.14.3&amp;gt;bin\elasticsearch-reset-password -u elastic -i&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L3nj4/btsIPvclN3S/HFCTPkSdjI5p9L3Xq3RwkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L3nj4/btsIPvclN3S/HFCTPkSdjI5p9L3Xq3RwkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L3nj4/btsIPvclN3S/HFCTPkSdjI5p9L3Xq3RwkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL3nj4%2FbtsIPvclN3S%2FHFCTPkSdjI5p9L3Xq3RwkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;128&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;실행 확인&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;ElasticSearch는 9200 포트를 사용한다. 또한, 7.x 버전과 달리 8.x 버전은 보안 기능이 있어 https를 사용해서 접속해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left; font-family: 'Nanum Gothic';&quot;&gt;https://localhost:9200 으로 접속해서 정상 실행되는지 확인해보자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left; font-family: 'Nanum Gothic';&quot;&gt;고급 버튼을 눌러 (안전하지 않음)으로 이동을 클릭한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/siX3s/btsIPZRsiTC/J1VzmcTeivXAq1dLWshAPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/siX3s/btsIPZRsiTC/J1VzmcTeivXAq1dLWshAPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/siX3s/btsIPZRsiTC/J1VzmcTeivXAq1dLWshAPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsiX3s%2FbtsIPZRsiTC%2FJ1VzmcTeivXAq1dLWshAPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;371&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;739&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;user와 password를 입력해 로그인 하고, 현재 실행 중인 클러스터 정보를 볼 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;user는 기본적으로 elastic이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;306&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cb0aW1/btsIPJOWoUX/5Y5vtfbrzs5WhW8QG8RoU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cb0aW1/btsIPJOWoUX/5Y5vtfbrzs5WhW8QG8RoU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cb0aW1/btsIPJOWoUX/5Y5vtfbrzs5WhW8QG8RoU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcb0aW1%2FbtsIPJOWoUX%2F5Y5vtfbrzs5WhW8QG8RoU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;330&quot; height=&quot;261&quot; data-origin-width=&quot;306&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvQDXv/btsIPwhPFyt/s2MS3V6DntrWdegH9DGCu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvQDXv/btsIPwhPFyt/s2MS3V6DntrWdegH9DGCu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvQDXv/btsIPwhPFyt/s2MS3V6DntrWdegH9DGCu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvQDXv%2FbtsIPwhPFyt%2Fs2MS3V6DntrWdegH9DGCu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;243&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;Kibana 연동&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 Kibana와 연동해보자. &lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; letter-spacing: 0px;&quot;&gt;bin 폴더 하위의 kibana 배치 파일을 실행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Kibana는 5601 포트를 사용한다. http://localhost:5601 로 접속해자 (https 아님)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음과 같은 창이 뜨면, elasticsearch를 실행할 때 받은 토큰 값을 입력해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oFImQ/btsIRo3HwaY/ccggBnuYEXkXPyZdkxZttk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oFImQ/btsIRo3HwaY/ccggBnuYEXkXPyZdkxZttk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oFImQ/btsIRo3HwaY/ccggBnuYEXkXPyZdkxZttk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoFImQ%2FbtsIRo3HwaY%2FccggBnuYEXkXPyZdkxZttk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;520&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;토큰 값을 입력한 후, Configure Elastic 버튼을 누르면 다음과 같이 인증 코드를 요구한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dK8nmh/btsIQIBtCsv/cHk2xLL3MlAw7fRfQpid7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dK8nmh/btsIQIBtCsv/cHk2xLL3MlAw7fRfQpid7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dK8nmh/btsIQIBtCsv/cHk2xLL3MlAw7fRfQpid7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdK8nmh%2FbtsIQIBtCsv%2FcHk2xLL3MlAw7fRfQpid7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;319&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;kibana-verification-code 배치 파일을 실행해서 코드를 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722104013103&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\Users\...\kibana-8.14.3\bin&amp;gt;kibana-verification-code&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1487&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ADc5n/btsIQdIJ4Hn/3x4L0YeyZo0afeoldM5Ga0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ADc5n/btsIQdIJ4Hn/3x4L0YeyZo0afeoldM5Ga0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ADc5n/btsIQdIJ4Hn/3x4L0YeyZo0afeoldM5Ga0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FADc5n%2FbtsIQdIJ4Hn%2F3x4L0YeyZo0afeoldM5Ga0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;54&quot; data-origin-width=&quot;1487&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Welcome to Elastic이라는 문구와 함께 username과 password를 요구하면, elasticsearch에서 사용한 user와 password를 사용하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yO2Jl/btsIPZ41gck/DRvNXaS6s6aHwfpmaYlWPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yO2Jl/btsIPZ41gck/DRvNXaS6s6aHwfpmaYlWPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yO2Jl/btsIPZ41gck/DRvNXaS6s6aHwfpmaYlWPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyO2Jl%2FbtsIPZ41gck%2FDRvNXaS6s6aHwfpmaYlWPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;473&quot; height=&quot;528&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;좌측 메뉴에서 Analytics 카테고리가 Kibana이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lz6s4/btsIQZ39itY/Zm9rkPjFQhLhEuGaeUAm51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lz6s4/btsIQZ39itY/Zm9rkPjFQhLhEuGaeUAm51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lz6s4/btsIQZ39itY/Zm9rkPjFQhLhEuGaeUAm51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLz6s4%2FbtsIQZ39itY%2FZm9rkPjFQhLhEuGaeUAm51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;397&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;877&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>elasticsearch</category>
      <category>kibana</category>
      <author>Chef.Yeon</author>
      <guid isPermaLink="true">https://yeon-dev.tistory.com/250</guid>
      <comments>https://yeon-dev.tistory.com/250#entry250comment</comments>
      <pubDate>Sun, 28 Jul 2024 04:12:25 +0900</pubDate>
    </item>
    <item>
      <title>[TIL - 20240612] Swagger HTTPS 설정</title>
      <link>https://yeon-dev.tistory.com/249</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byRbiq/btsHVFtOe73/6KhtFjQF98NhDXvBJVF9M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byRbiq/btsHVFtOe73/6KhtFjQF98NhDXvBJVF9M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byRbiq/btsHVFtOe73/6KhtFjQF98NhDXvBJVF9M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyRbiq%2FbtsHVFtOe73%2F6KhtFjQF98NhDXvBJVF9M0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;350&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 문제점&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring REST Docs와 Swagger UI를 연동했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 정상 동작하는 것을 확인하고 개발 서버로 배포해서 테스트했으나, 다음과 같은 오류가 발생했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5hvbb/btsHXaTE2JD/GGQg0dKizcTEjzBZkdxfo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5hvbb/btsHXaTE2JD/GGQg0dKizcTEjzBZkdxfo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5hvbb/btsHXaTE2JD/GGQg0dKizcTEjzBZkdxfo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5hvbb%2FbtsHXaTE2JD%2FGGQg0dKizcTEjzBZkdxfo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;756&quot; height=&quot;174&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 서버 도메인은 https를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, Request URL을 보면 http를 사용하는 것을 볼 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;해결&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swagger가 기본적으로 http로 요청을 보내기 때문에 발생한 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwaggerConfig에서 @OpenAPIDefinition 애노테이션의 servers 속성을 사용하여 API가 호스팅 되는 서버 목록을 정의해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwaggerConfig&lt;/p&gt;
&lt;pre id=&quot;code_1718199093664&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.servers.Server;
// ...other import

@OpenAPIDefinition(
        servers = {
            @Server(url = &quot;https://개발서버.com&quot;, description = &quot;개발 서버&quot;),
            @Server(url = &quot;http://localhost:8080&quot;, description = &quot;로컬 서버&quot;)
        })
@Configuration
public class SwaggerConfig {
    private final String AUTH_SCHEME_NAME = &quot;JWT Access Token&quot;;
    private final String AUTH_SCHEME = &quot;bearer&quot;;
    private final String BEARER_FORMAT = &quot;JWT&quot;;

    @Bean
    public OpenAPI openAPI() {
        SecurityRequirement securityRequirement =
                new SecurityRequirement().addList(AUTH_SCHEME_NAME);
        Components components =
                new Components()
                        .addSecuritySchemes(
                                AUTH_SCHEME_NAME,
                                new SecurityScheme()
                                        .name(AUTH_SCHEME_NAME)
                                        .type(SecurityScheme.Type.HTTP)
                                        .description(&quot;bearer는 제외하고 입력해주세요.&quot;)
                                        .scheme(AUTH_SCHEME)
                                        .bearerFormat(BEARER_FORMAT));

        return new OpenAPI().components(components).addSecurityItem(securityRequirement);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 후 다시 확인해 본 결과, https를 사용해서 정상적으로 API 호출이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r8AiA/btsHWXG13Wg/j43WfkKSIlSmAsYM8fV7n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r8AiA/btsHWXG13Wg/j43WfkKSIlSmAsYM8fV7n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r8AiA/btsHWXG13Wg/j43WfkKSIlSmAsYM8fV7n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr8AiA%2FbtsHWXG13Wg%2Fj43WfkKSIlSmAsYM8fV7n1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;767&quot; height=&quot;477&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;477&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL</category>
      <category>til</category>
      <author>Chef.Yeon</author>
      <guid isPermaLink="true">https://yeon-dev.tistory.com/249</guid>
      <comments>https://yeon-dev.tistory.com/249#entry249comment</comments>
      <pubDate>Wed, 12 Jun 2024 22:39:09 +0900</pubDate>
    </item>
  </channel>
</rss>