스프링부트 3.x와 엘라스틱서치 8.x 통합 가이드

엘라스틱서치

Docker 배포

컨테이너 구성 (docker-compose.yml)

version: "3.1"
# 서비스 구성
services:
  elasticsearch:
    container_name: elasticsearch-8.8.1
    image: docker.elastic.co/elasticsearch/elasticsearch:8.8.1
    # 컨테이너 root 권한 부여 (보안에 취약하므로 실제 운영 환경에서는 제거)
    privileged: true
    # Linux의 ulimit 명령으로 프로세스 리소스 제한
    ulimits:
      memlock:
        soft: -1
        hard: -1
    environment:
      - "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
      - "http.host=0.0.0.0"
      - "node.name=elastic01"
      - "cluster.name=cluster_elasticsearch"
      - "discovery.type=single-node"
    ports:
      - "9200:9200"
      - "9300:9300"
    volumes:
      # - ./elasticsearch/config:/usr/share/elasticsearch/config
      - ./elasticsearch/data:/usr/share/elasticsearch/data
      # - ./elasticsearch/plugin/xxx:/usr/share/elasticsearch/plugins/xxx
    networks: 
      - elastic_net
  kibana:
    container_name: kibana-8.8.1
    image: docker.elastic.co/kibana/kibana:8.8.1
    ports:
      - "5601:5601"
    # volumes:
    #   - ./kibana/config:/usr/share/kibana/config
    networks:
      - elastic_net
# 네트워크 구성
networks:
  elastic_net:
    driver: bridge

---배포---

  • 서비스 시작 docker-compose up -d
  • 파일 생성
# 1. kibana 매핑 디렉토리 생성
# 2. elasticsearch 설정 복사
# 3. kibana 설정 복사
mkdir kibana
docker cp elasticsearch-8.8.1:/usr/share/elasticsearch/config ./elasticsearch
docker cp kibana-8.8.1:/usr/share/kibana/config ./kibana

  • elasticsearch 설정 (config/elasticsearch.yml)
# 클러스터 노드 이름
node.name: "elastic01"
# 클러스터 이름 설정
cluster.name: "cluster_elasticsearch"
# 네트워크 접근 제한
network.host: 0.0.0.0
# 단일 노드 모드로 시작
discovery.type: single-node


# CORS 지원 여부
http.cors.enabled: true
# 모든 도메인 허용
http.cors.allow-origin: "*"
# 메모리 스와핑 옵션, 공식 문서 추천값
bootstrap.memory_lock: true

# 보안 설정 수정 인증서 검증 비활성화
xpack.security.http.ssl:
  enabled: false
xpack.security.transport.ssl:
  enabled: false

  • kibana 설정 (kibana.yml)
# 한어
i18n.locale: zh-CN

  • docker-compose.yml 주석 해제
- ./elasticsearch/config:/usr/share/elasticsearch/config
- ./kibana/config:/usr/share/kibana/config
# 컨테이너 재시작
docker compose restart
# 접속 주소:
elastic:http://localhost:9200
kibana:http://localhost:5601

  • elastic 비밀번호 설정
docker exec -it elasticsearch-8.8.1 /usr/share/elasticsearch/bin/elasticsearch-reset-password -uelastic

  • kibana 비밀번호 설정
docker exec -it elasticsearch-8.8.1 /usr/share/elasticsearch/bin/elasticsearch-reset-password -ukibana_system

  • kibana 설정 추가
elasticsearch.username: kibana_system
elasticsearch.password: Ux*7RMHX0ErLEn4=RMmx

  • 인증 코드 획득
docker exec -it kibana-8.8.1 /usr/share/kibana/bin/kibana-verification-code

  • kibana 로그인
elastic
xxxx  elastic 비밀번호

기본 연산

ES8.x:URL 구성

  • /인덱스/문서/내용ID

인덱스 Index

# 인덱스 목록 조회
GET /_cat/indices?v=true&pretty

# 샤드 상황 조회
GET /_cat/shards?v=true&pretty

# 인덱스 생성 (Create Index)
PUT /<index_name>
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  }
}

# 인덱스 존재 여부 확인
HEAD /<index_name>
 
# 인덱스 정보 조회 (Get Index)
GET /<index_name>

# 인덱스 설정 업데이트 (Update Index Settings)
PUT /<index_name>/_settings
{
  "settings": {
    "number_of_replicas": 2
  }
}

# 인덱스 삭제 (Delete Index):
DELETE /<index_name>

문서 Document

# 문서 조회
GET /xdclass_shop/_doc/1
# 문서 추가 (ID 지정)
PUT /xdclass_shop/_doc/1
{
  "id":5555,
  "title":"content111",
  "pv":144
}
# 문서 추가 (ID 미지정, 자동 생성)
POST /xdclass_shop/_doc
{
  "id":123,
  "title":"content222",
  "pv":244
}

# 수정 (put과 post 모두 사용 가능, ID 지정 필요)
PUT /xdclass_shop/_doc/1
{
  "id":999,
  "title":"content111v2",
  "pv":999,
  "uv":55
}

POST /xdclass_shop/_doc/1
{
  "id":999,
  "title":"content222v2",
  "pv":999,
  "uv":559
}

# 검색
GET /xdclass_shop/_search

# 필드 설명
#   took 필드는 해당 작업의 소요 시간(밀리초 단위)을 나타냅니다.
#   timed_out 필드는 시간 초과 여부를 나타냅니다.
#   hits 필드는 검색된 기록을 배열 형태로 나타냅니다.
#   total:반환된 기록 수, 예에서는 1개입니다.
#   max_score:최고 일치 정도, 예에서는 1.0입니다.

# 데이터 삭제
DELETE /xdclass_shop/_doc/1

Mapping과 일반 필드 유형

  • Mapping이란

  • 데이터베이스의 테이블 구조 정의인 schema와 유사합니다,

  • 인덱스의 필드 이름과 데이터 유형(예: 문자열, 숫자, 부울 등)을 정의합니다

  • 인덱스의 필드 유형 조회

# GET /[index]/_mapping
GET /my_index/_mapping

  • 동적 매핑 (Dynamic Mapping)

  • 인덱스 문서 생성 시 자동으로 필드 데이터 유형을 감지하고 정의하는 기능

  • 인덱스에 새 문서를 추가할 때 Elasticsearch는 문서의 각 필드를 자동으로 감지하고 값에 따라 필드 유형을 추론합니다

  • 일반적인 필드 유형에는 텍스트(text), 키워드(keyword), 날짜(date), 숫자(numeric) 등이 있습니다

  • 동적 매핑은 자동으로 필드를 생성하고 분석하는 편의성을 제공하지만, 필드 유형의 불확실성으로 인해 문제가 발생할 수 있습니다

  • 필드 유형이 명확히 요구되는 경우에는 인덱스 생성 전 명시적 매핑 정의를 통해 지정하는 것이 좋습니다

  • Elasticsearch 일반 데이터 유형

  • ES 7.X 이후에는 두 가지 문자열 유형이 있습니다: Text와 Keyword

  • Text 유형: 전체 텍스트 검색을 위한 문자열 유형으로, 토큰화 및 인덱싱을 지원합니다

  • Keyword 유형: 정확한 일치를 위한 문자열 유형으로, 토큰화를 수행하지 않으며 필터링 및 집계 작업에 적합합니다

  • Numeric 유형: 정수 유형(long, integer, short, byte)와 부동 소수점 유형(double, float)을 포함합니다

  • Date 유형: 날짜와 시간을 저장하는 유형입니다

  • Boolean 유형: 불리언 값(true 또는 false)을 저장하는 유형입니다

  • Binary 유형: 이진 데이터를 저장하는 유형입니다

  • Array 유형: 배열 또는 목록 데이터를 저장하는 유형입니다

  • Object 유형: 복잡한 구조의 데이터를 저장하는 유형입니다

  • 인덱스 필드 유형 mapping 지정

PUT /my_index
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "text"
      },
      "price": {
        "type": "float"
      }
    }
  }
}

  • 가장 자주 사용되는 데이터 유형

  • text 필드 유형

  • text 유형은 주로 전체 텍스트 검색에 사용되며, 전체 텍스트 토큰화가 필요한 콘텐츠(예: 기사, 뉴스 등)를 저장하는 데 적합합니다

  • text 필드는 텍스트 콘텐츠를 토큰(tokens)으로 분리하여 인덱스를 생성하고, 이를 통해 유연하고 효율적인 검색이 가능합니다

  • text 필드는 검색 시 토큰화된 결과와 일치시키고 관련성 점수를 계산하여 최적의 일치 결과를 반환합니다

  • keyword 필드 유형

  • keyword 유형은 정확한 일치 및 집계 작업에 주로 사용되며, 토큰화가 필요 없는 정확한 값(예: ID, 태그, 키워드 등)을 저장하는 데 적합합니다

  • keyword 필드는 토큰화를 수행하지 않고 전체 필드를 하나의 단위로 인덱싱하고 검색합니다

  • 이를 통해 검색은 정확한 값만 일치시킬 수 있으며, 토큰을 기반으로 콘텐츠를 모호하게 검색할 수 없습니다

  • keyword 필드는 필터링 및 정확한 일치에 적합하며, 정확한 값을 기반으로 빠른 집계 작업을 수행할 수 있습니다

  • 요약

  • text 필드 유형과 keyword 필드 유형을 선택할 때는 구체적인 요구 사항에 따라 균형을 맞춰야 합니다:

  • 전체 텍스트 검색이 필요하고 토큰화된 결과를 기반으로 관련성 점수를 계산하여 최적의 일치 결과를 얻고자 한다면 text 필드 유형을 선택합니다

  • 정확한 일치, 정렬 또는 집계 작업이 필요하며 콘텐츠를 토큰화할 필요가 없다면 keyword 필드 유형을 선택합니다

  • 사례 실습

  • 인덱스 생성 및 문서 삽입

