Spring Boot + Spring Data + Elasticsearch 구현과 고찰

2017. 7. 7. 17:58DB&NoSQL/Elasticsearch,ELK

 역시 Mkyong.com 에서 가이드를 해주고 계셨습니다.
해당 아티클에 대한 내용을 정리해봅시다.


이 기사는, 우리가 "Spring Boot + Spring Data + Elasticsearch 예제를 생성하는 방법"에 대해서 논의해보고자 합니다.
이 기사에서 사용되는 툴은 아래와 같습니다.

  1. Spring Boot 1.5.1 Release
  2. Spring Boot Starter Data Elasticsearch 1.5.1 RELEASE
  3. Spring Data Elasticsearch 2.10 RELEASE
  4. Elasticsearch 2.4.4


충격

SpringBoot 1.5.1 RELEASE 와 Spring Data Elasticsearch 2.10 RELEASE는 Elasticsearch 2.4.0 만 지원합니다.
가장 최신 버전인 Elasticsearch 5.x 는 지원하지 않습니다. T.T



1. Project Structure

springboot-springdata-elasticsearch-example
 - book
   -model
     -- Book
   -repository
     -- BookRepository
   -service
     -- BookService
     -- BookServiceImpl
   -- Application
   --EsConfig
-resource
  --application.properties
-test
 - java
   -package
      -BookServiceTest

2. Project Dependency

Spring Data ElasticSearch 어플리케이션을 위한 "springboot-boot-starter-data-elasticsearch" 를 선언합니다. 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.neonex.enav.elasticsearch</groupId>
    <artifactId>LogViewer</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--Runtime, for Embedded Elasticsearch,
            comment this if connect to external elastic search server -->
        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <!-- Package as an executable jar/war -->
            <plugin>
                <groupId>org.springframwork.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

프로젝트 의존성들을 다시 봅시다.
mvn dependency:tree 

