Spring boot elasticsearch

저번 포스팅까지 elasticsearch 를 사용해 봤다.
이번에는 Spring boot로 elasticsearch를 사용해보자

spring-data-elasticsearch를 사용할 예정이고 ElasticsearchRepository와 ElasticsearchTemplate 두개를 사용해서 해볼 것이다.

ElasticsearchRepository

@Document(indexName = "account", type = "account", shards = 1, replicas = 0, refreshInterval = "-1")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {

  @Id
  private String id;

  private String name;

  private String email;

  public Account(String name, String email) {
    this.name = name;
    this.email = email;
  }
}

type(table)과 매핑되는 엔티티(?)들이다. @Document에 index명과 type명을 지정해 줄수 있다. 여기서는 account와 account로 지정해놨다 조금 이상하게 지정해 놨다. 아무튼

public interface AccountRepository extends ElasticsearchRepository<Account, Long> {

  List<Account> findByname(String name);

  Account findByemail(String email);
}

그리고 spring data 프로젝트인 만큼 ElasticsearchRepository 상속받아서 사용했다. 이것은 우리가 흔히 쓰던 JPA구조와 동일하다. 참 간편하고 확장성있게 잘 만든 듯하다.

각각을 테스트를 해보자

 @Test
  public void saveTest() {
    accountRepository.save(new Account("wid", "wid.com"));
  }

  @Test
  public void findAllTest() {
    accountRepository.findAll().forEach(System.out::println);
  }

  @Test
  public void findOneTest(){
    List<Account> wonwoo = accountRepository.findByname("wonwoo");
    System.out.println(wonwoo);
  }

잘들어가고 잘 나온다. 우리가 흔히 썼기 때문에 넘어가자

ElasticsearchTemplate

다음은 ElasticsearchTemplate을 활용해 보자.
스프링부트를 사용하면 ElasticsearchTemplate 는 자동으로 빈으로 생성되기 때문에 빈설정을 하지 않아도 된다.

  @Autowired
  private ElasticsearchTemplate elasticsearchTemplate;

  @Test
  public void saveTemplateTest() {
    Account account = new Account("kk", "kk@test.com");
    IndexQuery indexQuery = new IndexQuery();
    indexQuery.setObject(account);
    String index = elasticsearchTemplate.index(indexQuery);
    System.out.println(index);
  }

  @Test
  public void updateTemplateTest() {
    IndexRequest indexRequest = new IndexRequest();
    indexRequest.source("name", "wowowowo");
    UpdateQuery updateQuery = new UpdateQueryBuilder().withId("AVSAOJvzW3yAedZaYx2w")
      .withClass(Account.class).withIndexRequest(indexRequest).build();
    UpdateResponse update = elasticsearchTemplate.update(updateQuery);
    System.out.println(update);
  }

  @Test
  public void deleteTemplateTest() {
    String delete = elasticsearchTemplate.delete(Account.class, "AVSEzpSE4HLWkOEZq-DT");
    System.out.println(delete);
  }

간단하게 R 을 제외하고 CUD과정이다.
딱히 설명은 필요 없는 듯하다. 설명하지 않아도 알 듯싶으니.. 엘라스틱서치는 검색이 훨씬 방대하기 때문에 이거 또한 그냥 넘어가자.

검색을 보자. 다 설명하지 못하지만 필자가 테스트 해본것은 최대한 많이 설명 해볼 것이다.

  @Test
  public void getAccount() {
    GetQuery getQuery = new GetQuery();
    getQuery.setId("AVSAOJvzW3yAedZaYx2w");
    Account account = elasticsearchTemplate.queryForObject(getQuery, Account.class);
    System.out.println(account);
  }

위는 Id 기준으로 검색을 하는 쿼리이다. ID는 엘라스틱 서치에서 만들어주는 아이디다. 물론 지정해 줄수도 있다.

  @Test
  public void getAccountTemplateIndexAndTypeTest() {
    SearchQuery searchQuery = new NativeSearchQueryBuilder()
      .withQuery(matchAllQuery())
      .withIndices("account")
      .withTypes("account")
      .build();
    List<Account> accounts = elasticsearchTemplate.queryForList(searchQuery, Account.class);
    System.out.println(accounts);
  }