PUT /my_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      },
      "tags": {
        "type": "keyword"
      },
      "publish_date": {
        "type": "date"
      },
      "rating": {
        "type": "float"
      },
      "is_published": {
        "type": "boolean"
      },
      "author": {
        "properties": {
          "name": {
            "type": "text"
          },
          "age": {
            "type": "integer"
          }
        }
      },
      "comments": {
        "type": "nested",
        "properties": {
          "user": {
            "type": "keyword"
          },
          "message": {
            "type": "text"
          }
        }
      }
    }
  }
}

POST /my_index/_doc/1
{
  "title": "작은 강의실 최근에 새로운 과정 Elasticsearch 소개",
  "tags": ["검색", "빅데이터", "분산 시스템", "작은 강의실"],
  "publish_date": "2025-01-01",
  "rating": 4.5,
  "is_published": true,
  "author": {
    "name": "John Doe",
    "age": 30
  },
  "comments": [
    {
      "user": "Alice",
      "message": "훌륭한 글입니다!"
    },
    {
      "user": "Bob",
      "message": "매우 정보가 많습니다."
    }
  ]
}

  • text 필드 유형 일치 쿼리 (토큰화)
GET /my_index/_search
{
  "query": {
    "match": {
      "title": "Elasticsearch"
    }
  }
}

  • keyword 필드 유형 일치 쿼리 (미토큰화)
GET /my_index/_search
{
  "query": {
    "match": {
      "tags": "빅데이터"
    }
  }
}

토큰화기

  • 검색 엔진의 토큰화란

  • Elasticsearch 8.X에서 토큰화(tokenization)는 텍스트 콘텐츠를 독립적인 단어 또는 토큰(tokens)으로 분리하는 과정입니다

  • 토큰화는 검색 엔진에서 인덱스를 구축하고 쿼리를 실행할 때의 핵심 단계로, 텍스트를 단어로 분리하고 역색인(inverted index)을 구축하여 더 나은 검색 및 검색 효과를 구현합니다

  • 사례

두 가지 제품 제목이 있다고 가정해 봅시다:

"Apple iPhone 12 Pro Max 256GB"

"Samsung Galaxy S21 Ultra 128GB"

기본 표준 토큰화기(Standard Tokenizer)를 사용하면 이 제목들은 다음과 같이 토큰으로 분할됩니다:

제목1:["Apple", "iPhone", "12", "Pro", "Max", "256GB"]

제목2:["Samsung", "Galaxy", "S21", "Ultra", "128GB"]

토큰화기는 구두점과 공백을 기준으로 제목을 독립적인 단어로 분할합니다. 검색을 실행할 때 쿼리를 토큰화하고 이를 제목의 토큰과 일치시킵니다.

예를 들어
  "iPhone 12"를 검색할 때 기본 토큰화기를 사용하면 쿼리가 ["iPhone", "12"]로 분해되고, 이를 토큰과 일치시킵니다.
  제목1의 토큰 ["iPhone", "12"]는 쿼리와 일치합니다. 제목2에는 쿼리와 일치하는 토큰이 없습니다

  • 토큰화 규칙은 텍스트를 분할하는 규칙과 알고리즘을 정의합니다

  • Elasticsearch는 일련의 토큰화기(analyzer)와 토큰 생성기(tokenizer)를 사용하여 텍스트 콘텐츠를 처리합니다

  • 토큰화기는 일반적으로 하나 이상의 토큰 생성기로 구성되어 토큰화의 구체적인 규칙을 정의합니다

  • 다음은 토큰화의 일반적인 과정입니다:

  • 토큰화(Tokenization):

  • 토큰화의 첫 단계는 텍스트 콘텐츠를 개별 토큰으로 분리하는 것입니다. 토큰은 단어, 숫자, 특수 문자 등일 수 있습니다.

  • 토큰화 과정은 토큰 생성기(tokenizer)에 의해 수행되며, 토큰 생성기는 일련의 규칙을 기반으로 텍스트를 토큰으로 분할합니다.

  • 필터링(Filtering):

  • 토큰화 후, 토큰은 추가적으로 필터(filters)에 의해 처리됩니다.

  • 필터는 소문자 변환, 불용어(stop words) 제거, 어간 추출(stemming), 동의어 확장 등 다양한 변환 및 작업을 수행합니다.

  • 역색인(Inverted Indexing):

  • 토큰화 처리가 완료되면, Elasticsearch는 토큰화 결과를 저장하기 위해 역색인(inverted index)을 사용합니다.

  • 역색인은 토큰을 해당 문서와 매핑하여 특정 토큰을 포함하는 문서를 빠르게 식별할 수 있는 데이터 구조입니다.

  • 쿼리 일치:

  • 쿼리를 실행할 때 쿼리 텍스트도 토큰화 처리됩니다.

  • Elasticsearch는 역색인을 사용하여 쿼리 토큰을 포함하는 문서를 빠르게 찾고 관련성 점수를 계산합니다.

  • 일반적인 토큰화기로는 Standard 토큰화기, Simple 토큰화기, Whitespace 토큰화기, IK 토큰화기 등이 있으며, 사용자 정의 토큰화기도 지원합니다

  • 기본 Standard 토큰화기의 토큰화 규칙

  • 구두점 분할:

  • 구두점은 삭제되고 하이픈은 두 개의 독립적인 단어로 분할됩니다.

  • 예를 들어, "Let's go!"는 "Let", "s", "go"로 분할됩니다.

  • 소문자 변환:

  • 모든 텍스트는 소문자 형태로 변환됩니다.

  • 예를 들어, "Hello World"는 "hello", "world"로 분할됩니다.

  • 불용어 필터링:

  • 불용어(stop words)는 검색에서 실제 의미가 없는 일반적인 단어로, "a", "an", "the" 등이 있습니다.

  • 불용어는 필터링되어 독립적인 단어로 인덱싱 및 검색되지 않습니다.

  • 어간 추출:

  • Porter2 어간 추출 알고리즘을 적용하여 단어를 원래 형태로 환원합니다.

  • 예를 들어, running -> run, swimming -> swim, jumped -> jump

  • 단어 분리:

  • 공백을 기준으로 텍스트를 단어 토큰(tokens)으로 분할합니다.

  • ES 토큰화 저장 효과를 어떻게 확인하는가?

  • analyze API를 사용하여 텍스트를 토큰화 처리하고 토큰화 결과를 확인할 수 있으며, 기본 구문은 다음과 같습니다

GET /_analyze
{
  "analyzer": "토큰화기 이름",
  "text": "분석할 텍스트"
}

  • 사례
# 필드가 text 유형인 경우
POST /my_index/_analyze
{
  "field": "title",
  "text": "This is some text to analyze"
}

# 필드가 text 유형인 경우
POST /my_index/_analyze
{
  "field": "title",
  "text": "오늘은 작은 강의실에서 아키텍처 대강의를 배웠습니다"
}

# 필드가 keyword 유형인 경우
POST /my_index/_analyze
{
  "field": "tags",
  "text": "This is some text to analyze"
}

# 필드가 keyword 유형인 경우
POST /my_index/_analyze
{
  "field": "tags",
  "text": ["This is","작은 강의실","Spring Boot" ]
}

  • 각 토큰화 결과 객체에는 다음이 포함됩니다
  • 토큰화된 단어(token)
  • 시작 위치(start_offset)
  • 끝 위치(end_offset)
  • 유형(type)
  • ALPHANUM은 데이터 유형으로, 문자열 필드가 문자와 숫자만 포함하고 다른 토큰화나 처리를 수행하지 않음을 나타냅니다
  • 필드의 모든 비 문자 숫자 문자(예: 구두점, 공백 등)를 무시하고 문자와 숫자 문자만 유지합니다
  • 원본 텍스트에서의 단어 위치(position)

IK 중국어 토큰화기

  • 배경

  • Elasticsearch 8.X에서 토큰화(tokenization)는 텍스트 콘텐츠를 독립적인 단어 또는 토큰(tokens)으로 분리하는 과정입니다

  • 기본 Standard 토큰화기는 중국어 지원이 다소 제한적입니다. 예를 들어

# 필드가 text 유형인 경우
POST /my_index/_analyze
{
  "field": "title",
  "text": "오늘은 작은 강의실에서 spring cloud 프로젝트 대강의를 배웠습니다"
}