$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO][INFO] ------------------------------------------------------------------------
[INFO] Building springboot-springdata-elasticsearch-example 1.0
[INFO] ------------------------------------------------------------------------
[INFO][INFO] --- maven-dependency-plugin:2.10:tree (default-cli) @ springboot-springdata-elasticsearch-example ---
[INFO] org.springframework.boot:springboot-springdata-elasticsearch-example:jar:1.0
[INFO] +- org.springframework.boot:spring-boot-starter-data-elasticsearch:jar:1.5.1.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:1.5.1.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:1.5.1.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:1.5.1.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.5.1.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.1.9:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.1.9:compile
[INFO] |  |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.22:compile
[INFO] |  |  |  \- org.slf4j:log4j-over-slf4j:jar:1.7.22:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.17:compile
[INFO] |  \- org.springframework.data:spring-data-elasticsearch:jar:2.1.0.RELEASE:compile
[INFO] |     +- org.springframework:spring-context:jar:4.3.6.RELEASE:compile
[INFO] |     |  +- org.springframework:spring-aop:jar:4.3.6.RELEASE:compile
[INFO] |     |  +- org.springframework:spring-beans:jar:4.3.6.RELEASE:compile
[INFO] |     |  \- org.springframework:spring-expression:jar:4.3.6.RELEASE:compile
[INFO] |     +- org.springframework:spring-tx:jar:4.3.6.RELEASE:compile
[INFO] |     +- org.springframework.data:spring-data-commons:jar:1.13.0.RELEASE:compile
[INFO] |     +- commons-lang:commons-lang:jar:2.6:compile
[INFO] |     +- joda-time:joda-time:jar:2.9.7:compile
[INFO] |     +- org.elasticsearch:elasticsearch:jar:2.4.4:compile
[INFO] |     |  +- org.apache.lucene:lucene-core:jar:5.5.2:compile
[INFO] |     |  +- org.apache.lucene:lucene-backward-codecs:jar:5.5.2:compile
[INFO] |     |  +- org.apache.lucene:lucene-analyzers-common:jar:5.5.2:compile
[INFO] |     |  +- org.apache.lucene:lucene-queries:jar:5.5.2:compile
[INFO] |     |  +- org.apache.lucene:lucene-memory:jar:5.5.2:compile
[INFO] |     |  +- org.apache.lucene:lucene-highlighter:jar:5.5.2:compile
[INFO] |     |  +- org.apache.lucene:lucene-queryparser:jar:5.5.2:compile
[INFO] |     |  |  \- org.apache.lucene:lucene-sandbox:jar:5.5.2:compile
[INFO] |     |  +- org.apache.lucene:lucene-suggest:jar:5.5.2:compile
[INFO] |     |  |  \- org.apache.lucene:lucene-misc:jar:5.5.2:compile
[INFO] |     |  +- org.apache.lucene:lucene-join:jar:5.5.2:compile
[INFO] |     |  |  \- org.apache.lucene:lucene-grouping:jar:5.5.2:compile
[INFO] |     |  +- org.apache.lucene:lucene-spatial:jar:5.5.2:compile
[INFO] |     |  |  +- org.apache.lucene:lucene-spatial3d:jar:5.5.2:compile
[INFO] |     |  |  \- com.spatial4j:spatial4j:jar:0.5:compile
[INFO] |     |  +- com.google.guava:guava:jar:18.0:compile
[INFO] |     |  +- org.elasticsearch:securesm:jar:1.0:compile
[INFO] |     |  +- com.carrotsearch:hppc:jar:0.7.1:compile
[INFO] |     |  +- com.fasterxml.jackson.dataformat:jackson-dataformat-smile:jar:2.8.6:compile
[INFO] |     |  +- com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.8.6:compile
[INFO] |     |  +- com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:jar:2.8.6:compile
[INFO] |     |  +- io.netty:netty:jar:3.10.6.Final:compile
[INFO] |     |  +- com.ning:compress-lzf:jar:1.0.2:compile
[INFO] |     |  +- com.tdunning:t-digest:jar:3.0:compile
[INFO] |     |  +- org.hdrhistogram:HdrHistogram:jar:2.1.6:compile
[INFO] |     |  +- commons-cli:commons-cli:jar:1.3.1:compile
[INFO] |     |  \- com.twitter:jsr166e:jar:1.1.0:compile
[INFO] |     +- com.fasterxml.jackson.core:jackson-core:jar:2.8.6:compile
[INFO] |     +- com.fasterxml.jackson.core:jackson-databind:jar:2.8.6:compile
[INFO] |     |  \- com.fasterxml.jackson.core:jackson-annotations:jar:2.8.0:compile
[INFO] |     +- org.slf4j:slf4j-api:jar:1.7.22:compile
[INFO] |     \- org.slf4j:jcl-over-slf4j:jar:1.7.22:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:1.5.1.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test:jar:1.5.1.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:1.5.1.RELEASE:test
[INFO] |  +- com.jayway.jsonpath:json-path:jar:2.2.0:test
[INFO] |  |  \- net.minidev:json-smart:jar:2.2.1:test
[INFO] |  |     \- net.minidev:accessors-smart:jar:1.1:test
[INFO] |  |        \- org.ow2.asm:asm:jar:5.0.3:test
[INFO] |  +- junit:junit:jar:4.12:test
[INFO] |  +- org.assertj:assertj-core:jar:2.6.0:test
[INFO] |  +- org.mockito:mockito-core:jar:1.10.19:test
[INFO] |  |  \- org.objenesis:objenesis:jar:2.1:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  +- org.skyscreamer:jsonassert:jar:1.4.0:test
[INFO] |  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] |  +- org.springframework:spring-core:jar:4.3.6.RELEASE:compile
[INFO] |  \- org.springframework:spring-test:jar:4.3.6.RELEASE:test
[INFO] \- net.java.dev.jna:jna:jar:4.2.2:runtime
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.867 s
[INFO] Finished at: 2017-03-14T19:55:41+08:00
[INFO] Final Memory: 27M/437M
[INFO] ------------------------------------------------------------------------


3. Spring Data Elasticsearch Application

Spring Boot + Spring Data + Elasticsearch 예제를 드디어 시작해봅시다.