위의 코드는 index는 account type도 account에서 모든 데이터를 가져오는 쿼리이다.

  @Test
  public void getAccountTemplateCountTest() {
    SearchQuery searchQuery = new NativeSearchQueryBuilder()
      .withQuery(matchAllQuery())
      .withIndices("account")
      .withTypes("account")
      .build();
    long count = elasticsearchTemplate.count(searchQuery, Account.class);
    System.out.println(count);
  }

위의 코드는 count를 세는 코드이다 쿼리는 이 전과 동일하다.

  @Test
  public void getAccountTemplateIndexAndTypeFieldsTest() {
    SearchQuery searchQuery = new NativeSearchQueryBuilder()
      .withQuery(matchAllQuery())
      .withIndices("account")
      .withTypes("account")
      .withFields("name")
      .build();
    List<Account> accounts = elasticsearchTemplate.queryForList(searchQuery, Account.class);
    System.out.println(accounts);
  }

위의 코드는 이전과 동일하지만 필드를 name만 가져오는 쿼리이다. 실제 해보면 email은 null이 출력된다.

  @Test
  public void getAccountTemplatePage() {
    SearchQuery searchQuery = new NativeSearchQueryBuilder().withIndices("account")
      .withTypes("account").withQuery(matchAllQuery())
      .withSort(new FieldSortBuilder("name").order(SortOrder.DESC))
      .withPageable(new PageRequest(0, 5)).build();
    Page<Account> accounts = elasticsearchTemplate.queryForPage(searchQuery, Account.class);

    String str = accounts.getContent().stream()
      .map(i -> i.toString())
      .collect(joining("\n"));
    System.out.println(str);
    System.out.println(accounts.getContent().size());
  }

엘라스틱 서치의 페이징을 할 수 있는 쿼리다. 페이지는 0페이지이고 5개만 출력하며 name 기준으로 desc한 쿼리이다.

  @Test
  public void getAccountTemplateSearchTest() {

    SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("name", "wonwoo")).build();
    List<Account> accounts = elasticsearchTemplate.queryForList(searchQuery, Account.class);

    String str = accounts.stream()
      .map(i -> i.toString())
      .collect(joining("\n"));
    System.out.println(str);
    System.out.println(accounts.size());
  }

이번에는 name 필드에 wonwoo라는 단어가 있으면 모두 출력한다. wonwoo123이라는 단어가 있다면 출력 되지 않는다.
만약 wonwoo 123 이면 출력된다.

  @Test
  public void getAccountTemplateSearchWildcardQueryTest() {

    SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(wildcardQuery("name", "*wonwoo*")).build();

    List<Account> accounts = elasticsearchTemplate.queryForList(searchQuery, Account.class);
    String str = accounts.stream()
      .map(i -> i.toString())
      .collect(joining("\n"));
    System.out.println(str);
    System.out.println(accounts.size());
  }

이번에는 와일드카드쿼리를 사용한 것이다. 만약 이번에는 wonwoo123이 있다면 출력된다.

  @Test
  public void getAccountTemplateSearchQueryStringQueryTest() {

    SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(queryStringQuery("*wonwoo*")).build();

    List<Account> accounts = elasticsearchTemplate.queryForList(searchQuery, Account.class);
    String str = accounts.stream()
      .map(i -> i.toString())
      .collect(joining("\n"));
    System.out.println(str);
    System.out.println(accounts.size());
  }

이번 쿼리는 name email 어떤 필드건 상관없이 wonwoo라는 문자가 있으면 모두 출력한다. 단어와 상관없이 문자가 있으면 모두 출력된다.

이것으로 조금 엘라스틱서치에 대해 알것도 같다. 하지만 이거 또한 세발에 피인듯 싶다. 완전히 사용하려면 아직 멀었다.
일단 해당 소스는 github에 올려놔야 겠다.

elasticsearch 사용해보자.

저번 포스팅중에 엘라스틱서치를 설치 해봤다. 여기

설치는 해봤으니 사용해보자.

관계형 데이터베이스 elasticsearch
Database Index
Table Type
Row Document
Column Field
Schema Mapping
Index Everything is indexed
SQL Query DSL