# 결과는 다음과 같으며, 중국어는 각자 하나의 단어로 처리됩니다
{
  "tokens": [
    {
      "token": "오",
      "start_offset": 0,
      "end_offset": 1,
      "type": "<IDEOGRAPHIC>",
      "position": 0
    },
    {
      "token": "늘",
    },
    {
      "token": "은",
    },
    ......
    {
      "token": "spring",
      "start_offset": 10,
      "end_offset": 16,
      "type": "<ALPHANUM>",
      "position": 10
    },
    {
      "token": "cloud",
      "start_offset": 17,
      "end_offset": 22,
      "type": "<ALPHANUM>",
      "position": 11
    },
    {
      "token": "프",
      "start_offset": 22,
      "end_offset": 23,
      "type": "<IDEOGRAPHIC>",
      "position": 12
    },
    {
      "token": "로",
      "start_offset": 23,
      "end_offset": 24,
      "type": "<IDEOGRAPHIC>",
      "position": 13
    },
  ]
}

  • IK 토큰화기란

  • Java로 개발된 오픈 소스 중국어 토큰화기로, 중국어 텍스트를 개별 단어(토큰)로 분리하는 데 사용됩니다

  • 중국어 언어의 특성과 요구 사항을 고려하여 설계되었으며, 중국어 토큰화의 복잡성과 다양성을 효과적으로 처리할 수 있습니다

  • 주소

  • 여러 버전: https://github.com/medcl/elasticsearch-analysis-ik/releases

  • 본문에서 사용하는 버전: https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v8.8.1

  • 참고: Elasticsearch 버전과 IK 토큰화기 버전이 일치해야 합니다

  • 특징

  • 효율적이고 유연함

  • IK 토큰화기는 다양한 알고리즘과 데이터 구조를 사용하여 높은 토큰화 속도를 제공합니다.

  • 세밀한 토큰화를 지원하며 애플리케이션 요구에 따라 유연하게 구성할 수 있습니다.

  • 토큰화 정확도

  • IK 토큰화기는 사전과 규칙을 사용하여 토큰화하여 문장을 정확하게 단어로 분할할 수 있습니다.

  • 또한 품사 태깅 기능을 제공하여 단어의 다양한 의미와 용도를 식별하는 데 도움이 됩니다.

  • 원격 사전 확장 지원

  • IK 토큰화기는 구성 및 외부 사전을 로드하여 토큰화 능력을 확장할 수 있습니다

  • 사용자는 실제 요구에 따라 사용자 정의 사전을 추가하여 토큰화 정확성과 커버리지를 향상시킬 수 있습니다.

  • 호환성 및 통합성

  • IK 토큰화기는 Lucene 및 Elasticsearch와 같은 주요 검색 엔진과 호환되어 이러한 시스템에 쉽게 통합할 수 있습니다.

  • 해당 플러그인 및 구성 옵션을 제공하여 토큰화기 통합이 간편해집니다.

  • 설치

  • 압축을 풀고 로컬/elasticsearch/plugins 디렉토리에 배치합니다

  • docker-compose.yml의 마운트 주석을 해제합니다

  • Elasticsearch를 업데이트하면 됩니다

docker compose up -d

  • IK에는 두 가지 세밀도의 분할이 있습니다

  • ik_smart: 가장 거친 세밀도로 분할합니다

  • ik_max_word(일반적으로 사용됨): 텍스트를 가장 세밀하게 분할합니다

  • 사례 실습

GET /_analyze
{
  "text":"오늘은 월요일, 오늘은 작은 강의실에서 spring cloud 프로젝트 대강의를 배웠습니다",
  "analyzer":"ik_smart"
}


GET /_analyze
{
  "text":"오늘은 월요일, 오늘은 작은 강의실에서 spring cloud 프로젝트 대강의를 배웠습니다",
  "analyzer":"ik_max_word"
} 

구문 및 응용

  • Query DSL이란

  • Query DSL(Domain-Specific Language)은 검색 쿼리를 구축하기 위한 강력한 도메인별 쿼리 언어입니다

  • 관계형 데이터베이스의 SQL 쿼리 구문과 유사합니다,

  • Elasticsearch에서 JSON 구조화 방식으로 다양한 쿼리 작업을 정의하고 실행하며, Elasticsearch에서 고급 검색 및 필터링을 수행합니다

  • 기본 구문

GET /인덱스명/_search 
{ 
  "query":{ 
    "쿼리 유형":{
    
    }
}

  • 일반적인 Query DSL 쿼리 문장 및 기능

  • match 쿼리: 전체 텍스트 검색을 실행하며, 지정된 필드의 텍스트와 검색 쿼리를 일치시킵니다

{
  "query": {
    "match": {
      "title": "elasticsearch"
    }
  }
}

  • term 쿼리: 지정된 필드의 키워드를 정확히 일치시키며, 토큰화 처리를 수행하지 않습니다.
{
  "query": {
    "term": {
      "category": "books"
    }
  }
}

  • 요약

  • Query DSL은 더 다양한 종류의 쿼리 및 필터 문장을 제공하여 다양한 검색 요구를 충족합니다.

  • 구체적인 비즈니스 요구 사항과 데이터 구조에 따라 다양한 쿼리 방식을 결합하여 복잡한 검색 및 필터링 작업을 구축할 수 있습니다

  • 데이터 준비

  • 인덱스 생성

PUT /xdclass_shop_v1
{
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 0
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "keyword"
      },
      "summary": {
        "type": "text"
      },
      "price": {
        "type": "float"
      }
    }
  }
}
# 데이터 삽입
PUT /xdclass_shop_v1/_bulk
{ "index": { "_index": "xdclass_shop_v1" } }
{ "id": "1", "title": "Spring Boot","summary":"this is a summary Spring Boot video", "price": 9.99 }
{ "index": { "_index": "xdclass_shop_v1" } }
{ "id": "2", "title": "java","summary":"this is a summary java video", "price": 19.99 }
{ "index": { "_index": "xdclass_shop_v1" } }
{ "id": "3", "title": "Spring Cloud","summary":"this is a summary Spring Cloud video", "price": 29.99 }
{ "index": { "_index": "xdclass_shop_v1" } }
{ "id": "4", "title": "Spring_Boot", "summary":"this is a summary Spring_Boot video","price": 59.99 }
{ "index": { "_index": "xdclass_shop_v1" } }
{ "id": "5", "title": "SpringBoot","summary":"this is a summary SpringBoot video", "price": 0.99 }

match

  • 전체 데이터 쿼리 (match_all)

  • 인덱스의 모든 문서와 일치하는 간단한 쿼리입니다

GET /xdclass_shop_v1/_search
{
  "query": {
    "match_all": {}
  }
}

  • 조건부 데이터 쿼리

  • match는 쿼리 내용을 토큰화한 후 쿼리를 수행하며, 여러 용어 간의 관계는 OR 관계입니다

  • 그 후 문서 내의 토큰과 일치시키며, 일치도가 높을수록 점수가 높고 앞에 표시됩니다

GET /xdclass_shop_v1/_search
{
  "query": {
    "match": {
      "summary": "Spring"
    }
  }
}

# 여러 단어 포함
GET /xdclass_shop_v1/_search
{
  "query": {
    "match": {
      "summary": "Spring Java"
    }
  }
}

  • 전체 키워드 쿼리

  • term 쿼리는 쿼리 조건을 토큰화하지 않고 문서 내의 토큰과 직접 일치시킵니다

  • match 쿼리도 가능하지만 match 쿼리는 토큰화를 추가로 수행하므로 리소스가 낭비됩니다

#keyword 유형 필드, ES는 토큰화를 수행하지 않음
GET /xdclass_shop_v1/_search
{
  "query": {
    "term": {
      "title": {
        "value": "Spring Boot"
      }
    }
  }
}

  • 지정된 필드 가져오기

  • 특정 시나리오에서는 전체 필드 반환을 원하지 않을 수 있으며, 리소스 낭비를 방지하기 위해 _source를 사용하여 해당 필드를 지정할 수 있습니다

GET /xdclass_shop_v1/_search
{
"_source":["price","title"],
  "query": {
    "term": {
      "title": {
        "value": "Spring Boot"
      }
    }
  }
}

  • 요약

  • match는 검색 시 키워드를 토큰화한 후 토큰 일치 검색을 수행하고, term은 키워드를 직접 검색합니다

  • 일반 비즈니스에서 모호한 검색이 필요할 때는 match를 선택하고, 정확한 검색이 필요할 때는 term 쿼리를 선택합니다

불-범위 및 페이지네이션-정렬

  • 일반 Query DSL 구문 사례 실습

  • range 쿼리

  • 범위 조건에 따라 쿼리하며, 예를 들어 가격 특정 구간 내의 상품을 지정합니다

  • 범위 기호

  • gte: 이상

  • gt: 초과

  • lte: 이하

  • lt: 미만

GET /xdclass_shop_v1/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 5,
        "lte": 100
      }
    }
  }
}

  • 페이지네이션 쿼리
  • fromsize 매개변수를 사용하여 페이지네이션 쿼리를 수행할 수 있습니다
  • 건너뛰어야 할 문서 수(from)와 반환해야 할 문서 수(size)를 지정할 수 있습니다
GET /xdclass_shop_v1/_search
{
  "size": 10,
  "from": 0,
  "query": {
    "match_all": {}
  }
}

  • 쿼리 결과 정렬
  • sort 필드를 사용하여 정렬할 수 있으며, descasc가 있습니다
GET /xdclass_shop_v1/_search
{
  "size": 10,
  "from": 0,
  "sort": [
    {
      "price": "asc"
    }
  ],
  "query": {
    "match_all": {}
  }
}

  • bool 쿼리

  • 여러 쿼리 조건을 부울 논리(AND, OR, NOT)로 조합하여 복잡한 쿼리 작업을 수행합니다

  • 구문 형식

  • "must" 키워드는 일치해야 할 조건을 지정하며, 모든 조건을 모두 만족해야 합니다

  • "must_not" 키워드는 일치하지 않아야 할 조건을 지정하며, 모든 조건을 만족해서는 안 됩니다

  • "should" 키워드는 선택적 일치 조건을 지정하며, 최소한 하나의 조건을 만족해야 합니다

{
  "query": {
    "bool": {
      "must": [
        // 반드시 일치해야 할 조건
      ],
      "must_not": [
        // 반드시 일치하지 않아야 할 조건
      ],
      "should": [
        // 선택적 일치 조건
      ],
      "filter": [
        // 필터 조건
      ]
    }
  }
}

  • 사례 실습
GET /xdclass_shop_v1/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "summary": "Cloud" }},
        { "range": { "price": { "gte": 5 }}}
      ]
    }
  }
}