3.1 우리 프로젝트를 위한 모델 개발하기 

package com.neonex.enav.elasticsearch.book.model;

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

/**
 * Created by dennis on 2017-07-06.
 */

@Document(indexName="dennis"type="books")
public class Book {

    @Id
    private String id;
    private String title;
    private String author;
    private String releaseDate;
   
    public Book(){
    }

    public Book(String idString titleString authorString releaseDate) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.releaseDate = releaseDate;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getReleaseDate() {
        return releaseDate;
    }

    public void setReleaseDate(String releaseDate) {
        this.releaseDate = releaseDate;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id='" id '\'+
                ", title='" title '\'+
                ", author='" author '\'+
                ", releaseDate='" releaseDate '\'+
                '}';
    }
}

3.2 우리의 프로젝트를 위한 Elasticsearch Repository 개발하기 

package com.neonex.enav.elasticsearch.book.repository;

import com.neonex.enav.elasticsearch.book.model.Book;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

/**
 * Created by dennis on 2017-07-06.
 */
public interface BookRepository extends ElasticsearchRepository<Book,String>{
    Page<Book> findByAuthor(String authorPageable pageable);

    List<Book> findByTitle(String title);
}

3.3 우리 프로젝트를 위한 서비스 컴포넌트 개발하기 

package com.neonex.enav.elasticsearch.book.service;

import com.neonex.enav.elasticsearch.book.model.Book;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;

import java.util.List;

/**
 * Created by dennis on 2017-07-06.
 */
public interface BookService {

    Book save(Book book);

    void delete(Book book);

    Book findOne(String id);

    Iterable<Book> findAll();

    Page<Book> findByAuthor(String authorPageRequest pageRequest);

    List<Book> findByTitle(String title);
}


package com.neonex.enav.elasticsearch.book.service;

import com.neonex.enav.elasticsearch.book.model.Book;
import com.neonex.enav.elasticsearch.book.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * Created by dennis on 2017-07-06.
 */
@Service
public class BookServiceImpl implements BookService {

    private BookRepository bookRepository;

    @Autowired
    public void setBookRepository(BookRepository bookRepository){
        this.bookRepository = bookRepository;
    }

    @Override
    public Book save(Book book) {
        return bookRepository.save(book);
    }

    @Override
    public void delete(Book book) {
        bookRepository.delete(book);
    }

    @Override
    public Book findOne(String id) {
        return bookRepository.findOne(id);
    }

    @Override
    public Iterable<Book> findAll() {
        return bookRepository.findAll();
    }

    @Override
    public Page<Book> findByAuthor(String authorPageRequest pageRequest) {
        return bookRepository.findByAuthor(author,pageRequest);
    }

    @Override
    public List<Book> findByTitle(String title) {
        return bookRepository.findByTitle(title);
    }
}

3.4 Elasticsearch properties와 함께 application.properties 개발하기 

elasticsearch.clustername=dennis-cluster
elasticsearch.host=localhost
elasticsearch.port=9300

#
# Home directory of the embedded Elasticsearch instance. Default to the
# current working directory.
#
#spring.data.elasticsearch.properties.path.home=target/elastic-embedded
#spring.data.elasticsearch.properties.transport.tcp.connect_timeout=60s

3.5 SpringBoot Configuration 개발하기. 
      TransportClient를 통해서 Elasticsearch 클러스터에 연결하기


package com.neonex.enav.elasticsearch.book;

import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;

import java.net.InetAddress;

/**
 * Created by dennis on 2017-07-06.
 */
@Configuration
@EnableElasticsearchRepositories(basePackages="com.neonex.enav.elasticsearch.book.repository")
public class EsConfig {

    @Value("${elasticsearch.host}")
    private String EsHost;

    @Value("${elasticsearch.port}")
    private int EsPort;

    @Value("${elasticsearch.clustername}")
    private String EsClusterName;