출처 : http://d2.naver.com/helloworld/273788

관계형 데이터베이스와 엘라스틱 서치에 관한비교를 해봤다. 네이버 개발자블로그에서 찾았다.
우리는 예전에 엘라스틱서치의 플러그인도 설치를 해봤으니 거기서 해도 되고 curl로 해도 상관없다.
엘라스틱과 키바나를 실행 시키고 http://localhost:5601 다음과 같이 접속해보자!

엘라스틱 서치는 Rest Api를 지원한다.

http://host:port/(index)/(type)/(action|id) 

실습을 한번 해보자.

curl -XPUT "http://localhost:9200/movie/users/1" -d'
{
  "name": "wonwoo",
  "email": "wonwoo@test.com"
}'

위와 같이 요청을 했을 경우 아래와 같이 응답을 받을 것이다.

{
  "_index": "movie",
  "_type": "users",
  "_id": "1",
  "_version": 1,
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

index는 movie 이고 type은 users다 id는 1로 지정하였다.
그럼 이제 get을 해보자

curl -XGET "http://localhost:9200/movie/users/1"

위와 같이 해당 id를 get하면 된다.

{
  "_index": "movie",
  "_type": "users",
  "_id": "1",
  "_version": 1,
  "found": true,
  "_source": {
    "name": "wonwoo",
    "email": "wonwoo@test.com"
  }
}

그럼 우리가 위에서 넣어두었던 데이터들이 나올 것이다.
만약 id를 지정 하고 싶지 않다면 POST로 요청하면 된다.

curl -XPOST "http://localhost:9200/movie/users/" -d'
{
  "name": "kevin",
  "email": "kevin@test.com"
}'

위와 같이 요청을 하였을 경우에는 id가 자동 생성된다.

{
  "_index": "movie",
  "_type": "users",
  "_id": "AVSBOFuDW3yAedZaY19R",
  "_version": 1,
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

그리고 확인을 해보자

curl -XGET "http://localhost:9200/movie/users/_search"

위는 전체를 서치 하는 것이다.

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "movie",
        "_type": "users",
        "_id": "AVSBOFuDW3yAedZaY19R",
        "_score": 1,
        "_source": {
          "name": "kevin",
          "email": "kevin@test.com"
        }
      },
      {
        "_index": "movie",
        "_type": "users",
        "_id": "1",
        "_score": 1,
        "_source": {
          "name": "wonwoo",
          "email": "wonwoo@test.com"
        }
      }
    ]
  }
}

제대로 동작한다. 두번째 id는 자동으로 생성 된것을 확인 할 수 있다.

만약 컬럼(필드)중 name이라는 필드만 보이고 싶다면 아래와 같이 하면 된다.

curl -XGET "http://localhost:9200/movie/users/1?_source=name"

그럼 아래와 같이 출력 될것이다. 전체로 검색 하였을 경우에도 가능하다.

{
  "_index": "movie",
  "_type": "users",
  "_id": "1",
  "_version": 1,
  "found": true,
  "_source": {
    "name": "wonwoo"
  }
}

특정 필드를 검색 하고 싶다면 아래와 같이 하자.

curl -XGET "http://localhost:9200/movie/users/_search?q=name:wonwoo"
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.30685282,
    "hits": [
      {
        "_index": "movie",
        "_type": "users",
        "_id": "1",
        "_score": 0.30685282,
        "_source": {
          "name": "wonwoo",
          "email": "wonwoo@test.com"
        }
      }
    ]
  }
}

like로 검색을 하고 싶다면 아래와 같이 하면 된다.

curl -XGET "http://localhost:9200/movie/users/_search?q=name:*woo*"

앞뒤로 * 붙여도 되고 앞뒤 한곳에만 붙어도 상관없다. 원하는 것을 검색하고 싶을때 마음 대로 하면 된다.

{
  "took": 15,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "movie",
        "_type": "users",
        "_id": "AVSBUBdyW3yAedZaY2gz",
        "_score": 1,
        "_source": {
          "name": "wonwoo123",
          "email": "test@test.com"
        }
      },
      {
        "_index": "movie",
        "_type": "users",
        "_id": "1",
        "_score": 1,
        "_source": {
          "name": "wonwoo",
          "email": "wonwoo@test.com"
        }
      }
    ]
  }
}