쿼리 필터링 Filter

  • filter 쿼리

  • 검색 결과를 필터링하고 필터링하여 특정 조건을 만족하는 문서만 반환하며, 검색 점수를 변경하지 않습니다

  • Filter 쿼리는 결과를 캐싱하여 쿼리 성능을 향상시키며, 숫자 범위, 날짜 범위, 부울 논리, 존재성 검사 등 다양한 필터링 작업에 사용됩니다.

  • 구문 형식

  • "filter" 키워드는 필터 조건을 지정하며, term, range 등과 같은 구체적인 필터 또는 중첩된 bool 필터일 수 있습니다

{
  "query": {
    "bool": {
      "filter": {
        // 필터 조건
      }
    }
  }
}

  • 쿼리 사례 데이터 환경 준비

  • 인덱스 라이브러리 생성

PUT /product
{
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 0
  },
  "mappings": {
   "properties": {
    "product_id": {
      "type": "integer"
    },
    "product_name": {
      "type": "text"
    },
    "category": {
      "type": "keyword"
    },
    "price": {
      "type": "float"
    },
    "availability": {
      "type": "boolean"
    }
  } }  
}

  • 데이터 삽입

  • 단일 POST 요청을 통해 배치 삽입을 구현합니다

  • 각 문서는 index 지시문과 product_id로 구성되며, product_id는 문서의 고유 식별자입니다

  • 삽입 후 GET /product/_search로 조회

POST /product/_bulk
{ "index": { "_id": "1" } }
{ "product_id": 1, "product_name": "Product 1", "category": "books", "price": 19.99, "availability": true }
{ "index": { "_id": "2" } }
{ "product_id": 2, "product_name": "Product 2", "category": "electronics", "price": 29.99, "availability": true }
{ "index": { "_id": "3" } }
{ "product_id": 3, "product_name": "Product 3", "category": "books", "price": 9.99, "availability": false }
{ "index": { "_id": "4" } }
{ "product_id": 4, "product_name": "Product 4", "category": "electronics", "price": 49.99, "availability": true }
{ "index": { "_id": "5" } }
{ "product_id": 5, "product_name": "Product 5", "category": "fashion", "price": 39.99, "availability": true }

  • 사례 1:term 필터를 사용하여 categorybooks인 제품을 쿼리합니다:
GET /product/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": {
          "category": "books"
        }
      }
    }
  }
}

  • 사례 2:range 필터를 사용하여 가격 price가 30에서 50 사이인 제품을 쿼리합니다:
GET /product/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "price": {
            "gte": 30,
            "lte": 50
          }
        }
      }
    }
  }
}

  • 요약

  • 필터 조건은 일반적으로 결과를 필터링하는 데 사용되며, 쿼리 조건보다 더 효율적입니다

  • bool 쿼리는 구체적인 요구에 따라 여러 조건, 필터 및 쿼리 하위 절을 조합할 수 있습니다

다중 필드 일치 및 구문 검색

  • 다중 필드 검색 일치

  • 비즈니스 쿼리에서 여러 필드에서 텍스트 검색이 필요한 경우, multi_match를 사용합니다

  • match를 기반으로 여러 필드에서 텍스트 쿼리 일치를 지원합니다

  • 구문 형식

GET /index/_search
{
  "query": {
    "multi_match": {
      "query": "검색할 텍스트",
      "fields": ["필드1", "필드2", ...]
    }
  }
}

# query:일치시킬 쿼리 텍스트입니다.
# fields:일치시킬 필드 목록을 포함하는 배열입니다.

  • 구문 검색 일치

  • Elasticsearch에서 제공하는 고급 일치 쿼리 유형으로, 정확한 구문 검색을 수행합니다

  • match 쿼리와 비교하여 match_phrase는 단어 간의 순서와 위치를 고려하여 일치시킵니다

  • 구문 형식

GET /index/_search
{
  "query": {
    "match_phrase": {
      "field_name": {
        "query": "검색할 구문"
      }
    }
  }
}

# field_name:일치시킬 필드 이름입니다.
# query:검색할 구문입니다.

  • 데이터 환경 준비
# 인덱스 라이브러리 생성
PUT /product_v2
{
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 0
  },
  "mappings": {
    "properties": {
      "product_name": {
        "type": "text"
      },
      "description": {
        "type": "text"
      },
      "category": {
        "type": "keyword"
      }
    }
  }
}
#데이터 배치 삽입
POST /product_v2/_bulk
{ "index": { "_index": "product_v2", "_id": "1" } }
{ "product_name": "iPhone 12", "description": "Apple의 최신 iPhone 모델", "category": "electronics" }
{ "index": { "_index": "product_v2", "_id": "2" } }
{ "product_name": "Samsung Galaxy S21", "description": "고성능 Android 스마트폰", "category": "electronics" }
{ "index": { "_index": "product_v2", "_id": "3" } }
{ "product_name": "MacBook Pro", "description": "전문가용 강력한 노트북", "category": "electronics" }
{ "index": { "_index": "product_v2", "_id": "4" } }
{ "product_name": "해리 포터와 마법사의 돌", "description": "J.K. Rowling의 판타지 소설", "category": "books" }
{ "index": { "_index": "product_v2", "_id": "5" } }
{ "product_name": "위대한 게츠비", "description": "F. Scott Fitzgerald의 고전 소설", "category": "books" }

  • 다중 필드 검색 사례 실습

  • product_namedescription 필드에서 multi_match 쿼리를 실행합니다

  • 쿼리 텍스트를 "iPhone"으로 설정하여 이 두 필드를 검색하고, 일치하는 문서를 반환합니다. 이는 OR 관계이며 최적의 일치가 됩니다

GET /product_v2/_search
{
  "query": {
    "multi_match": {
      "query": "iPhone",
      "fields": ["product_name", "description"]
    }
  }
}

  • 구문 검색 사례 실습

  • match_phrase 쿼리를 사용하여 description 필드에서 구문 검색을 수행합니다. 검색할 구문을 "classic novel"로 설정합니다.

  • match_phrase 쿼리를 사용하면 Elasticsearch는 "classic novel" 구문을 포함하는 문서를 반환합니다

#match_phrase 구문 검색
GET /product_v2/_search
{
  "query": {
    "match_phrase": {
      "description": "classic novel"
    }
  }
}

#match 검색, 토큰화를 수행합니다
GET /product_v2/_search
{
  "query": {
    "match": {
      "description": "classic novel"
    }
  }
}

fuzzy 모호 검색

  • fuzzy 모호 일치란

  • fuzzy 쿼리는 Elasticsearch에서 제공하는 모호 일치 쿼리 유형으로, 검색 시 일부 오타 또는 근접 일치를 허용합니다

  • fuzzy 쿼리를 사용하면 지정된 편집 거리(즉, 단어 간의 다른 문자 수)에 따라 쿼리 단어를 모호하게 일치시킬 수 있습니다

  • 확장: 편집 거리

  • 한 용어를 다른 용어로 변환하기 위해 필요한 문자 변경 횟수입니다.

  • 예를 들어

  • 문자 변경(box→fox)

  • 문자 삭제(black→lack)

  • 문자 삽입(sic→sick)

  • 인접한 두 문자 전환(dgo→dog)

  • fuzzy 모호 검색은 오타에 대한 간단한 해결책이지만, CPU 오버헤드가 매우 높고 정확도가 매우 낮습니다

  • 사용법은 match와 기본적으로 동일하며, Fuzzy 쿼리는 토큰화를 수행하지 않습니다

  • 기본 구문 형식

GET /index/_search
{
  "query": {
    "fuzzy": {
      "field_name": {
        "value": "검색할 단어",
        "fuzziness": "모호도"
      }
    }
  }
}

  • 설명

  • field_name:모호 일치를 수행할 필드 이름입니다.

  • value:검색할 단어

  • fuzziness 매개변수는 모호도를 지정하며, 일반적인 값은 다음과 같습니다

  • 0, 1, 2

  • 숫자를 지정하면 허용되는 최대 편집 거리를 나타내며, 낮은 숫자는 더 엄격한 일치를 의미하고, 높은 숫자는 더 느슨한 일치를 의미합니다

  • fuzziness 값은 각 단어에 대한 것이며, 전체 오류 수가 아닙니다

  • AUTO:Elasticsearch가 단어의 길이에 따라 자동으로 모호도를 선택합니다

  • 문자열 길이가 5보다 크면 fuzziness 값이 자동으로 2로 설정됩니다

  • 문자열 길이가 2보다 작으면 fuzziness 값이 자동으로 0으로 설정됩니다

  • 사례 작업

# 모호도 2 지정, 더 느슨한 일치
GET /xdclass_shop_v1/_search
{
  "query": {
    "fuzzy": {
      "summary": {
        "value": "clo",
        "fuzziness": "2"
      }
    }
  }
}

# 모호도 1 지정, 더 엄격한 일치
GET /xdclass_shop_v1/_search
{
  "query": {
    "fuzzy": {
      "summary": {
        "value": "clo",
        "fuzziness": "1"
      }
    }
  }
}

# 자동 검사 사용, 1개 단어 오타
GET /xdclass_shop_v1/_search
{
  "query": {
    "fuzzy": {
      "summary": {
        "value": "Sprina",
        "fuzziness": "auto"
      }
    }
  }
}

검색 하이라이트 표시

  • 요구 사항

  • 일반적으로 제품을 검색할 때 키워드가 다른 색상으로 표시되어 사용자가 직관적으로 차이를 볼 수 있습니다

  • Elastic Search 검색 엔진이 하이라이트 표시를 구현하는 방법

  • ES에서 하이라이트 구문은 검색 결과에서 쿼리와 일치하는 키워드를 강조 표시하는 데 사용됩니다

  • 하이라이트 표시는 일반적으로 <em> 또는 다른 HTML 태그로 일치하는 텍스트를 감싸서 구현됩니다

  • 기본 사용법: highlight 안에 하이라이트 표시할 필드를 작성하며, 여러 개를 작성할 수 있습니다

  • 사례 실습

  • 환경 및 데이터 준비