    @Bean
    public Client client() throws Exception{
        Settings esSettings = Settings.settingsBuilder()
                .put("cluster.name",EsClusterName)
                .build();

        //https://www.elastic.co/guide/en/elasticsearch/guide/current/_transport_client_versus_node_client.html
        return TransportClient.builder()
                .settings(esSettings)
                .build()
                .addTransportAddress(
                        new InetSocketTransportAddress(InetAddress.getByName(EsHost)EsPort)
                );
    }

    @Bean
    public ElasticsearchOperations elasticsearchTemplate() throws Exception{
        return new ElasticsearchTemplate(client());
    }

    //Embedded Elasticsearch Server
    /*@Bean
    public ElasticsearchOperations elasticsearchTemplate() {
        return new ElasticsearchTemplate(nodeBuilder().local(true).node().client());
    }*/
}


4. Test Application 개발하기

우리 코드를 단위 테스트 하기 위한 Test Application을 개발해봅시다.


package com.neonex.enav.elasticsearch;

import com.neonex.enav.elasticsearch.book.Application;
import com.neonex.enav.elasticsearch.book.model.Book;
import com.neonex.enav.elasticsearch.book.service.BookService;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;

/**
 * Created by dennis on 2017-07-06.
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class BookServiceTest {

    @Autowired
    private BookService bookService;

    @Autowired
    private ElasticsearchTemplate  esTemplate;

    @Before
    public void before(){
        esTemplate.deleteIndex(Book.class);
        esTemplate.createIndex(Book.class);
        esTemplate.putMapping(Book.class);
        esTemplate.refresh(Book.class);
    }

    @Test
    public void testSave(){
        Book book = new Book("1001""Elasticsearch Basics""Rambabu Posa""23-FEB-2017");
        Book testBook = bookService.save(book);

        assertNotNull(testBook.getId());
        assertEquals(testBook.getTitle(),book.getTitle());
        assertEquals(testBook.getAuthor()book.getAuthor());
        assertEquals(testBook.getReleaseDate()book.getReleaseDate());
    }

    @Test
    public void testFindOne(){

        Book book = new Book("1001""Elasticsearch Basics""Rambabu Posa""23-FEB-2017");
        bookService.save(book);

        Book testBook = bookService.findOne(book.getId());

        assertNotNull(testBook.getId());
        assertEquals(testBook.getTitle(),book.getTitle());
        assertEquals(testBook.getAuthor()book.getAuthor());
        assertEquals(testBook.getReleaseDate()book.getReleaseDate());
    }

    @Test
    public void testFindByTitle(){
        Book book = new Book("1001""Elasticsearch Basics""Rambabu Posa""23-FEB-2017");
        bookService.save(book);

        List<Book> byTitle = bookService.findByTitle(book.getTitle());
        assertThat(byTitle.size(),is(1));
    }

    @Test
    public void testFindByAuthor(){
        List<Book> bookList = new ArrayList();

        bookList.add(new Book("1001""Elasticsearch Basics""Rambabu Posa""23-FEB-2017"));
        bookList.add(new Book("1002""Apache Lucene Basics""Rambabu Posa""13-MAR-2017"));
        bookList.add(new Book("1003""Apache Solr Basics""Rambabu Posa""21-MAR-2017"));
        bookList.add(new Book("1007""Spring Data + ElasticSearch""Rambabu Posa""01-APR-2017"));
        bookList.add(new Book("1008""Spring Boot + MongoDB""Mkyong""25-FEB-2017"));

        for(Book book:bookList){
            bookService.save(book);
        }

        Page<Book> byAuthor = bookService.findByAuthor("Rambabu Posa", new PageRequest(010));
        assertThat(byAuthor.getTotalElements()is(4L));

        Page<Book> byAuthor2 = bookService.findByAuthor("Mkyong", new PageRequest(010));
        assertThat(byAuthor2.getTotalElements()is(1L));

    }

    @Test
    public void testDelete(){
        Book book = new Book("1001""Elasticsearch Basics""Rambabu Posa""23-FEB-2017");
        bookService.save(book);
        bookService.delete(book);
        Book testBook = bookService.findOne(book.getId());
        assertNull(testBook);
    }
}

5. Run Spring Boot Application

5.1 이 데모를 구동하기 위해, 아래 단계를 밟아야 합니다.

- Prerequisite-1 : Java 설치하고, JAVA_HOME  과 PATH 변수값 등록
- Prerequisite-2 : Maven 설치
- Prerequisite-3 : Elasticsearch 2.4.0 을 설치 
   ELASTICSEARCH_HOME = C:\elasticsearch-2.4.0
- Prerequisite-4 : Elasticsearch Cluster를 설정합니다.
  config/elasticsearch.yml 파일을 열어서 아래 설정을 추가해주세요

# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
#
cluster.name: dennis-cluster

- Prerequisite-5 : Elasticsearch 인스턴스를 시작하세요.

5.2 Spring Boot를 시작하시요, Elastic Server 안에 3개의 Book 객체들을 추가할 것입니다.

package com.neonex.enav.elasticsearch.book;

import com.neonex.enav.elasticsearch.book.model.Book;
import com.neonex.enav.elasticsearch.book.service.BookService;
import org.elasticsearch.client.Client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;

import java.util.Map;

/**
 * Created by dennis on 2017-07-07.
 */
