記事ディレクトリ
分散型検索エンジン 01
– elasticsearch の基本
0. 学習目標
1 逆インデックスの原理を理解する
2 インデックスライブラリ、型、マッピング、ドキュメント、フィールドの概念を理解する
3 IK トークナイザーをインストールして使用できる
4 kibana を使用してインデックスライブラリ、型マッピング、ドキュメント操作を実現できる
5 RestClient を使用してインデックスを実現できるライブラリ、タイプマッピング、ドキュメント操作
1. まず elasticsearch について理解する
1.1. ES を理解する
1.1.1. elasticsearch の役割
Elasticsearch は、大量のデータから必要なものを迅速に見つけるのに役立つ多くの強力な機能を備えた、非常に強力なオープンソースの検索エンジンです。
例えば:
-
GitHub でコードを検索する
-
ECサイトで商品を探す
- Baidu で答えを検索する
-
タクシーアプリで近くの車を検索
1.1.2. ELK テクノロジースタック
elasticsearch は、kibana、Logstash、Elastic Stack (ELK) である Beats を組み合わせたものです。ログデータ分析、リアルタイム監視などの分野で広く使用されています。
Elasticsearch はエラスティック スタックの中核であり、データの保存、検索、分析を担当します。
1.1.3.elasticsearchとlucene
elasticsearch の最下層はluceneに基づいて実装されています。
Luceneは Java 言語検索エンジンのクラス ライブラリ(jar パッケージ) であり、Apache Corporation のトップレベル プロジェクトであり、1999 年に Doug Cutting によって開発されました。公式ウェブサイトのアドレス: https://lucene.apache.org/。
最下層はluceneに基づいており、luceneは jar パッケージ (Java 言語クラス ライブラリ) であるため、Java 言語開発に限定されます。API の
設計はより複雑で、ほとんどの人は、提供される API に従って直接開発します。学習の難易度は依然として非常に高い (学習曲線は急である)
検索のみを考慮し、高い同時実行性は考慮しておらず、効率的に使用したい場合は二次開発 (水平拡張) が必要です = " elasticsearch はこれを実行し、ネイティブの多くの欠点を解決しましたルシーン
elasticsearchの開発履歴:
- 2004 年、Shay Banon は Lucene に基づいて Compass を開発しました。
- 2010 年に、Shay Banon は Compass を書き換え、Elasticsearch と名付けました。
Restful は Http アクセスです。言語とは関係ありません。どの言語でも呼び出すことができます。
1.1.4. 他の検索手法を使用しない理由は何ですか?
現在よく知られている検索エンジン技術ランキング:
初期の頃は、Apache Solr が最も重要な検索エンジン テクノロジでしたが、elasticsearch の開発により、徐々に Solr を上回り、リードするようになりました。
1.1.5. 概要
エラスティックサーチとは何ですか?
- 検索、ログ統計、分析、システム監視などの機能を実装するために使用できるオープンソースの分散型検索エンジン
エラスティック スタック (ELK) とは何ですか?
- elasticsearch をコアとする技術スタック(beats、Logstash、kibana、elasticsearch を含む)
ルシーンとは何ですか?
- Apache のオープンソース検索エンジン クラス ライブラリであり、検索エンジンのコア API を提供します。
1.2. 転置インデックス
転置インデックスの概念は、MySQL のような順方向インデックスに基づいています。
MySQLデータベースのインデックスは順方向インデックス、
elasticsearchのインデックスは逆インデックスと呼ばれるものですが、
大きな違いがあるようですが、何が違うのでしょうか?次の例
1.2.1. フォワードインデックス
では、フォワードインデックスとは何でしょうか? たとえば、次のテーブル (tb_goods) に ID のインデックスを作成します。
ID に基づいてクエリを実行すると、インデックスに直接アクセスするため、クエリ速度が非常に速くなります。(B+木 O(log k N))
ただし、タイトルに基づいてファジー クエリを実行する場合は、データを 1 行ずつスキャンすることしかできません。プロセスは次のとおりです。
1) ユーザーがデータを検索します。条件はタイトルが一致することです。"%手机%"
2) ID 1 のデータなど、データを行ごとに取得します。
3) データ内のタイトルがユーザーの検索条件を満たしているかどうかを判断します
4) 一致する場合は結果セットに追加し、一致しない場合は破棄します。ステップ1に戻る
プログレッシブ スキャン、つまりフル テーブル スキャンでは、データ量が増加するにつれてクエリ効率が低下します。データの量が数百万に達すると、それは大惨事になります。恐ろしいO(n)
1.2.2. 転置インデックス
転置インデックスには 2 つの非常に重要な概念があります。
- ドキュメント(
Document
):検索に使用するデータであり、各データがドキュメントです。例: Web ページ、製品情報 - エントリ(
Term
):文書データやユーザ検索データなどを、一定のアルゴリズムで単語を分割し、意味を持った単語をエントリとします。例: 私は中国人です。これはいくつかのエントリに分けることができます: 私、はい、中国人、中国、中国人
転置インデックスの作成は順方向インデックスの特別な処理であり、そのプロセスは次のとおりです。
- アルゴリズムを使用して各ドキュメントのデータをセグメント化し、各エントリを取得します
- テーブルを作成します。データの各行には、エントリ、エントリが存在するドキュメント ID、場所などの情報が含まれます。
- エントリの一意性により、ハッシュ テーブル構造インデックスなどのエントリのインデックスを作成できます。
図に示すように:
高速クエリのために新しいテーブルが生成されます
転置インデックスの検索プロセスは次のとおりです (例として「Huawei 携帯電話」の検索を取り上げます)。
1) ユーザーは"华为手机"
検索条件を入力します。
2) ユーザーが入力したコンテンツをセグメント化して、エントリを取得します:华为
、手机
。
3) エントリを取得し、転置インデックスを検索すると、エントリを含むドキュメント ID 1、2、および 3 を取得できます。(No.2は両方のエントリがあり、ソート時の優先度が高くなります)
4) ドキュメント ID を取得して、前方インデックス内の特定のドキュメントを検索します。
図に示すように:
最初に転置インデックスをクエリし、次に順方向インデックスをクエリする必要がありますが、エントリとドキュメント ID の両方にインデックスが作成されており、クエリ速度は非常に高速です。フルテーブルスキャンは必要ありません。
コンテンツに応じてIDが見つかるので、転置インデックスと呼ばれます。
1.2.3. 前進と後退
では、なぜ一方は順方向インデックスと呼ばれ、もう一方は逆インデックスと呼ばれるのでしょうか?
-
前方インデックス作成は、ID によるインデックス作成の最も伝統的な方法です。ただし、用語に基づいてクエリを実行する場合は、まず各ドキュメントを 1 つずつ取得し、ドキュメントに必要な用語が含まれているかどうかを判断する必要があります。これは、ドキュメントに基づいて用語を検索するプロセスです。
-
転置インデックスはその逆で、まずユーザーが検索したいエントリを見つけ、そのエントリに従ってそのエントリを保護するドキュメントの ID を取得し、その ID に従ってドキュメントを取得します。これは、エントリに基づいてドキュメントを検索するプロセスです。
それはただ逆ですか?
それでは、両方のアプローチの長所と短所は何でしょうか?
前方インデックス:
- アドバンテージ:
- 複数のフィールドに対してインデックスを作成できます
- インデックスフィールドに基づいた検索と並べ替えは非常に高速です
- 欠点:
- インデックスのないフィールドまたはインデックス付きフィールドの一部の用語に基づいて検索する場合、フル テーブル スキャンのみを実行できます。
転置インデックス:
- アドバンテージ:
- 用語検索やあいまい検索の速度が非常に速い
- 欠点:
- インデックスは用語に対してのみ作成でき、フィールドには作成できません
- フィールドごとに並べ替えることはできません
1.3.es のいくつかの概念
elasticsearch には、mysql とは少し異なる独自の概念が多数ありますが、類似点もあります。
1.3.1. ドキュメントとフィールド
Elasticsearch は **ドキュメント (Document)** に保存されます。これは、データベース内の製品データまたは注文情報の一部です。ドキュメント データは json 形式にシリアル化され、elasticsearch に保存されます。
Json ドキュメントには多くの場合、データベースの列に似た多くのフィールド (フィールド)が含まれます。
1.3.2. インデックスとマッピング
インデックス (Index) は、同じ種類のドキュメントの集合です。
例えば:
- すべてのユーザー ドキュメントは、ユーザー インデックスと呼ばれる 1 つにまとめて編成できます。
- すべての商品のドキュメントは、商品インデックスと呼ばれてまとめて編成できます。
- すべての注文の文書は、注文のインデックスと呼ばれてまとめて整理できます。
したがって、インデックスはデータベース内のテーブルと考えることができます。
データベースのテーブルには、テーブルの構造、フィールドの名前とタイプ、その他の情報を定義するために使用される制約情報があります。したがって、インデックス ライブラリには、テーブルの構造制約と同様に、インデックス内のドキュメントのフィールド制約情報であるマッピングが存在します。
1.3.3.mysqlとelasticsearch
mysql と elasticsearch の概念を統一した方法で比較してみましょう。
MySQL | エラスティックサーチ | 説明する |
---|---|---|
テーブル | 索引 | インデックスは、データベースのテーブルに似たドキュメントのコレクションです。 |
行 | 書類 | ドキュメント (Document) はデータベースの行 (Row) と同様のデータであり、ドキュメントは JSON 形式です |
カラム | 分野 | フィールド (Field) は、データベースの列 (Column) に似た、JSON ドキュメント内のフィールドです。 |
スキーマ | マッピング | マッピングは、フィールド タイプの制約など、インデックス内のドキュメントに対する制約です。データベースのようなテーブル構造 (スキーマ) |
SQL | DSL | DSL は elasticsearch によって提供される JSON スタイルのリクエスト ステートメントであり、 elasticsearch の操作とCRUD の実装に使用されます。 |
elasticsearchを学んだ後はmysqlはもう必要ないということですか?
実際にはそうではありませんが、どちらにも独自の長所と短所があります。
-
Mysql: トランザクション型の操作が得意で、データのセキュリティと一貫性を確保できます。
-
Elasticsearch: 大量のデータの検索、分析、計算が得意です。
したがって、企業では、この 2 つが組み合わせて使用されることがよくあります。
- 高度なセキュリティ要件が必要な書き込み操作の場合は、mysql を使用して実装します。
- 高いクエリ パフォーマンスを必要とする検索要件の場合は、elasticsearch を使用して、
- この 2 つは、データの同期を実現し、一貫性を確保するための特定の方法に基づいています。
1.4. es、kibanaのインストール
1.4.1. es、kibanaのインストール
リンク: https://pan.baidu.com/s/1LRpd6xncRhxHIgK13gHu4g
抽出コード: hzan
授業前の参考資料:
または、このブログを直接参照してください:検索エンジン elasticsearch
1.4.2. トークナイザーのインストール
授業前の参考資料:
または、このブログを直接参照してください:検索エンジン elasticsearch
1.4.3. 概要
トークナイザーの機能は何ですか?
- 転置索引作成時の文書の単語分割
- ユーザーが検索する際、入力内容の単語分割
IK トークナイザーにはモードがいくつありますか?
- ik_smart: インテリジェントなセグメンテーション、粗粒度
- ik_max_word: きめ細かい、最も細かいセグメンテーション
IK トークナイザーはどのようにエントリを展開しますか? エントリを非アクティブ化するにはどうすればよいですか?
- config ディレクトリ内の IkAnalyzer.cfg.xml ファイルを使用して、拡張辞書と無効化された辞書を追加します
- 拡張または無効化されたエントリを辞書に追加する
2. インデックスライブラリの操作
インデックス ライブラリはデータベース テーブルに似ており、マッピング マッピングはテーブル構造に似ています。
esにデータを格納したい場合は、まず「ライブラリ」と「テーブル」を作成する必要があります。
2.1.mapping マッピングプロパティ
マッピングは、インデックス ライブラリ内のドキュメントに対する制約です。一般的なマッピング属性には次のものがあります。
- type: フィールド データ型、一般的な単純型は次のとおりです。
- 文字列: テキスト (セグメント化できるテキスト)、キーワード (ブランド、国、IP アドレスなどの正確な値)
- 値:long、integer、short、byte、double、float、
- ブール値: ブール値
- 日付: 日付
- オブジェクト: オブジェクト
- (配列はありませんが、各型は複数の値を持つことができます。つまり、配列の型に関係なく、配列要素の型のみが考慮されます)
- Index: 転置インデックスを作成するかどうか。デフォルトは true (一度 false に設定されると、転置インデックスは存在せず、今後検索に参加できなくなります) (もちろん、一部の非転置インデックスは、コンテンツフィールドは検索に参加する必要はありません)
- アナライザー: どのトークナイザーを使用するか(テキスト タイプのみ単語分割が必要です。テキストで使用されるものとして理解できます)
- プロパティ: このフィールドのサブフィールド(ネスト時に使用)
たとえば、次の json ドキュメント:
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "whuer程序员Java讲师",
"email": "[email protected]",
"score": [99.1, 99.5, 98.9],
"name": {
"firstName": "云",
"lastName": "赵"
}
}
各フィールドのマッピング(マッピング)に対応:
- age: 型は整数です。検索に参加するため、インデックスは true である必要があります。トークナイザーは必要ありません。
- Weight: 型は float です。検索に参加するため、インデックスは true である必要があります。トークナイザーは必要ありません。
- isMarried: タイプはブール型です。検索に参加するため、インデックスは true である必要があります。トークナイザーは必要ありません。
- info: タイプは文字列で、単語の分割が必要なのでテキストです。検索に参加するには、インデックスが true である必要があります。単語の分割では ik_smart を使用できます。
- email: タイプは文字列ですが、単語の分割は必要ないため、キーワードになります。検索には関与しないため、インデックスは false である必要があります。単語の分割は必要ありません。
- スコア: 配列ですが、要素の型 (float) のみを確認します。検索に参加するため、インデックスは true である必要があります。トークナイザーは必要ありません。
- name: タイプはオブジェクトであり、複数のサブ属性を定義する必要があります
- name.firstName; タイプは文字列ですが、単語の分割は必要ないため、キーワードです。検索に参加するため、インデックスは true である必要があります。単語の分割は必要ありません。
- name.lastName; タイプは文字列ですが、単語の分割は必要ないため、キーワードになります。検索に参加するため、インデックスは true である必要があります。単語の分割は必要ありません。
2.2. インデックスライブラリのCRUD
ここでは、一様に Kibana を使用して DSL を記述し、デモを行います。
2.2.1. インデックスライブラリとマッピングの作成
基本的な構文:
- リクエストメソッド:PUT
- リクエスト パス: /index ライブラリ名 (カスタマイズ可能)
- リクエストパラメータ: マッピング マッピング
インデックス ライブラリ (およびマッピング) の作成は、mysql データベースにテーブルを作成することと同じです。Mysql は
SQL ステートメントであり、es は json スタイルの DSL ステートメントです。
フォーマット:
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}
"mappings": マッピング、構造を表します。
"properties": これがネストであることを表します。
各フィールドには、特に見逃せないタイプがあることに注意してください。 "name": {"type": "object"} 上記の残りの
部分すでに非常に詳しいです
例:
# 创建索引库(包含了映射)
PUT /whu
{
"mappings": {
"properties": {
"info":{
"type": "text",
"analyzer": "ik_smart"
},
"email":{
"type": "keyword",
"index": false
},
"name":{
"type": "object",
"properties": {
"firstName":{
"type": "keyword"
},
"lastName":{
"type": "keyword"
}
}
}
}
}
}
2.2.2. クエリインデックスライブラリ
基本的な構文:
-
リクエストメソッド:GET
-
リクエストパス: /index ライブラリ名
-
リクエストパラメータ: なし
形式:
GET /索引库名
例:
2.2.3. インデックスライブラリの変更
転置インデックスの構造は複雑ではありませんが、データ構造が変更されると (たとえば、トークナイザーが変更される)、転置インデックスを再作成する必要があり、これは大変なことです。したがって、インデックス ライブラリが作成されると、マッピングを変更することはできません。
マッピング内の既存のフィールドは変更できませんが、逆インデックスには影響しないため、マッピングに新しいフィールドを追加することはできます。
文法説明:
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
例:
2.2.4. インデックスライブラリの削除
文法:
-
リクエストメソッド:DELETE
-
リクエストパス: /index ライブラリ名
-
リクエストパラメータ: なし
フォーマット:
DELETE /索引库名
キバナでのテスト:
2.2.5. 概要
インデックスライブラリの操作とは何ですか?
- インデックス ライブラリを作成します: PUT /インデックス ライブラリ名
- クエリインデックスライブラリ: GET / インデックスライブラリ名
- インデックス ライブラリの削除: DELETE / インデックス ライブラリ名
- フィールドを追加: PUT /index ライブラリ名/_mapping
3. 文書操作
es のドキュメントは mysql のテーブル内のデータ行に似ており、
kibana は navigate に相当します。
3.1. 新しいドキュメント
文法:
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
ID を自分で書き込まないと、ランダムな ID が生成されますが、これは私たちが見たいものではないため、ID を記述する必要があります。
例:
# 插入文档
POST /whu/_doc/1
{
"info": "whuer学不会java",
"email": "[email protected]",
"name":{
"firstName": "波",
"lastName": "波"
}
}
idは1を指定し、それ以外はjson形式で記述できます
応答:
3.2. ドキュメントのクエリ
REST スタイルによれば、新しい追加は post であり、クエリは get である必要がありますが、クエリには通常条件が必要です。ここではドキュメント ID を持ってきます。
文法:
GET /{
索引库名称}/_doc/{
id}
kibana を介してデータを表示します。
GET /heima/_doc/1
結果の表示:
3.3. 文書の削除
削除には DELETE リクエストを使用します。同様に、ID に従って削除する必要があります。
文法:
DELETE /{
索引库名}/_doc/id值
例:
# 根据id删除数据
DELETE /heima/_doc/1
結果:
一度も変更がなかった場合、バージョン管理フィールドの「_version」が一度インクリメントされます。
3.4. ドキュメントの変更
変更するには 2 つの方法があります。
- 完全な変更: 元のドキュメントを直接上書きします(元のドキュメントを完全に削除して追加します)。
- 増分変更: ドキュメント内の一部のフィールドを変更します(元のフィールドは削除されず、元のフィールドが直接変更されます)
3.4.1. 完全な修正
完全な変更は元のドキュメントを上書きすることです。その本質は次のとおりです。
- 指定された ID に基づいてドキュメントを削除します
- 同じIDを持つ新しいドキュメントを追加します
注: id による削除時に id が存在しない場合は、2 番目の追加も実行され、変更から追加に動作が変わります。
文法:
PUT /{
索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
例:
# 全量修改文档
PUT /whu/_doc/1
{
"info": "whuer学不会java",
"email": "[email protected]",
"name":{
"firstName": "波",
"lastName": "波"
}
}
メール名が変更されました
3.4.2. 増分修正
増分変更とは、指定された ID に一致するドキュメント内の一部のフィールドのみを変更することです。
文法:
POST /{
索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
POST の部分的な変更。JSON ドキュメントに大きな「doc」が必要です
。PUT の完全な変更。JSON ドキュメントは正常です。「doc」は必要ありません。
例:
# 局部修改文档字段
POST /whu/_update/1
{
"doc":{
"email": "[email protected]"
}
}
電子メール フィールドのみを変更する場合は、1 つのフィールドの変更の説明のみを記述する必要があります
。!!真ん中は _doc ではなく _update です (_doc と書かれていたのは完全な修正となり、他は削除され、最後にこのフィールドだけが残りました)
3.5. 概要
文書操作とは何ですか?
- ドキュメントの作成: POST /{インデックス ライブラリ名}/_doc/ドキュメント ID { json ドキュメント }
- ドキュメントのクエリ: GET /{インデックス ライブラリ名}/_doc/ドキュメント ID
- ドキュメントの削除: DELETE /{インデックス ライブラリ名}/_doc/ドキュメント ID
- ドキュメントを変更します。
- 完全な変更: PUT /{インデックス ライブラリ名}/_doc/ドキュメント ID { json ドキュメント }
- 増分変更: POST /{インデックス ライブラリ名}/ _update /ドキュメント ID { "doc": {フィールド}}
4.RestAPI
ESは、ESを操作するためのさまざまな言語のクライアントを正式に提供します。これらのクライアントの本質は、DSL ステートメントを組み立て、http リクエストを通じて ES に送信することです。公式ドキュメントのアドレス: https://www.elastic.co/guide/en/elasticsearch/client/index.html
Java Rest クライアントには 2 つのタイプが含まれます。
- Java 低レベル REST クライアント
- Java高レベルRESTクライアント
私たちが学習しているのは、Java HighLevel Rest Client クライアント API です。
4.0. デモプロジェクトをインポートする
4.0.1. データのインポート
リンク: https://pan.baidu.com/s/1LRpd6xncRhxHIgK13gHu4g
抽出コード: hzan
まず新しいデータベースを作成します
create DATABASE es01;
次に、授業前の資料で提供されたデータベース データをインポートします。
次に、データベース es01 を更新して確認します。
データ構造は次のとおりです。
CREATE TABLE `tb_hotel` (
`id` bigint(20) NOT NULL COMMENT '酒店id',
`name` varchar(255) NOT NULL COMMENT '酒店名称;例:7天酒店',
`address` varchar(255) NOT NULL COMMENT '酒店地址;例:航头路',
`price` int(10) NOT NULL COMMENT '酒店价格;例:329',
`score` int(2) NOT NULL COMMENT '酒店评分;例:45,就是4.5分',
`brand` varchar(32) NOT NULL COMMENT '酒店品牌;例:如家',
`city` varchar(32) NOT NULL COMMENT '所在城市;例:上海',
`star_name` varchar(16) DEFAULT NULL COMMENT '酒店星级,从低到高分别是:1星到5星,1钻到5钻',
`business` varchar(255) DEFAULT NULL COMMENT '商圈;例:虹桥',
`latitude` varchar(32) NOT NULL COMMENT '纬度;例:31.2497',
`longitude` varchar(32) NOT NULL COMMENT '经度;例:120.3925',
`pic` varchar(255) DEFAULT NULL COMMENT '酒店图片;例:/img/1.jpg',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.0.2. プロジェクトのインポート
次に、授業前の教材で提供される項目をインポートします。
プロジェクトの構造を次の図に示します。
次に、テスト クラスに小さなデモを作成して、プロジェクトが実行できるかどうかを確認します。
cn.whu.hotel.HotelDemoApplicationTests
@Autowired
private HotelService hotelService;
@Test
public void test(){
int id = 36934;
Hotel hotel = hotelService.getById(id);
System.out.println(hotel);
}
4.0.3.マッピング マッピング分析
インデックス ライブラリを作成する鍵となるのはマッピングであり、マッピングに関して考慮すべき情報には次のものが含まれます。
- フィールド名
- フィールドのデータ型
- 捜索に参加するかどうか
- 単語の分割が必要ですか
- 単語の分割の場合、トークナイザーとは何ですか?
で:
- フィールド名、フィールドのデータ型、データテーブル構造の名前と型を参照できます
- 検索に参加するかどうかは画像アドレスなどの事業内容を分析して判断する必要があり、検索に参加する必要はありません
- 単語の分割がコンテンツに依存するかどうか。コンテンツが全体である場合、単語の分割は必要ありません。それ以外の場合、単語の分割が必要です。
- Tokenizer、ik_max_word を均一に使用できます
ホテル データのインデックス ライブラリ構造を見てみましょう。
- ID は常に文字列であるため、長く定義することはできません。es には 2 種類の文字列しかありません: テキスト (セグメント化できるテキスト)、キーワード (正確な値、セグメント化不可)、明らかにここでの ID はキーワードです
- ユーザーは住所でホテルを検索しないと考えられているため、住所キーワード (文字列型であることを示すだけ) は検索に参加しません。(写真と同じ)
- 価格は並べ替えてフィルタリングする必要があるため、検索に参加する必要があります (他の同様のもの)
- 座標点には特殊なタイプがあります
# 酒店的mapping (类似于定义表结构)
PUT /hotel
{
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword",
"copy_to": "all"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
Index: false は検索に参加しないことを意味します
いくつかの特別なフィールドの説明:
- location: 精度と緯度を含む地理座標
- all: 結合フィールド。その目的は、copy_to を使用して複数のフィールドの値を結合し、ユーザーが検索できるようにすることです (複数の条件を簡単に検索できるようにする補助フィールド)
地理座標の説明:
コピー先の説明:
複数のフィールドの内容を1つのフィールドで検索でき、実際にフィールドをコピーするのではなく、それをもとにインデックスを作成することで、複数フィールドの検索を工夫かつ簡単に実現します。
書いたら忘れずに実行してください
4.0.4. RestClientの初期化
elasticsearch が提供する API では、elasticsearch とのすべての対話は RestHighLevelClient という名前のクラスにカプセル化されており、elasticsearch との接続を確立するには、最初にこのオブジェクトの初期化を完了する必要があります。
3 つのステップに分かれています。
1) es の RestHighLevelClient 依存関係を導入します。
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2) SpringBoot のデフォルトの ES バージョンは 7.6.2 であるため、デフォルトの ES バージョンをオーバーライドする必要があります。
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
3) RestHighLevelClient を初期化します。
初期化コードは次のとおりです。
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
ここでは、単体テストの便宜のために、テスト クラス HotelIndexTest を作成し、@BeforeEach メソッドに初期化コードを記述します。
package cn.whu.hotel;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import java.io.IOException;
public class HotelIndexTest {
private RestHighLevelClient client;
@BeforeEach
public void setUp(){
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.141.100:9200/") // 最后的'/'不能有
// 集群的话可以指定多个,中间逗号隔开
));
}
@AfterEach
public void tearDown() throws IOException {
client.close();
}
@Test
void testInit() {
System.out.println(client);
// org.elasticsearch.client.RestHighLevelClient@12d2ce03
}
}
4.1. インデックスライブラリの作成
4.1.1. コードの解釈
インデックス ライブラリを作成するための API は次のとおりです。
コードは 3 つのステップに分かれています。
- 1) リクエストオブジェクトを作成します。インデックス ライブラリを作成する操作であるため、リクエストは CreateIndexRequest です。
- 2) リクエスト パラメータの追加は、実際には DSL の JSON パラメータ部分です。json 文字列は非常に長いため、コードをより洗練されたものにするために静的文字列定数 MAPPING_TEMPLATE がここで定義されています。
- 3) リクエストを送信する場合、 client.indices() メソッドの戻り値は、インデックス ライブラリ操作に関連するすべてのメソッドをカプセル化する IndicesClient タイプです。
4.1.2. 完全な例
Hotel-demo の cn.whu.hotel.constants パッケージの下に、マッピング マッピングの JSON 文字列定数を定義するクラスを作成します。
package cn.whu.hotel.constants;
public class HotelConstants {
public static final String MAPPING_TEMPLATE = "{\n" +
" \"mappings\": {\n" +
" \"properties\": {\n" +
" \"id\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"name\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"address\":{\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"price\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"score\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"brand\":{\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"city\":{\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"starName\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"business\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"location\":{\n" +
" \"type\": \"geo_point\"\n" +
" },\n" +
" \"pic\":{\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"all\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}
Hotel-demo の HotelIndexTest テスト クラスで、インデックスを作成する単体テストを作成します。
@Test
void createHotelIndex() throws IOException {
// 1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.准备请求的参数:DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发送请求 (request对象传递进来)
client.indices().create(request, RequestOptions.DEFAULT);
}
注 1: import static cn.whu.hotel.constants.HotelConstants.MAPPING_TEMPLATE;
DSL ステートメント MAPPING_TEMPLATE は静的な方法で定数をインポートします
注
2: import org.elasticsearch.client.indices.CreateIndexRequest;
このパッケージには同じ内容の 2 つのファイルがあります名前 はい、誤解しないでください
最後に、インデックス ライブラリにクエリを実行して、次のことを確認します。
# 查询索引库
GET /hotel
4.2. インデックスライブラリの削除
インデックス ストアを削除する DSL ステートメントは非常に簡単です。
DELETE /hotel
インデックス ライブラリの作成と比較すると、次のようになります。
- リクエストメソッドが PUT から DELTE に変更されました
- リクエストのパスは変更されません
- リクエストパラメータがありません
したがって、コードの違いは Request オブジェクトに反映される必要があります。まだ 3 つのステップがあります。
- 1) リクエストオブジェクトを作成します。今回は DeleteIndexRequest オブジェクトです
- 2) パラメータを準備します。ここにはパラメータはありません
- 3) リクエストを送信します。代わりに delete メソッドを使用してください
Hotel-demo の HotelIndexTest テスト クラスで、インデックスを削除する単体テストを作成します。
@Test
void testDeleteHotelIndex() throws IOException {
// 1. 创建Request对象: 参数是索引库名称
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2. 准备请求参数 没有参数 (删除知道个名字就行了嘛)
// 3. 发送请求: (需要传入request对象)
client.indices().delete(request, RequestOptions.DEFAULT);
}
4.3. インデックスライブラリが存在するかどうかを確認する
インデックス ライブラリが存在するかどうかを判断する本質はクエリであり、対応する DSL は次のとおりです。
GET /hotel
つまり、削除された Java コード フローと似ています。まだ 3 つのステップがあります。
- 1) リクエストオブジェクトを作成します。今回は GetIndexRequest オブジェクト
- 2) パラメータを準備します。ここにはパラメータはありません
- 3) リクエストを送信します。代わりにexistsメソッドを使用してください
@Test
void testExistHotelIndex() throws IOException {
// 1. 创建Request对象: 参数是索引库名称
GetIndexRequest request = new GetIndexRequest("hotel");
// 2. 发送请求: (需要传入request对象)
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
// 3. 打印结果
System.out.println("exists = " + exists);
}
4.4. 概要
JavaRestClient が elasticsearch を操作するプロセスは基本的に同様です。中心となるのは、インデックス ライブラリの操作オブジェクトを取得する client.indices() メソッドです。
インデックス ライブラリ操作の基本的な手順は次のとおりです。
- RestHighLevelClient を初期化する
- XxxIndexRequest を作成します。XXX は作成、取得、削除です
- DSL の準備 (作成時に必須、その他はパラメータなし)
- リクエストを送信します。RestHighLevelClient#indices().xxx() メソッドを呼び出します。xxx は create、existent、delete です。
5.RestClient操作ドキュメント
インデックス ライブラリの操作から分離するために、次の 2 つのことを行うテスト クラスを再度追加します。
- RestHighLevelClient を初期化する
- ホテルのデータはデータベース内にあり、クエリには IHotelService を使用する必要があるため、このインターフェイスを挿入します。
package cn.whu.hotel;
import cn.whu.hotel.service.IHotelService;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest
public class HotelDocumentTest {
@Autowired
private IHotelService service;
private RestHighLevelClient client;
@BeforeEach
public void setUp(){
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.141.100:9200") // 最后的'/'不能有
// 集群的话可以指定多个,中间逗号隔开
));
}
@AfterEach
public void tearDown() throws IOException {
client.close();
}
@Test
public void testInit(){
System.out.println(service.getById(36934));
}
}
5.1. 新しいドキュメント
データベースからホテル データをクエリし、elasticsearch に書き込みたいと考えています。
5.1.1. インデックスライブラリエンティティクラス
データベース クエリの結果は、ホテル タイプのオブジェクトです。構造は次のとおりです。
@Data
@TableName("tb_hotel")
public class Hotel {
@TableId(type = IdType.INPUT)
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String longitude;
private String latitude;
private String pic;
}
インデックス ライブラリの構造には次のような違いがあります。
- 経度と緯度を位置に結合する必要がある
したがって、インデックス ライブラリの構造に一致する新しい型を定義する必要があります。
package cn.itcast.hotel.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
// 通过构造方法 将Hotel转换为HotelDoc
public HotelDoc(Hotel hotel) {
this.id = hotel.getId();
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.score = hotel.getScore();
this.brand = hotel.getBrand();
this.city = hotel.getCity();
this.starName = hotel.getStarName();
this.business = hotel.getBusiness();
this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
this.pic = hotel.getPic();
}
}
5.1.2. 構文の説明
新しく追加されたドキュメントの DSL ステートメントは次のとおりです。
POST /{
索引库名}/_doc/1
{
"name": "Jack",
"age": 21
}
対応する Java コードを図に示します。
最初に小さなデモを作成して試してみることができます
@Test
void testAddDocument() throws IOException {
// 1. 准备request对象: 参数为索引名称 和 索引id(必须字符串类型)
IndexRequest request = new IndexRequest("hotel").id("1");
// 2. 准备json文档
request.source("{\"name\":\"jack\",\"price\":21}", XContentType.JSON);
// 3. 发送请求
client.index(request, RequestOptions.DEFAULT);
}
これはインデックス ライブラリの作成に似ており、次の 3 段階のプロセスであることがわかります。
- 1) リクエストオブジェクトを作成する
- 2) リクエストパラメータ(DSL 内の JSON ドキュメント)を準備します。
- 3) リクエストの送信
変更点は、ここでは client.xxx() の API が直接使用され、 client.indices() が不要になったことです。
5.1.3. 完全なコード
ホテル データをインポートする場合、基本的なプロセスは同じですが、考慮する必要がある変更がいくつかあります。
- ホテル データはデータベースから取得されます。ホテル オブジェクトを取得するには、まずデータベースにクエリを実行する必要があります。
- ホテル オブジェクトは HotelDoc オブジェクトに変換する必要があります
- HotelDoc は json 形式にシリアル化する必要があります
したがって、コードの全体的な手順は次のようになります。
- 1) ホテルデータをクエリ ID に基づいてホテルを検索します
- 2) ホテルを HotelDoc としてパッケージ化
- 3) HotelDoc を JSON にシリアル化する
- 4) IndexRequest を作成し、インデックス ライブラリの名前と ID を指定します。
- 5) リクエストパラメータ(JSONドキュメント)を準備します。
- 6) リクエストの送信
Hotel-demo の HotelDocumentTest テスト クラスで、単体テストを作成します。
@Test
void testAddDocument() throws IOException {
// 准备数据
// 1. 根据id查询hotel数据
Hotel hotel = service.getById(36934L);
// 2. 转换为es匹配的文档类型
HotelDoc hotelDoc = new HotelDoc(hotel);
// 3. 转换为json格式
String json = JSON.toJSONString(hotelDoc);
// 1. 准备request对象: 参数为索引名称 和 索引id(必须字符串类型)
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
// 2. 准备json文档
request.source(json, XContentType.JSON);
// 3. 发送请求
client.index(request, RequestOptions.DEFAULT);
}
5.2. ドキュメントのクエリ
5.2.1. 構文の説明
クエリ DSL ステートメントは次のとおりです。
GET /hotel/_doc/{
id}
非常にシンプルなので、コードは大きく 2 つのステップに分かれています。
- リクエストオブジェクトを準備する
- リクエストを送信する
ただし、クエリの目的は、HotelDoc に解析される結果を取得することなので、結果の解析が困難になります。完全なコードは次のとおりです。
ご覧のとおり、結果は JSON であり、ドキュメントが_source
属性に配置されているため、解析ではそれを取得し_source
、Java オブジェクトに逆シリアル化します。
以前と同様に、これも 3 つのステップからなるプロセスです。
- 1) Request オブジェクトを準備します。今回はクエリなのでGetRequest
- 2) リクエストを送信し、結果を取得します。クエリなのでここで client.get() メソッドが呼び出されます。
- 3) 解析結果は、JSON を逆シリアル化します。
5.2.2. 完全なコード
Hotel-demo の HotelDocumentTest テスト クラスで、単体テストを作成します。
@Test
public void testGetDocumentById() throws IOException {
// 1. 准备request
GetRequest request = new GetRequest("hotel","36934");
// 2. 发送请求,得到响应
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3. 解析响应,得到json
String json = response.getSourceAsString();
// 4. 打印结果
// 4.1 打印json
System.out.println(json);
// 4.2 json转为Object再打印: 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
5.3. 文書の削除
削除用の DSL は次のようなものです。
DELETE /hotel/_doc/{
id}
クエリと比較すると、リクエスト メソッドが DELETE から GET に変わるだけですが、Java コードは引き続き 3 つのステップを踏む必要があると考えられます。
- 1) Request オブジェクトを準備します。削除されるため、今回は DeleteRequest オブジェクトです。インデックス ライブラリの名前と ID を指定するには
- 2) パラメーターを準備します。パラメーターはありません(このステップは必要ありません)
- 3) リクエストを送信します。削除されるのでclient.delete()メソッドです
Hotel-demo の HotelDocumentTest テスト クラスで、単体テストを作成します。
@Test
void testDeleteDocument() throws IOException {
// 1. 准备request
DeleteRequest request = new DeleteRequest("hotel","36934");
// 2. 发送请求
client.delete(request, RequestOptions.DEFAULT);//只是删es,不会删数据库哈
}
5.4. ドキュメントの変更
5.4.1. 構文の説明
2 つの方法について説明しました。
- 完全な変更: 本質は、最初に ID に従って削除し、次に追加することです。
- 増分変更: ドキュメント内の指定されたフィールド値を変更します。
RestClient API では、完全な変更は新しく追加された API とまったく同じであり、判断は ID に基づいて行われます。
- 追加時にIDがすでに存在する場合は変更してください
- 追加時にIDが存在しない場合は追加してください
ここでは詳細には触れませんが、主に増分修正に焦点を当てます。
コード例を次の図に示します。
以前と同様に、これも 3 つのステップからなるプロセスです。
- 1) Request オブジェクトを準備します。今回は修正なのでUpdateRequestです
- 2) パラメータを準備します。つまり、変更するフィールドを含む JSON ドキュメントです。
- 3) ドキュメントを更新します。ここで client.update() メソッドを呼び出します
5.4.2. 完全なコード
Hotel-demo の HotelDocumentTest テスト クラスで、単体テストを作成します。
@Test
void testUpdateDocument() throws IOException {
// 1. 准备request
UpdateRequest request = new UpdateRequest("hotel","36934");
// 2. 准备参数
request.doc(
// 可变参数 逗号隔开
"price","345",
"starName","三钻"
);
// 3. 发送请求
client.update(request, RequestOptions.DEFAULT);
}
5.5. ドキュメントのバッチインポート
ケースの要件: BulkRequest を使用して、データベース データをインデックス ライブラリにバッチでインポートします。
次のように進めます。
-
mybatis-plus を使用してホテル データをクエリする
-
クエリされたホテル データ (Hotel) をドキュメント タイプ データ (HotelDoc) に変換します。
-
JavaRestClient で BulkRequest バッチ処理を使用してドキュメントをバッチで追加する
5.5.1. 構文の説明
BulkRequest バッチ処理の本質は、複数の通常の CRUD リクエストを結合して一緒に送信することです。
他のリクエストを追加するための add メソッドが提供されます。
ご覧のとおり、追加できるリクエストには次のものがあります。
- 追加する IndexRequest
- UpdateRequest、変更するものです
- DeleteRequest、つまり削除
そのため、新機能であるBulkに複数のIndexRequestを一括で追加します。例:
実際には、まだ 3 つのステップがあります。
- 1) リクエストオブジェクトを作成します。一括リクエストはこちら
- 2) パラメータを準備します。バッチ処理のパラメータは他の Request オブジェクトです。ここでは複数の IndexRequest が使用されます。
- 3) リクエストを開始します。これはバッチ処理であり、呼び出されるメソッドは client.bulk() メソッドです
ホテル データをインポートするときに、上記のコードを for ループに変換できます。
5.5.2. 完全なコード
Hotel-demo の HotelDocumentTest テスト クラスで、単体テストを作成します。
@Test
public void testBulkRequest() throws IOException {
// 0. 准备数据: 批量查询酒店数据
List<Hotel> list = service.list();
// 1. 创建Request
BulkRequest request = new BulkRequest();
// 2. 准备参数,添加多个新增的Request
for (Hotel hotel : list) {
HotelDoc doc = new HotelDoc(hotel);
request.add(new IndexRequest("hotel")
.id(doc.getId().toString())
.source(JSON.toJSONString(doc),XContentType.JSON));
}
// 3. 发送请求
client.bulk(request,RequestOptions.DEFAULT);
}
ブラウザ側でデータをバッチクエリする
# 批量查询
GET /hotel/_search
その他の DSL ステートメント関数:このブログまたは他の Baidu ブログを参照してください。
5.6. 概要
ドキュメント操作の基本的な手順は次のとおりです。
- RestHighLevelClient を初期化する
- XxxRequest を作成します。XXX はインデックス、取得、更新、削除、一括です
- パラメータを準備します (インデックス、更新、一括に必要)
- リクエストを送信します。RestHighLevelClient#.xxx() メソッドを呼び出します。xxx は、index、get、update、delete、bulk です。
- 解析結果 (Get に必要)