#인덱스 라이브러리 생성
PUT /xdclass_high_light_test
{
  "mappings": {
    "properties": {
      "title": {
          "type": "text",
          "analyzer": "ik_max_word"
        },
        "content": {
          "type": "text",
          "analyzer": "ik_max_word"
        }
     }
  }, 
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 0
  }
}
#데이터 삽입
PUT /xdclass_high_light_test/_doc/1
{
  "title": "작은 강의실 2028년 최신 추천 영화",
  "content": "매년 새로운 영화가 상영되며, 2028년 최신 추천 영화는 많습니다. 작은 강의실에서 《아키텍처 대강의》, 《로코드 플랫폼》, 《왕님의 과거》 등 훌륭한 영화가 상영됩니다"
}


PUT /xdclass_high_light_test/_doc/2
{
  "title": "좋아하는 영화를 적어보세요",
  "content": "모두가 많은 영화를 보았으며, 최근 10년간 본 비교적 좋은 영화를 말해보세요. 예를 들어 《아키텍처 대강의》, 《대용량 데이터 프로젝트 대강의》, 《빙빙과 왕님의 이야기》 등"
}

  • 단일 조건 쿼리 하이라이트 표시
GET /xdclass_high_light_test/_search 
{
  "query": {
    "match": {
      "content": "영화"
    }
  },
  "highlight": {
    "fields": {
      "content": {}
    }
  }
}

  • 조합 다중 조건 쿼리, highlight 안에 하이라이트 표시할 필드 작성
GET /xdclass_high_light_test/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": "강의실"
          }
        },
        {
          "match": {
            "content": "왕님"
          }
        }
      ]
    }
  },
  "highlight": {
    "fields": {
      "title": {},
      "content": {}
    }
  }
}

  • match 쿼리, highlight 속성을 사용하여 속성을 추가하고 하이라이트 스타일을 수정할 수 있습니다

  • pre_tags: 접두 태그

  • post_tags: 접미 태그

  • fields: 하이라이트 표시할 필드

  • 사례 실습

GET /xdclass_high_light_test/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": "강의실"
          }
        },
        {
          "match": {
            "content": "왕님"
          }
        }
      ]
    }
  },
  "highlight": {
    "pre_tags": "<font color='yellow'>",
    "post_tags": "</font>",
    "fields": [{"title":{}},{"content":{}}]
  } 
}

검색 집계 쿼리

  • 집계 쿼리란

  • 대량의 데이터에 대한 집계 통계 처리로, MySQL 데이터베이스 작업의 group by 그룹화, sum, avg, max 등 함수 처리와 유사합니다

  • Elasticsearch의 강력한 기능 중 하나로, 데이터를 그룹화, 필터링, 계산 및 통계하여 데이터 세트에 대한 정보를 추출하고 데이터 분석을 수행합니다

  • 데이터 시각화 대시보드의 파이 차트, 막대 차트, 선 차트, 대시보드 데이터 등은 모두 집계 쿼리의 핵심 응용입니다

  • 용어 1:데이터 세트에 대한 최대, 최소, 합계, 평균 등 지표의 집계를 지표 집계 metric라고 합니다

  • 기본 구문 형식은 다음과 같습니다

GET /index/_search
{
  "size": 0,
  "aggs": {
    "aggregation_name": {
      "aggregation_type": {
        "aggregation_field": "field_name"
        // 선택적 매개변수
      }
    }
    // 더 많은 집계를 추가할 수 있습니다
  }
}

# 설명
index:집계 쿼리를 실행할 인덱스 이름입니다.
size: 0으로 설정하여 집계 결과만 반환하고 실제 검색 결과는 반환하지 않습니다. 여기서 hits를 0으로 변경하면 원본 데이터가 0으로 변경됩니다
aggs:집계 작업을 지정하는 컨테이너입니다.

aggregation_name:집계 이름, 사용자 정의할 수 있습니다.
aggregation_type:집계 작업 유형, 예를 들어 terms, avg, sum 등입니다.
aggregation_field:집계 작업의 대상 필드, 어떤 필드에 대해 집계를 수행할지

  • 용어 2:데이터 세트를 그룹화 group by한 후 그룹에서 지표 집계를 수행하는 ES에서는 버킷, 버킷 집계 bucketing이라고 합니다

  • 기본 구문 형식은 다음과 같습니다(단순히 알아둡니다, 나중에 더 자세히 설명)

GET /index/_search
{
  "size": 0,
  "aggs": {
    "aggregation_name": {
      "bucket_type": {
        "bucket_options": {
          "bucket_option_name": "bucket_option_value",
          ...
        },
        "aggs": {
          "sub_aggregation_name": {
            "sub_aggregation_type": {
              "sub_aggregation_options": {
                "sub_aggregation_option_name": "sub_aggregation_option_value",
                ...
              }
            }
          }
        }
      }
    }
  }
}
#설명
index: 집계 쿼리를 실행할 인덱스 이름으로 대체합니다.
aggregation_name: 사용자 정의 집계 이름으로 대체합니다.
bucket_type: 특정 버킷 집계 유형(예: terms, date_histogram, range 등)으로 대체합니다.
bucket_option_name 및 bucket_option_value: 특정 버킷 집계 옵션의 이름과 값으로 대체합니다.

sub_aggregation_name: 하위 집계의 이름으로 대체합니다.
sub_aggregation_type: 특정 하위 집계 유형(예: sum, avg, max, min 등)으로 대체합니다.
sub_aggregation_option_name 및 sub_aggregation_option_value: 특정 하위 집계 옵션의 이름과 값으로 대체합니다

  • 일반적인 집계 용도 및 응용 사례 사례

  • 지표 집계(Aggregation Metrics):

  • Avg Aggregation:문서 필드의 평균값을 계산합니다.

  • Sum Aggregation:문서 필드의 합계를 계산합니다.

  • Min Aggregation:문서 필드의 최소값을 찾습니다.

  • Max Aggregation:문서 필드의 최대값을 찾습니다.

  • 버킷 집계(Aggregation Buckets):

  • Terms Aggregation:필드 값을 기준으로 문서를 다른 버킷으로 그룹화합니다.

  • Date Histogram Aggregation:날짜/시간 필드를 기준으로 시간 간격의 버킷을 생성합니다.

  • Range Aggregation:필드 값의 범위를 기준으로 버킷을 생성합니다.

  • 중첩 집계(Nested Aggregations)、집계 필터링(Aggregation Filtering) 등...

  • 사례 실습

# 인덱스 생성
PUT /sales
{
  "mappings": {
    "properties": {
      "product": {
        "type": "keyword"
      },
      "sales": {
        "type": "integer"
      }
    }
  }
}

# 배치 데이터 삽입
POST /sales/_bulk
{"index": {}}
{"product": "iPhone", "sales": 4}
{"index": {}}
{"product": "Samsung", "sales": 60}
{"index": {}}
{"product": "iPhone", "sales": 100}
{"index": {}}
{"product": "Samsung", "sales": 80}
{"index": {}}
{"product": "작은 폰", "sales": 50}
{"index": {}}
{"product": "작은 폰", "sales": 5000}
{"index": {}}
{"product": "작은 폰", "sales": 200}

  • 집계 쿼리를 실행하여 각 상품 이름(product)을 기준으로 그룹화합니다
GET /sales/_search
{
  "aggs":{//집계 작업
    "product_group":{//이름, 임의로 지정
      "terms":{//그룹화
        "field":"product"//그룹화 필드
      }
    }
  }
}

  • 각 그룹의 판매 총액을 계산하며, terms 집계와 sum 집계를 사용하여 구현합니다
  • 쿼리 결과는 각 제품의 이름과 판매 총액을 반환합니다
GET /sales/_search
{
  "size": 0,
  "aggs": {
    "product_sales": {
      "terms": {
        "field": "product"
      },
      "aggs": {
        "total_sales": {
          "sum": {
            "field": "sales"
          }
        }
      }
    }
  }
}

지표metric 집계

  • 지표 집계란

  • 데이터 세트에 대한 최대, 최소, 합계, 평균 등 지표의 집계를 지표 집계 metric라고 합니다

  • max, min, avg, sum 등 함수 사용

  • 사례 실습

  • 집계 쿼리 max 적용 사례:

  • 데이터 준비:이커머스 웹사이트의 판매 기록 인덱스를 가정합니다. 제품 이름과 판매 가격 필드가 포함됩니다

POST /sales_v1/_doc
{ "product_name": "핸드폰", "price": 1000 }

POST /sales_v1/_doc
{ "product_name": "TV", "price": 1500 }

POST /sales_v1/_doc
{ "product_name": "작은 강의실 왕님의 검은 스타킹", "price": 4500 }

  • 사례 설명:max 집계 쿼리를 사용하여 제품 가격의 최고값을 가져옵니다.
GET /sales_v1/_search
{
  "size": 0,
  "aggs": {
    "max_price": {
      "max": {
        "field": "price"
      }
    }
  }
}

  • 집계 쿼리 - min 적용 사례:

  • 데이터 준비:학생 시험 점수 인덱스를 가정합니다. 학생 이름과 시험 점수 필드가 포함됩니다.

POST /exam_scores/_doc
{ "student_name": "작은 강의실-대소", "score" : 80 }

POST /exam_scores/_doc
{ "student_name": "왕님", "score" : 90 }

POST /exam_scores/_doc
{ "student_name": "작은 강의실-D님", "score" : 40 }

  • 사례 설명:min 집계 쿼리를 사용하여 학생의 최저 시험 점수를 가져옵니다.