@SpringBootApplication
public class Application implements CommandLineRunner {

    @Autowired
    private ElasticsearchOperations es;

    @Autowired
    private BookService bookService;

    public static void main(String args[]) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) throws Exception {

        printElasticSearchInfo();

        bookService.save(new Book("1001""Elasticsearch Basics""Rambabu Posa""23-FEB-2017"));
        bookService.save(new Book("1002""Apache Lucene Basics""Rambabu Posa""13-MAR-2017"));
        bookService.save(new Book("1003""Apache Solr Basics""Rambabu Posa""21-MAR-2017"));

        //fuzzey search
        Page<Book> books = bookService.findByAuthor("Rambabu", new PageRequest(010));

        //List<Book> books = bookService.findByTitle("Elasticsearch Basics");

        books.forEach(x -> System.out.println(x));
    }

    //useful for debug, print elastic search details
    private void printElasticSearchInfo() {

        System.out.println("--ElasticSearch--");
        Client client = es.getClient();
        Map<StringString> asMap = client.settings().getAsMap();

        asMap.forEach((kv) -> {
            System.out.println(k + " = " + v);
        });
        System.out.println("--ElasticSearch--");
    }

}

확인

http://localhost:9200/_cat/indices?v&pretty=true

health status index  pri rep docs.count docs.deleted store.size pri.store.size 
yellow open   mkyong   5   1          3            0     11.5kb         11.5kb 

http://localhost:9200/mkyong/books/_search?pretty=true


{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "mkyong",
      "_type" : "books",
      "_id" : "1001",
      "_score" : 1.0,
      "_source" : {
        "id" : "1001",
        "title" : "Elasticsearch Basics",
        "author" : "Rambabu Posa",
        "releaseDate" : "23-FEB-2017"
      }
    }, {
      "_index" : "mkyong",
      "_type" : "books",
      "_id" : "1002",
      "_score" : 1.0,
      "_source" : {
        "id" : "1002",
        "title" : "Apache Lucene Basics",
        "author" : "Rambabu Posa",
        "releaseDate" : "13-MAR-2017"
      }
    }, {
      "_index" : "mkyong",
      "_type" : "books",
      "_id" : "1003",
      "_score" : 1.0,
      "_source" : {
        "id" : "1003",
        "title" : "Apache Solr Basics",
        "author" : "Rambabu Posa",
        "releaseDate" : "21-MAR-2017"
      }
    } ]
  }
}

http://localhost:9200/mkyong/books/_search?q=_id:1003&pretty=true


{
  "took" : 11,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "mkyong",
      "_type" : "books",
      "_id" : "1003",
      "_score" : 1.0,
      "_source" : {
        "id" : "1003",
        "title" : "Apache Solr Basics",
        "author" : "Rambabu Posa",
        "releaseDate" : "21-MAR-2017"
      }
    } ]
  }
}

더 간단한 예제 