마지막으로 삭제를 해보자

curl -XDELETE "http://localhost:9200/movie/users/1"

그럼 성공 했다고 아래와 같이 출력 될 것이다.

{
  "found": true,
  "_index": "movie",
  "_type": "users",
  "_id": "1",
  "_version": 2,
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  }
}

그리고 다시 확인해보면 삭제된 것을 알 수 있다.

몇가지만 더 보자

curl -XGET "http://localhost:9200/movie/users/_search?q=wonwoo"

어떤 필드이던 간에 wonwoo라는 값이 있으면 다 출력 된다.

curl -XGET "http://localhost:9200/movie/users/_search?q=-kevin"

kevin이라는 값이 있는걸 제외하고 출력한다.

curl -XGET "http://localhost:9200/movie/users/_search?_source=false"

source를 제외하고 meta 정보만 출력한다.

curl -XGET "http://localhost:9200/movie/users/_search?sort=name:desc"

name이라는 필드 기준으로 정렬을 해준다. 기본은 asc이다.

curl -XGET "http://localhost:9200/movie/users/_search?q=name:(wonwoo%20AND%20test)"

name이라는 필드에 wonwoo와 test가 있으면 출력한다.

curl -XGET "http://localhost:9200/movie/users/_search?q=email:test%20kevin"

email이라는 필드에 test나 kevin이 있으면 출력한다. 기본으로 or조건이듯 하다.

curl -XGET "http://localhost:9200/movie/users/_search?q=(name:(wonwoo%20AND%20test)%20AND%20email:wonwoo)"

name이라는 필드에는 wonwoo와 test가 있어야 하고 email이라는 필드에는 wonwoo가 있으면 출력한다.

물론 curl로 요청 하는 것도 괜찮지만 우리가 설치 했던 플러그인 중 sense라는 플러그인을 사용해서 하면 UI도 이쁘게 나오고 훨씬 편하다 sense라는 플러그인을 사용해서 한번씩 해보자!

elasticsearch 설치 해보자

elasticsearch 설치를 해보자
mac 기준으로 brew 말고 파일을 다운로드 받으면서 할 것이다.
일단 홈페이지 가서 다운로드 받자.

압축을 해지한 뒤에 적절할 곳에 폴더를 두자.
엘라스틱서치 루트에 가서 다음과 같이 입력하자

bin/elasticsearch

그럼 쭉쭉 로그가 올라온다.
잘되는지 확인하기 위해 http://localhost:9200 으로 접속해보자.
그러면 json으로 출력 결과를 보여줄 것이다.

다음으로 플러그인을 설치하자.
marvel 플러그인과 sense 플러그인을 설치 할 것이다.
그전에 설치할 것이 있는데 kibana 라는 것이다.
여기 가서 다운로드 받자.
이것 또한 적절할 곳에 압축을 해지 한후에 다음과 같이 입력하면 된다.

bin/kibana

그럼 실행이 될 것이다. 그리고 나서 http://localhost:5601 같이 브라우저를 띄우면 웹페이지가 나올 것이다.

이제는 marvel 플러그인과 sense 플러그인을 설치하자.

엘라스틱서치 폴더로 가서 다음과 같이 플러그인을 설치하자.

bin/plugin install license
bin/plugin install marvel-agent

이번엔 키바나 폴더에 가서 다음과 같이 입력하자

bin/kibana plugin --install elasticsearch/marvel/2.3.1

2.3.1 은 버전인듯 신규 버전이 나오면 다른걸로 해야될듯 하다.
marvel 플러그인은 설치가 됐다.
다음으로 sense 설치 하자. 간단하다.

bin/kibana plugin --install elastic/sense

이제 설치를 마치고 엘라스틱 과 키바나를 재 실행 하자.
엘라스틱 폴더로 가서

bin/elasticsearch

키바나 폴더로 가서

bin/kibana

그리고 웹페이지를 열어서 확인하자!
그럼 일단 설치는 완료 되었다.
나중에 활용해보도록 하자!