GET /exam_scores/_search
{
  "size": 0,
  "aggs": {
    "min_score": {
      "min": {
        "field": "score"
      }
    }
  }
}

  • 집계 쿼리 - avg 적용 사례:

  • 데이터 준비(동일):학생 시험 점수 인덱스를 가정합니다. 학생 이름과 시험 점수 필드가 포함됩니다.

  • avg 집계 쿼리를 사용하여 학생의 평균 시험 점수를 계산합니다

GET /exam_scores/_search
{
  "size": 0,
  "aggs": {
    "avg_score": {
      "avg": {
        "field": "score"
      }
    }
  }
}

  • 집계 쿼리 - sum 적용 사례:

  • 데이터 준비:이커머스 웹사이트의 판매 기록 인덱스를 가정합니다. 제품 이름과 판매 수량 필드가 포함됩니다.

POST /sales_order/_doc
{ "product_name": "핸드폰", "sales_count" : 100 }

POST /sales_order/_doc
{ "product_name": "TV", "sales_count" : 50 }

POST /sales_order/_doc
{ "product_name": "작은 강의실 영구 회원권", "sales_count" : 999 }

  • 사례 설명:sum 집계 쿼리를 사용하여 판매 기록의 총 판매 수량을 계산합니다.
GET /sales_order/_search
{
  "size": 0,
  "aggs": {
    "total_sales": {
      "sum": {
        "field": "sales_count"
      }
    }
  }
}

버킷 집구문법 및 Terms

  • 버킷bucket 집계란

  • 데이터 세트를 그룹화 group by한 후 그룹에서 지표 집계를 수행하는 ES에서는버킷, 버킷 집계 bucketing이라고 합니다

  • 기본 구문 형식은 다음과 같습니다

GET /index/_search
{
  "size": 0,
  "aggs": {
    "aggregation_name": {
      "bucket_type": {
        "bucket_options": {
          "bucket_option_name": "bucket_option_value",
          ...
        },
        "aggs": {
          "sub_aggregation_name": {
            "sub_aggregation_type": {
              "sub_aggregation_options": {
                "sub_aggregation_option_name": "sub_aggregation_option_value",
                ...
              }
            }
          }
        }
      }
    }
  }
}
#설명
index: 집계 쿼리를 실행할 인덱스 이름입니다.
aggregation_name: 사용자 정의 집계 이름입니다.
bucket_type: 특정 버킷 집계 유형(예: terms, date_histogram, range 등)입니다.
bucket_option_name 및 bucket_option_value: 특정 버킷 집계 옵션의 이름과 값입니다.

sub_aggregation_name: 하위 집계의 이름입니다.
sub_aggregation_type: 특정 하위 집계 유형(예: sum, avg, max, min 등)입니다.
sub_aggregation_option_name 및 sub_aggregation_option_value: 특정 하위 집계 옵션의 이름과 값입니다

  • 사례 실습

  • 버킷 집계 쿼리 - Terms 사례:

  • 데이터 준비:온라인 서점의 도서 판매 기록 인덱스를 가정합니다. 도서 이름과 판매 수량 필드가 포함됩니다.

#인덱스 라이브러리 생성
PUT /book_sales
{
  "mappings": {
    "properties": {
      "book_title": {
          "type": "keyword"
        },
        "sales_count": {
          "type": "integer"
        }
     }
  }, 
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 0
  }
}

# 배치 데이터 삽입
POST /book_sales/_bulk
{ "index": {} }
{ "book_title": "Elasticsearch in Action", "sales_count" : 100 }
{ "index": {} }
{ "book_title": "작은 강의실 마이크로서비스 최적 실천", "sales_count" : 50 }
{ "index": {} }
{ "book_title": "대용량 데이터 프로젝트 대강의", "sales_count" : 80 }
{ "index": {} }
{ "book_title": "작은 강의실 면접 보급서", "sales_count" : 120 }
{ "index": {} }
{ "book_title": "자료 구조와 알고리즘의 아름다움", "sales_count" : 90 }
{ "index": {} }
{ "book_title": "Python 프로그래밍 빠른 시작", "sales_count" : 70 }
{ "index": {} }
{ "book_title": "작은 강의실 면접 보급서", "sales_count" : 110 }
{ "index": {} }
{ "book_title": "작은 강의실 Java 핵심 기술", "sales_count" : 200 }
{ "index": {} }
{ "book_title": "컴퓨터 시스템 깊이 이해", "sales_count" : 150 }
{ "index": {} }
{ "book_title": "작은 강의실 Java 핵심 기술", "sales_count" : 80 }

  • 사례 설명:terms 집계 쿼리를 사용하여 도서를 판매 수량에 따라 버킷으로 분할하고 각 버킷 내의 판매 수량 합계를 가져옵니다.
GET /book_sales/_search
{
  "size": 0,
  "aggs": {
    "book_buckets": {
      "terms": {
        "field": "book_title",
        "size": 10
      },
      "aggs": {
        "total_sales": {
          "sum": {
            "field": "sales_count"
          }
        }
      }
    }
  }
}

버킷 집계 Date Histogram

  • 버킷 집계 쿼리 - Date Histogram

  • 날짜 유형 필드를 고정된 시간 간격으로 버킷화하고 각 시간 간격 내의 문서에 대해 추가 작업 및 계산을 수행합니다

  • 기본 구문은 다음과 같습니다

GET /index/_search
{
  "size": 0,
  "aggs": {
    "date_histogram_name": {
      "date_histogram": {
        "field": "date_field_name",
        "interval": "interval_expression"
      },
      "aggs": {
        "sub_aggregation": {
          "sub_aggregation_type": {}
        }
      }
    }
  }
}

#설명
index:집계 쿼리를 실행할 인덱스 이름입니다.
date_histogram_name:사용자 정의 date_histogram 집계 이름입니다.
date_field_name:집계할 날짜 유형 필드 이름입니다.
interval_expression:버킷화할 시간 간격을 지정합니다. 시간 간격은 유효한 날짜 형식(예: 1d, 1w, 1M) 또는 숫자와 시간 단위의 조합(예: 7d는 7일, 1h는 1시간)일 수 있습니다.
sub_aggregation:각 날짜 버킷 내에서 수행하는 하위 집계 작업을 지정합니다.
sub_aggregation_type:개별 하위 집계 작업의 유형으로, 모든 유효한 하위 집계 유형일 수 있습니다.

  • 데이터 준비:이커머스 웹사이트의 주문 인덱스로, 주문 날짜와 주문 금액 필드가 포함됩니다.
POST /order_history/_bulk
{ "index": {} }
{ "order_date": "2025-01-01", "amount" : 100 ,"book_title": "작은 강의실 Java 핵심 기술"}
{ "index": {} }
{ "order_date": "2025-02-05", "amount" : 150, "book_title": "작은 강의실 면접 보급서" }
{ "index": {} }
{ "order_date": "2025-03-02", "amount" : 500 ,"book_title": "작은 강의실 Java 핵심 기술"}
{ "index": {} }
{ "order_date": "2025-05-02", "amount" : 250 , "book_title": "작은 강의실 면접 보급서"}
{ "index": {} }
{ "order_date": "2025-05-05", "amount" : 10 ,"book_title": "작은 강의실 마이크로서비스 최적 실천"}
{ "index": {} }
{ "order_date": "2025-02-18", "amount" : 290 , "book_title": "작은 강의실 마이크로서비스 최적 실천"}

  • 사례 설명:date_histogram 집계 쿼리를 사용하여 주문을 날짜별로 버킷화하고 각 버킷 내의 주문 금액 합계를 계산합니다.
GET /order_history/_search
{
  "size": 0,
  "aggs": {
    "sales_per_month": {
      "date_histogram": {
        "field": "order_date",
        "calendar_interval": "month",
        "format": "yyyy-MM"
      },
      "aggs": {
        "total_sales": {
          "sum": {
            "field": "amount"
          }
        }
      }
    }
  }
}

버킷 집계 Range

  • 버킷 집계 쿼리 - Range

  • 필드 값을 다른 범위로 나누고 각 범위 내의 문서를 해당 버킷에 할당하여 이러한 범위에 대해 다양한 작업 및 계산을 수행합니다.

  • 구문 소개

GET /index/_search
{
  "size": 0,
  "aggs": {
    "range_name": {
      "range": {
        "field": "field_name",
        "ranges": [
          { "key": "range_key_1", "from": from_value_1, "to": to_value_1 },
          { "key": "range_key_2", "from": from_value_2, "to": to_value_2 },
          ...
        ]
      },
      "aggs": {
        "sub_aggregation": {
          "sub_aggregation_type": {}
        }
      }
    }
  }
}

#설명
index:집계 쿼리를 실행할 인덱스 이름입니다.
range_name:사용자 정의 range 집계 이름입니다.
field_name:집계할 필드 이름입니다.
ranges:범위 배열을 지정하며, 각 범위는 key, from 및 to 매개변수를 사용하여 정의합니다.
key:범위의 고유 식별자입니다.
from:범위의 시작 값(포함).
to:범위의 끝 값(미포함).
sub_aggregation:각 범위 내에서 수행하는 하위 집계 작업을 지정합니다.
sub_aggregation_type:개별 하위 집계 작업의 유형으로, 모든 유효한 하위 집계 유형일 수 있습니다.

  • 데이터 준비:온라인 상점의 제품 인덱스로, 제품 이름과 가격 필드가 포함됩니다