결론 
1) 다시 2.4.0 을 설치하고, 위의 소스대로 테스트 했더니 잘 된다.
   주의 할 점은 port 지정이다. 나도 처음에 왜? 9200 으로 포트를 지정했는데, 9300 으로 적어 놨지 했는데 계속 에러 발생..
   이상해서.. 찾아보니... 아래와 같이 node의 포트를 체크하라고 나왔습니다. 웹 URL은 9200 , node의 포트는 9300 이라는 놀라운 사실.
   (" You should check the node's port, you could do it using head. These ports are not same.
      Example, The web URL you can open is localhost:9200, but the node's port is 9300,
       so none of the configured nodes are available if you use the 9200 as the port.")

2)  Spring Boot 에서는 .... Elasticsearch와의 연동이 아주 잘되는 것처럼 기재해 놨다.
 ( Elasticsearch is an open source, distributed, real-time search and analytics engine.
   Spring Boot offers basic auto-configuration for the Elasticsearch and abstractions on top of it provided by Spring Data Elasticsearch.
    There is a spring-boot-starter-data-elasticsearch ‘Starter’ for collecting the dependencies in a convenient way. Spring Boot also supports Jest.)
    이중에서 Jest 라는 것도 있다.  아무튼 현재 스냅샷 버전에서는 5.x.x 를 지원하니...

3) 현재 최신 버전의 Elasticsearch 는 5.4.0 버전이고, 처음 언급한대로,  위의 소스로는 반영이 안된다. 
    Spring Boot 와 궁합이 맞는 Elasticsearch 5.x.x 버전과 Spring Boot (Spring Data Elasticsearch)와 궁합이 맞는 버전은 
   아직 Release 된건 없지만, 3.0.0.BUILD-SNAPSHOT 으로는 가능하다고 한다. 이 부분을 좀더 살펴봐야 겠다. 


Spring Data Elasticsearch

Elasticsearch를 위한 Spring Data의 구현(implementation)

Spring Data는 관계형 데이터베이스 기술들을 위한 개선된 지원을 제공하는 것 뿐만 아니라, 새로운 데이터 접근 기술, non-relation 데이터베이스,
map-reduce 프레임워크들, 클라우드 기반 데이터 서비스들과 같은 것들을 사용하는 Spring-powered 어플리케이션을 구축하는 것을 용이하게 만들어 줍니다.
Spring Data Elasticsearch 프로젝트는 elasticsearch 검색 엔진과의 통합을 제공합니다.

Guide
- Reference Documentation
- API Documentation
- Spring Data project
- Sample Test Application
- Issues
- Spring Data Elasticsearch Google Group
- Questions.

Quick Start
- Wiki page for Getting Started

메이븐 구성 (Maven configuration)
메이븐 의존관계를 주입하도록 하겠습니다.
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-elasticsearch</artifactId>
    <version>x.y.z.RELEASE</version>
</dependency>

여러분이 차라리 최신의 메이저 버전의  snapshot을 사용하길 원하신다면,
Maven snapshot repository를 사용하고, 적당한 의존관계 버전을 선언하세요.

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-elasticsearch</artifactId>
  <version>x.y.z.BUILD-SNAPSHOT</version>
</dependency>

<repository>
  <id>spring-libs-snapshot</id>
  <name>Spring Snapshot Repository</name>
  <url>http://repo.spring.io/libs-snapshot</url>
</repository>


spring data elasticsearchelasticsearch
3.0.0.BUILD-SNAPSHOT5.4.0
2.0.4.RELEASE2.4.0
2.0.0.RELEASE2.2.0
1.4.0.M11.7.3
1.3.0.RELEASE1.5.2
1.2.0.RELEASE1.4.4
1.1.0.RELEASE1.3.2
1.0.0.RELEASE
1.1.1

ElasticsearchRepository

ElasticsearchRepostiroy의 기본 구현, 일반적인 Repository Interface들에 대한 정렬을 제공합니다.
Spring은 인터페이스 정의안에 메소드 이름들에 따라 여러분들을 위해 Repository 구현을 할수 있습니다.

ElasticsearchCrudRepository 은 PagingAndSortingRepository을 확장합니다.