POST /product_v4/_bulk
{ "index": {} }
{ "product_name": "작은 강의실 영구 회원권", "price" : 2000 }
{ "index": {} }
{ "product_name": "JVM 주제 과정", "price" : 200 }
{ "index": {} }
{ "product_name": "SpringBoot3.X 최적 실천", "price" : 300 }
{ "index": {} }
{ "product_name": "고동시 프로젝트 대강의", "price" : 1500 }
{ "index": {} }
{ "product_name": "대용량 데이터 프로젝트 대강의", "price" : 4120 }
{ "index": {} }
{ "product_name": "모니터링 경보 Prometheus 최적 실천", "price" : 180 }
{ "index": {} }
{ "product_name": "풀스택 엔지니어 학습 경로", "price" : 250 }
{ "index": {} }
{ "product_name": "자동화 테스트 플랫폼 대강의", "price" : 4770 }
{ "index": {} }
{ "product_name": "작은 강의실-왕님 헤어짐 최적 실천", "price" : 400 }
{ "index": {} }
{ "product_name": "작은 강의실-대소 마사지 이야기", "price" : 150 }

  • 사례 설명:range 집계 쿼리를 사용하여 제품을 가격 범위로 버킷화하고 각 버킷 내의 제품 수량을 계산합니다.

  • key를 작성하지 않으면 기본으로 생성됩니다

GET /product_v4/_search
{
  "size": 0,
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 100 },
          { "from": 100, "to": 200 },
          { "from": 200 }
        ]
      },
      "aggs":{
        "total_price":{
          "sum":{
           "field":"price"
          }
        }
      }
    }
  }
}

스프링부트 3.X 통합 ES

  • ElasticSearch는 검색 엔진으로, 서버 측 프로그램으로 HTTP Restful 인터페이스를 제공하여 연결합니다

  • 따라서 여러 다른 언어로 쉽게 ES 검색 기능에 연결할 수 있습니다

  • ES 공식은 Java 클라이언트를 통해 ES에 연결하며, 두 가지 유형으로 나뉩니다

  • 이전 버전의 ES는 TransportClient(7.0 버전에서 사용 중지 표시)를 사용합니다

  • Java Low Level REST Client(계속 유지 관리)

  • 저 수준 REST 클라이언트를 기반으로 HTTP 요청을 Elasticsearch로 보내 통신합니다.

  • 사용자가 직접 조합한 문자열이며, 반환 결과를 직접 파싱합니다; 모든 Elasticsearch 버전과 호환됩니다

  • Java High Level REST Client(7.1 버전에서 사용 중지 표시)

  • 저 수준 REST 클라이언트를 기반으로 더 높은 수준의 추상화를 제공하여 Elasticsearch와의 상호작용을 단순화합니다.

  • 더 쉬운 API를 제공하며, 저수준 요청 및 응답 처리 논리를 캡슐화하고 더 친숙하고 가독성이 높은 코드를 제공합니다.

  • JSON 데이터의 직렬화 및 역직렬화를 자동으로 처리하며, 인덱싱, 검색, 집계 등 대부분의 일반 작업에 적합합니다.

  • 더 복잡한 고급 기능 및 사용자 정의 작업의 경우 저 수준 REST 클라이언트 또는 원본 Elasticsearch REST API를 사용해야 할 수 있습니다

  • Java API Client(8.X 버전부터 권장 사용)

  • Elasticsearch 7.1 버전 이전에 사용된 Java 클라이언트는 Java REST Client입니다

  • 7.1 버전부터 Elasticsearch 공식은 Java REST Client를 폐지(deprecated) 표시하고 새 버전 Java 클라이언트인 Java API Client를 권장합니다

  • 새 버전의 Java API Client는 Elasticsearch 서버와 통신하기 위한 Java 클라이언트 라이브러리입니다

  • 저수준 Transport 통신을 캡슐화하고 동기 및 비동기 호출, 스트림 및 함수형 호출 방법을 제공합니다

  • 공식 문서 주소

  • https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/8.5/introduction.html

통합(springData)

  • Spring Data의 표준화된 데이터 액세스 기술을 기반으로 Elasticsearch 통합을 단순화합니다.

  • 풍부한 CRUD 작업 및 쿼리 방법을 제공하며 데이터 액세스를 단순화하고 자동화된 인덱스 관리 및 매핑을 포함합니다

  • Spring Data Elasticsearch는 일부 고급 기능 및 복잡한 쿼리에 대해 유연성이 부족하여 추가 사용자 정의 처리가 필요할 수 있습니다

  • Spring Data 프레임워크란

  • 데이터 액세스 및 지속성 개발을 단순화하기 위한 프레임워크로, 일관된 API 및 추상화 세트를 제공합니다

  • 다양한 데이터 저장 기술(예: 관계형 데이터베이스, NoSQL 데이터베이스, Elasticsearch 등)과 상호작용을 훨씬 쉽게 만듭니다

  • 공식 사이트:https://spring.io/projects/spring-data

  • springBoot3.x와 SpringData 프레임워크 통합

  • 의존성

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

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0-M3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>17</java.version>
    </properties>
        <dependencies>
        <!--spring boot and web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--spring boot test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--테스트 구성 요소-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--mqtt-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.integration</groupId>-->
<!--            <artifactId>spring-integration-mqtt</artifactId>-->
<!--        </dependency>-->
        <!--spring data elasticsearch-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

  • 구성
spring.elasticsearch.uris=http://127.0.0.1:9200
spring.elasticsearch.username=elastic
spring.elasticsearch.password=hXtO*Lzi2GGJ5wUmUA2c

인덱스 라이브러리 작업

  • ElasticsearchTemplate란

  • Spring Data Elasticsearch가 제공하는 핵심 클래스로, ElasticsearchClient의 구체적인 구현입니다

  • Spring Boot에서 데이터의 저장, 조회 및 쿼리를 위해 Elasticsearch를 조작하는 데 사용됩니다

  • 문서 저장, 업데이트, 삭제 및 쿼리, 집계 작업 등을 수행하기 위한 메서드 세트를 제공합니다

  • ElasticsearchTemplate의 일반적인 메서드

  • save(Object): 객체를 Elasticsearch에 저장합니다.

  • index(IndexQuery): IndexQuery 객체를 사용하여 인덱싱 작업을 수행합니다.

  • delete(String, String): 지정된 인덱스 및 유형의 문서를 삭제합니다.

  • get(String, String): 지정된 인덱스 및 유형의 문서를 가져옵니다.

  • update(UpdateQuery): UpdateQuery 객체를 사용하여 업데이트 작업을 수행합니다.

  • search(SearchQuery, Class): 검색 쿼리를 실행하고 결과를 지정된 유형의 객체로 매핑합니다.

  • count(SearchQuery, Class): 검색 쿼리를 실행하고 결과의 카운트를 반환합니다

  • ElasticsearchTemplate 일반 주석 구성(Spring Data Elasticsearch에 해당)

  • @Id 주 키 지정

  • @Document 엔티티 클래스와 인덱스 대응 관계 지정

indexName:인덱스 이름

  • @Field 일반 속성 지정
type Elasticsearch에서 속성 유형에 해당하며, FieldType 열거형을 사용하여 빠르게 가져옵니다.

text 유형은 토큰화 가능

keywords는 토큰화 불가능

index 검색 조건으로 사용할 경우 index는 반드시 true여야 함

analyzer 토큰화기 유형 지정.

  • DTO
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "video")
public class VideoDTO {
    @Id
    @Field(type = FieldType.Text, index = false)
    private Long id;
    @Field(type = FieldType.Text)
    private String title;

    @Field(type = FieldType.Text)
    private String description;

    @Field(type = FieldType.Keyword)
    private String category;

    @Field(type = FieldType.Integer)
    private Integer duration;

    @Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)
    private LocalDateTime createTime;
    
    public VideoDTO(Long id, String title, String description, Integer duration,String category) {
        this.id = id;
        this.title = title;
        this.description = description;
        this.duration = duration;
        this.createTime = LocalDateTime.now();
        this.category = category;
    }
	}
}

  • 테스트
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class EsTest {
    @Autowired
    private ElasticsearchTemplate restTemplate;
    /**
     * 인덱스 존재 여부 확인
     */
    @Test
    public void existsIndex() {
        IndexOperations indexOperations = restTemplate.indexOps(VideoDTO.class);
        boolean exists = indexOperations.exists();
        System.out.println(exists);
    }

    /**
     * 인덱스 생성
     */
    @Test
    public void createIndex() {
        // spring data es 모든 인덱스 작업은 이 인터페이스에 있음
        IndexOperations indexOperations = restTemplate.indexOps(VideoDTO.class);
        // 존재하면 삭제
        if(indexOperations.exists()){
            indexOperations.delete();
        }

        // 인덱스 생성
        indexOperations.create();
        log.info("테스트--인덱스 생성 성공");

        //매핑 설정: 실제 개발에서는 거의 프레임워크를 사용하여 인덱스를 생성하거나 매핑을 설정하지 않으며, 이는 아키텍처 또는 관리자의 작업이며 코드 구현에 적합하지 않습니다
        restTemplate.indexOps(VideoDTO.class).putMapping();
    }

    /**
     * 인덱스 삭제
     */
    @Test
    public void deleteIndex() {
        IndexOperations indexOperations = restTemplate.indexOps(VideoDTO.class);
        boolean delete = indexOperations.delete();
        System.out.println(delete);
    }

    /**
     * 추가
     */
    @Test
    public void insert(){
        VideoDTO videoDTO = new VideoDTO();
        videoDTO.setId(1L);
        videoDTO.setTitle("작은 강의실 아키텍처 대강의와 Spring Cloud");
        videoDTO.setCreateTime(LocalDateTime.now());
        videoDTO.setDuration(100);
        videoDTO.setCategory("백엔드");
        videoDTO.setDescription("이것은 종합 대형 과정으로 jvm, redis, 신버전 spring boot3.x, 아키텍처, 모니터링, 성능 최적화, 알고리즘, 고동시 등 다양한 내용을 포함합니다");

        VideoDTO saved = restTemplate.save(videoDTO);
        System.out.println(saved);
    }

    /**
     * 업데이트
     */
    @Test
    public void update(){
        VideoDTO videoDTO = new VideoDTO();
        videoDTO.setId(1L);
        videoDTO.setTitle("작은 강의실 아키텍처 대강의와 Spring Cloud V2");
        videoDTO.setCreateTime(LocalDateTime.now());
        videoDTO.setDuration(102);
        videoDTO.setCategory("백엔드");
        videoDTO.setDescription("이것은 종합 대형 과정으로 jvm, redis, 신버전 spring boot3.x, 아키텍처, 모니터링, 성능 최적화, 알고리즘, 고동시 등 다양한 내용을 포함합니다");

        VideoDTO saved = restTemplate.save(videoDTO);
        System.out.println(saved);
    }

    /**
     * 배치 추가
     */
    @Test
    public void batchInsert() {
        List<VideoDTO> list = new ArrayList<>();
        list.add(new VideoDTO(2L, "왕님이 녹화한 마사지 과정", "주로 마사지와 클럽 추천", 123, "백엔드"));
        list.add(new VideoDTO(3L, "빙빙의 프론트엔드 성능 최적화", "프론트엔드 고수 시리즈", 100042, "프론트엔드"));
        list.add(new VideoDTO(4L, "대용량 데이터 프로젝트 대강의", "D님의 백엔드 + 빅데이터 종합 과정", 5432345, "백엔드"));
        list.add(new VideoDTO(5L, "작은 강의실 영구 회원권", "대량의 주제 과정을 볼 수 있으며, IT 기술 지속적 충전 플랫폼", 6542, "백엔드"));
        list.add(new VideoDTO(6L, "대소-프론트엔드 로코드 플랫폼", "고효율 개발底层 기반 플랫폼, 효율 플랫폼 사례", 53422, "프론트엔드"));
        list.add(new VideoDTO(7L, "자동화 테스트 플랫폼 대강의", "마이크로서비스 아키텍처 하의 spring cloud 아키텍처 대강의, jvm, 효율 플랫폼 포함", 6542, "백엔드"));


        Iterable<VideoDTO> result = restTemplate.save(list);
        System.out.println(result);
    }

    /**
     * 주 키로 검색
     */
    @Test
    public void  searchById(){
        VideoDTO videoDTO = restTemplate.get("3", VideoDTO.class);
        assert videoDTO != null;
        System.out.println(videoDTO);
    }

    /**
     * Id로 삭제
     */
    @Test
    public void deleteById() {
        String delete = restTemplate.delete("2", VideoDTO.class);
        System.out.println(delete);
    }
}

다중 사례 검색

  • 새 버전의 ElasticSearch의 Query 인터페이스
  • Query는 Spring Data Elasticsearch의 인터페이스로, 여러 구체적 구현이 있으며새 버전 공식 문서에 내용이 부족하므로, 여기서 소스 코드를 보고 사례 실습을 진행합니다
  • CriteriaQuery
  • Criteria를 생성하여 데이터를 검색하며, Elasticsearch 쿼리 구문 또는 기본 지식을 이해할 필요가 없습니다
  • 사용자가 단순히 연결하고 조합하여 검색 문서가 반드시 만족해야 하는 객체를 지정하여 쿼리를 구축할 수 있습니다
  • StringQuery
  • Elasticsearch 쿼리를 JSON 문자열로 사용하며, Elasticsearch 쿼리 구문에 익숙한 사람에게 더 적합합니다
  • 또한 kibana 또는 postman과 같은 클라이언트 도구를 사용하여 디버깅하기에도 더 편리합니다
  • NativeQuery
  • 복잡한 쿼리 또는 Criteria API로 표현할 수 없는 쿼리 시 사용되는 클래스이며, 예를 들어 쿼리 구성 및 집계 사용 시나리오

NativeQuery 검색

  • 새 버전 검색 구문 사례, 쿼리는 새 버전의 lambda 표현식 구문을 사용하여 더 간결합니다

  • 전체 검색

  /**
   * 모든 것을 검색
   */
  @Test
  public void searchAll(){

    SearchHits<VideoDTO> search = restTemplate.search(Query.findAll(), VideoDTO.class);
    List<SearchHit<VideoDTO>> searchHits = search.getSearchHits();
    // searchHits를 얻어 순회하여 content를 가져옵니다
    List<VideoDTO> videoDTOS = new ArrayList<>();
    searchHits.forEach(hit -> {
      videoDTOS.add(hit.getContent());
    });
    System.out.println(videoDTOS);
  }

  • 일치 검색
/**
   * match 쿼리
   */
  @Test
  public void matchQuery(){

    Query query = NativeQuery.builder().withQuery(q -> q
        .match(m -> m
            .field("description") //필드
            .query("spring") //값
        )).build();
    SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);

    // searchHits를 얻어 순회하여 content를 가져옵니다
    List<VideoDTO> videoDTOS = new ArrayList<>();
    searchHits.forEach(hit -> {
      videoDTOS.add(hit.getContent());
    });
    System.out.println(videoDTOS);
  }

  • 페이지네이션 검색
    /**
     * 페이지네이션 검색
     */
    @Test
    public void pageSearch() {
        Query query = NativeQuery.builder().withQuery(Query.findAll())
                .withPageable(Pageable.ofSize(3).withPage(0)).build();

        SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);
        // searchHits를 얻어 순회하여 content를 가져옵니다
        List<VideoDTO> videoDTOS = new ArrayList<>();
        searchHits.forEach(hit -> {
            videoDTOS.add(hit.getContent());
        });
        System.out.println(videoDTOS);
    }

  • 정렬
#ascending():기본값, 오름차순

#descending():내림차순

   /**
     * 정렬 검색, 시간 길이에 따라 내림차순 정렬
     */
    @Test
    public void sortSearch() {
        Query query = NativeQuery.builder().withQuery(Query.findAll())
                .withPageable(Pageable.ofSize(10).withPage(0))
                .withSort(Sort.by("duration").descending()).build();

        SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);
        // searchHits를 얻어 순회하여 content를 가져옵니다
        List<VideoDTO> videoDTOS = new ArrayList<>();
        searchHits.forEach(hit -> {
            videoDTOS.add(hit.getContent());
        });
        System.out.println(videoDTOS);
    }


원본 StringQuery 검색

  • StringQuery란

  • Elasticsearch 쿼리를 JSON 문자열로 사용하며, Elasticsearch 쿼리 구문에 익숙한 사람에게 더 적합합니다

  • 또한 kibana 또는 postman과 같은 클라이언트 도구를 사용하여 디버깅하기에도 더 편리합니다

  • 사례 실습

  • 사례 1:bool must 쿼리, 제목에 아키텍처 키워드가 있고, 설명에 spring 키워드가 있으며, 시간 길이는 10~6000 사이인

  • 원본 DSL 쿼리

GET /video/_search
{
  "query": {
    "bool": {
      "must": [{
        "match": {
          "title": "아키텍처"
        }
      }, {
        "match": {
          "description": "spring"
        }
      }, {
        "range": {
          "duration": {
            "gte": 10,
            "lte": 6000
          }
        }
      }]
    }
  }
}

  • SpringBoot+SpringData 쿼리
  @Test
  public void stringQuery() {

        //제목에 아키텍처 키워드가 있고, 설명에 spring 키워드가 있으며, 시간 길이는 10~6000 사이인
        String dsl = """
                   {"bool":{"must":[{"match":{"title":"아키텍처"}},{"match":{"description":"spring"}},{"range":{"duration":{"gte":10,"lte":6000}}}]}}
                """;
        Query query = new StringQuery(dsl);

        List<SearchHit<VideoDTO>> searchHitList = restTemplate.search(query, VideoDTO.class).getSearchHits();

        // searchHits를 얻어 순회하여 content를 가져옵니다
        List<VideoDTO> videoDTOS = new ArrayList<>();
        searchHitList.forEach(hit -> {
            videoDTOS.add(hit.getContent());
        });
        System.out.println(videoDTOS);
    }

집계 검색

  • 집계 검색 사례

  • 방안 1:원본 DSL을 사용하여 처리할 수 있습니다

  • 방안 2:NativeQuery를 사용하여 집계 검색을 완료합니다

  • 다른 분류별 비디오 수 통계

GET /video/_search
{
  "size": 1,
  "aggs": {
    "category_group": {
      "terms": {
        "field": "category"
      }
    }
  }
}

		/**
     * 집계 쿼리
     */
    @Test
    void aggQuery() {
        Query query = NativeQuery.builder()
                .withAggregation("category_group", Aggregation.of(a -> a
                        .terms(ta -> ta.field("category").size(2))))
                .build();

        SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);

        //집계 데이터 가져오기
        ElasticsearchAggregations aggregationsContainer = (ElasticsearchAggregations) searchHits.getAggregations();
        Map<String, ElasticsearchAggregation> aggregations = Objects.requireNonNull(aggregationsContainer).aggregationsAsMap();

        //해당 이름의 집계 가져오기
        ElasticsearchAggregation aggregation = aggregations.get("category_group");
        Buckets<StringTermsBucket> buckets = aggregation.aggregation().getAggregate().sterms().buckets();

        //집계 정보 출력
        buckets.array().forEach(bucket -> {
            System.out.println("그룹명:"+bucket.key().stringValue() + ", 값" + bucket.docCount());
        });

        // searchHits를 얻어 순회하여 content를 가져옵니다
        List<VideoDTO> videoDTOS = new ArrayList<>();
        searchHits.forEach(hit -> {
            videoDTOS.add(hit.getContent());
        });
        System.out.println(videoDTOS);
    }

태그: 스프링부트 엘라스틱서치 자바 데이터베이스 검색엔진

7월 21일 09:39에 게시됨