public interface ElasticsearchCrudRepository<T, ID extends Serializable>
                   extends ElasticsearchRepository<T,ID>, PagingAndSortingRepository<T,ID>{
}

고객(custom) 메소드들을 위한 ElasticsearchRepository 확장

public interface BookRepository extends Repository<Book, String>{

   List<Book> findByNameAndPrice(String name, Integer price);

   List<Boot> findByNameOrPrice(String name, Integer price);

   Page<Book> findByName(String name, Pageable page);

   Page<Book> findyByNameNot(String name, Pageable page);

   Page<Book> findByPriceBetween(int price, Pageable page);

   Page<Book> findByNameLike(String name,Pageable page);

  @Query("{\"bool\" : {\"must\" : { \"term\" : {\"message\" : \"?0\"}}}}")
   Page<Book> findByMessage(String message, Pageable pageable);

}

Repository과 싱글 문서 인덱싱하기

@Autowired
private SampleElasticsearchRepository repository;

String documentId = "123456";
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setId(documentId);
sampleEntity.setMessage("some message");

repository.save(sampleEntity);

Repository를 사용한 멀티 문서(bulk index) 인덱싱하기

@Autowired
private SampleElasticsearchRepository repository;

String documentId = "123456";
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setId(documentId);
sampleEntity.setMessage("some message");

String documentId2 = "123457";
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setId(documentId2);
sampleEntity2.setMessage("test message");

List<SampleEntity> sampleEntities = Arrays.asList(sampleEntity1, sampleEntity2);

//bulk index
repository.save(sampleEntities);

ElasticsearchTemplate
elasticsearchTemplate은 elasticsearch 동작등을 위한 중앙 지원 클래스(the central support class) 입니다.

Elasticsaerch Template를 사용한 싱글 문서 인덱싱하기

String documentId = "123456";
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setId(documentId);
sampleEntity.setMessage("some message");
IndexQuery indexQuery = new IndexQueryBuilder().withId(sampleEntity.getID()).withObject(sampleEntity).build();
elasticsearchTemplate.index(indexQuery);

Elasticsearch Template를 사용한 멀티 문서  인덱스하기

@Autowired
private ElasticsearchTemplate elasticsearchTemplate;

List<IndexQuery> indexQueries = new ArrayList<indexQuery>();
//first document
String documentId = "123456";
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setId(documentId);
sampleEntity.setMessage("some message");

IndexQuery indexQuery1 = new IndexQueryBuilder().withId(sampleEntity1.getId()).withObject(sampleEntity1).build();
indexQueries.add(indexQuery1);

//second document

String documentId2 = "123457";
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setId(documentId2);
sampleEntity2.setMessage("test message");

IndexQuery indexQuery2 = new IndexQueryBuilder().withId(sampleEntity2.getId()).withObject(sampleEntity2).build();
indexQueries.add(indexQuery2);

//bulk index
elasticsearchTemplate.bulkIndex(indexQueries);

Elasticsearch Template를 사용하여 entites 찾기!!!!

@Autowired
private ElasticsearchTemplate elasticsearchTemplate;

SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryString(documentId).field("id"))
.build();
Page<SampleEntity> sampleEntites = elasticsearchTemplate.queryForPage(searchQuery,SampleEntity.class);


XML Namespace

xml configuration을 통해 여러분의 repositories를 잘 만들 , repository 스캐닝을 셋업 할수 있습니다.

Node 클라이언트 사용하기

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
       xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd              http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <elasticsearch:node-client id="client" local="true"/>

    <bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
        <constructor-arg name="client" ref="client"/>
    </bean>

</beans>

Transport 클라이언트 사용하기

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
       xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd              http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <elasticsearch:repositories base-package="com.xyz.acme"/>

    <elasticsearch:transport-client id="client" cluster-nodes="ip:9300,ip:9300" cluster-name="elasticsearch" />

    <bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
        <constructor-arg name="client" ref="client"/>
    </bean>

</beans>


Help Pages

- Geo distance and location search
- Custom object mapper