スプリングデータのエラスティックサーチ
45423 ワード
この記事では、Spring Data EllipticSearchライブラリを使用して、エラスティックサーチノードを取得して実行する方法と、Javaアプリケーションからエラスティックサーチでデータを検索したり、インデックスを作成する方法についての基本的な手順を説明します.
私もいくつかの一般的なタスクについて説明します.クエリを複数のフィールドで検索してフィルターを作成する方法.
新しいスプリングブートプロジェクトを作成する
シャード:インデックスのシャードの数. レプリカ:インデックスのレプリカの数. CreateIndex :リポジトリブートストラップのインデックスを作成するかどうかを設定します.デフォルト値はtrueです.
その上、analyzer , searchAnalyzer , and normalizer カスタマイズできます.弾性検索でstandard analyzer デフォルトのアナライザです. ここでは、上記の注釈を使用してブックのプロパティをインデックス化する方法を示します. 作者エンティティ
本の名前と同様.
次のステップは、私の実行しているエラスティックスセラピーへの接続を設定し、リポジトリを作成して、インデックスを検索し、本を検索することです.
上記のclienconfigurationは、ssl、connectおよびsocket timeout、headers、および他のパラメータのオプションを設定することができます.例えば、
あなたは行くことができますlocalhost:9200/books/_search すべてのインデックス帳を参照してください.
またはlocalhost:9200/books/_mapping 各フィールドの詳細なマッピングを参照してください.
検索とフィルタ
ドラッグアンドアップを取得 Springbootプロジェクトを設定します ポアオブジェクト インデックス作成とマッピング スプリングデータを使用した検索、フィルタ、ソート ソースコードの例を見つけるhere インジット
私もいくつかの一般的なタスクについて説明します.クエリを複数のフィールドで検索してフィルターを作成する方法.
新しいスプリングブートプロジェクトを作成する
新しいスプリングブートプロジェクトを必要とするときはいつでもstart.spring.io を生成する.以下がセットアップです.
必要なプロジェクト名、および依存関係を選択した後、「生成」をクリックし、ダウンロードしたZIPファイルを展開します.この例では、プロジェクトディレクトリはspring-data-elasticsearch-example
確実なエラスティックサーチの実行
始めるためには、私のアプリケーションが接続できるように、エラスティックサーチを実行する必要があります.
つの単純なオプションは
私はすでに私のMacにインストールされているDockerとDockerを持っているのでDocker Desktop ). を作成するelasticsearch.yml
ファイルを作成し、Dockerを使用してエラスティックサーチコンテナを起動します
クリエイトelasticsearch.yml
インsrc/main/docker
cd spring-data-elasticsearch-example
mkdir -p src/main/docker && touch src/main/docker/elasticsearch.yml
次のコンテンツを使用します.
version: '2'
services:
my-elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.9.2
container_name: my-elasticsearch
# volumes:
# - ~/data/my-elasticsearch/:/usr/share/elasticsearch/data/
ports:
- 9200:9200
environment:
- 'ES_JAVA_OPTS=-Xms1024m -Xmx1024m'
- 'discovery.type=single-node'
エラスティックサーチコンテナを起動する
docker-compose -f src/main/docker/elasticsearch.yml up -d
FlashSearchは、現在、実行中ですhttp://localhost:9200/
詳細についてはdocumentation
インデックス検索データ
私には、以下のサンプル実体があります.私はすべての書籍や著者のインデックスをインデックスとして'本と呼ばれる.
エンティティを作成し、インデックスを作成する方法を定義します
@Getter
@Setter
@Accessors(chain = true)
@EqualsAndHashCode
@ToString
@Document(indexName="books")
public class Book {
@Id
private String id;
@MultiField(
mainField = @Field(type = FieldType.Text, fielddata = true),
otherFields = {
@InnerField(suffix = "raw", type = FieldType.Keyword)
}
)
private String name;
@Field(type = FieldType.Text)
private String summary;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Object)
private List<Author> authors;
}
ブックエンティティでは、@ document、@ field、@ multifieldのような別の注釈を使用して、その実体がどのようにしてどのように索引付けされているかを確認します.
始めるためには、私のアプリケーションが接続できるように、エラスティックサーチを実行する必要があります.
つの単純なオプションは
私はすでに私のMacにインストールされているDockerとDockerを持っているのでDocker Desktop ). を作成する
elasticsearch.yml
ファイルを作成し、Dockerを使用してエラスティックサーチコンテナを起動しますクリエイト
elasticsearch.yml
インsrc/main/docker
cd spring-data-elasticsearch-example
mkdir -p src/main/docker && touch src/main/docker/elasticsearch.yml
次のコンテンツを使用します.version: '2'
services:
my-elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.9.2
container_name: my-elasticsearch
# volumes:
# - ~/data/my-elasticsearch/:/usr/share/elasticsearch/data/
ports:
- 9200:9200
environment:
- 'ES_JAVA_OPTS=-Xms1024m -Xmx1024m'
- 'discovery.type=single-node'
エラスティックサーチコンテナを起動するdocker-compose -f src/main/docker/elasticsearch.yml up -d
FlashSearchは、現在、実行中ですhttp://localhost:9200/ 詳細についてはdocumentation
インデックス検索データ
私には、以下のサンプル実体があります.私はすべての書籍や著者のインデックスをインデックスとして'本と呼ばれる.
エンティティを作成し、インデックスを作成する方法を定義します
@Getter
@Setter
@Accessors(chain = true)
@EqualsAndHashCode
@ToString
@Document(indexName="books")
public class Book {
@Id
private String id;
@MultiField(
mainField = @Field(type = FieldType.Text, fielddata = true),
otherFields = {
@InnerField(suffix = "raw", type = FieldType.Keyword)
}
)
private String name;
@Field(type = FieldType.Text)
private String summary;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Object)
private List<Author> authors;
}
ブックエンティティでは、@ document、@ field、@ multifieldのような別の注釈を使用して、その実体がどのようにしてどのように索引付けされているかを確認します.
@Getter
@Setter
@Accessors(chain = true)
@EqualsAndHashCode
@ToString
@Document(indexName="books")
public class Book {
@Id
private String id;
@MultiField(
mainField = @Field(type = FieldType.Text, fielddata = true),
otherFields = {
@InnerField(suffix = "raw", type = FieldType.Keyword)
}
)
private String name;
@Field(type = FieldType.Text)
private String summary;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Object)
private List<Author> authors;
}
@Document(indexName="books")
私はインデックスとして本を格納したいことを示しますbooks
. 既定では、インデックスのマッピングがJavaアプリケーションを起動するように作成されます.@ドキュメント注釈には多くの属性があります.詳細は公式に見ることができますdocumentation @Id
: アイデンティティの目的のために使用される、これはIDによって本を検索するか、エラスティック検索で既存の本を更新するのに役立ちます.@Field
: 文字列やブール値などのフィールドが含まれているデータの種類を指定するために使用します.データ型のリストはmapping-types ドキュメント.その上、analyzer , searchAnalyzer , and normalizer カスタマイズできます.弾性検索でstandard analyzer デフォルトのアナライザです.
summary
テキスト型とprice
ダブルタイプです.name
テキストフィールドとキーワードフィールドの両方にインデックスを付けます@MultiField
注釈.メインText
その間、フィールドは全文検索のために分析されます@InnerField
raw
is Keyword
これはエラスティックサーチのままで、ソートに使用することができます.以来raw
内部のフィールドは、我々はそれにアクセスすることができますname.raw
. authors
入れ子になったJSONオブジェクトとしてインデックス付けされます.@Getter
@Setter
@Accessors(chain = true)
@EqualsAndHashCode
@ToString
public class Author {
@Id
private String id;
@MultiField(
mainField = @Field(type = FieldType.Text, fielddata = true),
otherFields = {
@InnerField(suffix = "raw", type = FieldType.Keyword)
}
)
private String name;
}
著者実体は他の入れ子になったオブジェクトe . g接触を含むことができます、しかし、このブログ柱のために、私はそれを単純に保ちます.本の名前と同様.
Author's name
もインデックスText
(メインフィールド)およびキーワードraw
) だから我々は検索することができますauthor.name
フィールド、フィルタ、ソートauthor.name.raw
フィールド.クライアントの設定
次のステップは、私の実行しているエラスティックスセラピーへの接続を設定し、リポジトリを作成して、インデックスを検索し、本を検索することです.
@Configuration
@EnableElasticsearchRepositories(
basePackages = "dev.vuongdang.springdataelasticsearchexample.repository"
)
public class ElasticSearchConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
しかし、より高いレベルの抽象化を設定する必要がありますElasticsearch Repositories
は通常、アプリケーションで使用されます.@EnableElasticsearchRepositories
は、dev.vuongdang.springdataelasticsearchexample.repository
パッケージ.Spring Data AnalySearchのおかげで、インターフェイスを定義でき、実装が自動的に処理されます.上記のclienconfigurationは、ssl、connectおよびsocket timeout、headers、および他のパラメータのオプションを設定することができます.例えば、
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.useSsl()
.withConnectTimeout(Duration.ofSeconds(5))
.withSocketTimeout(Duration.ofSeconds(3))
.withBasicAuth(username, password);
上記のクライアント構成はlocalhost:9200
. あなたが使うならばapp.bonsai.io
設定は次のようになります.final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("sass-testing-1537538524.eu-central-1.bonsaisearch.net:443")
.usingSsl()
.withBasicAuth("<username>", "<password>")
.build();
リポジトリの作成
/**
* Define the repository interface. The implementation is done by Spring Data Elasticsearch
*/
public interface BookSearchRepository extends ElasticsearchRepository<Book, String> {
List<Book> findByAuthorsNameContaining(String name);
}
リポジトリの使い方を説明するテストを作成しましょう.私は3冊を作成し、これらの操作を正しく実行することを確認します.@SpringBootTest
class BookServiceTest {
@Autowired
private BookService bookService;
@Autowired
private BookSearchRepository bookSearchRepository;
@Autowired
private ElasticsearchOperations elasticsearchOperations;
public static final String BOOK_ID_1 = "1";
public static final String BOOK_ID_2 = "2";
public static final String BOOK_ID_3 = "3";
private Book book1;
private Book book2;
private Book book3;
@BeforeEach
public void beforeEach() {
// Delete and recreate index
IndexOperations indexOperations = elasticsearchOperations.indexOps(Book.class);
indexOperations.delete();
indexOperations.create();
indexOperations.putMapping(indexOperations.createMapping());
// add 2 books to elasticsearch
Author markTwain = new Author().setId("1").setName("Mark Twain");
book1 = bookSearchRepository
.save(new Book().setId(BOOK_ID_1).setName("The Mysterious Stranger")
.setAuthors(singletonList(markTwain))
.setSummary("This is a fiction book"));
book2 = bookSearchRepository
.save(new Book().setId(BOOK_ID_2).setName("The Innocents Abroad")
.setAuthors(singletonList(markTwain))
.setSummary("This is a special book")
);
book3 = bookSearchRepository
.save(new Book().setId(BOOK_ID_3).setName("The Other Side of the Sky").setAuthors(
Arrays.asList(new Author().setId("2").setName("Amie Kaufman"),
new Author().setId("3").setName("Meagan Spooner"))));
}
/**
* Read books by id and ensure data are saved properly
*/
@Test
void findById() {
assertEquals(book1, bookSearchRepository.findById(BOOK_ID_1).orElse(null));
assertEquals(book2, bookSearchRepository.findById(BOOK_ID_2).orElse(null));
assertEquals(book3, bookSearchRepository.findById(BOOK_ID_3).orElse(null));
}
@Test
public void query() {
List<Book> books = bookSearchRepository.findByAuthorsNameContaining("Mark");
assertEquals(2, books.size());
assertEquals(book1, books.get(0));
assertEquals(book2, books.get(1));
}
}
にbeforeEach
私はインデックスを再作成し、すべてのテストは、新鮮な新しいデータを持っていることを確認する3冊の本を挿入します.あなたは行くことができますlocalhost:9200/books/_search すべてのインデックス帳を参照してください.
またはlocalhost:9200/books/_mapping 各フィールドの詳細なマッピングを参照してください.
検索とフィルタ
インBookSearchRepository
, 私はメソッドを名前付けすることができます、そして、それは自動的に弾性検索JSON質問に解決されます.もう一つの方法はJSONクエリを定義することです@Query
注釈.例えば、
@Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
Page<Book> findByName(String name, Pageable pageable);
これらは単純なクエリーにとって素晴らしいですが、実際には、通常、エンドユーザーに検索フィールド、いくつかのフィルタ、および並べ替えを提供します.これを達成するために、私は、その柔軟性のためにビルトイン・クエリビルダーを使用するのを好みます.
マルチフィールド、フィルタリングおよび並べ替えを検索するための複雑なクエリを形成するクエリビルダを使用する方法を説明するブックサービスを作成しましょう
@Service
public class BookService {
@Getter
@Setter
@Accessors(chain = true)
@ToString
public static class BookSearchInput {
private String searchText;
private BookFilter filter;
}
@Getter
@Setter
@Accessors(chain = true)
@ToString
public static class BookFilter {
private String authorName;
}
@Autowired
private ElasticsearchOperations operations;
public SearchPage<Book> searchBooks(BookSearchInput searchInput, Pageable pageable) {
// query
QueryBuilder queryBuilder;
if(searchInput == null || isEmpty(searchInput.getSearchText())) {
// search text is empty, match all results
queryBuilder = QueryBuilders.matchAllQuery();
} else {
// search text is available, match the search text in name, summary, and authors.name
queryBuilder = QueryBuilders.multiMatchQuery(searchInput.getSearchText())
.field("name", 3)
.field("summary")
.field("authors.name")
.fuzziness(Fuzziness.ONE) //fuzziness means the edit distance: the number of one-character changes that need to be made to one string to make it the same as another string
.prefixLength(2);//The prefix_length parameter is used to improve performance. In this case, we require that the first three characters should match exactly, which reduces the number of possible combinations.;
}
// filter by author name
BoolQueryBuilder filterBuilder = boolQuery();
if(searchInput.getFilter() != null && isNotEmpty(searchInput.getFilter().getAuthorName())){
filterBuilder.must(termQuery("authors.name.raw", searchInput.getFilter().getAuthorName()));
}
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(queryBuilder)
.withFilter(filterBuilder)
.withPageable(pageable)
.build();
SearchHits<Book> hits = operations.search(query, Book.class);
return SearchHitSupport.searchPageFor(hits, query.getPageable());
}
}
この本サービスをチェックするには、以上のテストメソッドを追加する
@Test
void searchBook() {
// Define page request: return the first 10 results. Sort by book's name ASC
Pageable pageable = PageRequest.of(0, 10, Direction.ASC, "name.raw");
// Case 1: search all books: should return 3 books
assertEquals(3, bookService.searchBooks(new BookSearchInput(), pageable)
.getTotalElements());
// Case 2: filter books by author Mark Twain: Should return [book2, book1]
SearchPage<Book> booksByAuthor = bookService.searchBooks(
new BookSearchInput().setFilter(new BookFilter().setAuthorName("Mark Twain")),
pageable); // sort by book name asc
assertEquals(2, booksByAuthor.getTotalElements());
Iterator<SearchHit<Book>> iterator = booksByAuthor.iterator();
assertEquals(book2, iterator.next().getContent()); // The Innocents Abroad
assertEquals(book1, iterator.next().getContent()); // The Mysterious Stranger
// Case 3: search by text 'special': Should return book 2 because it has summary containing 'special'
// one typo in the search text: (specila) is accepted thanks to `fuziness`
SearchPage<Book> specialBook = bookService
.searchBooks(new BookSearchInput().setSearchText("specila"), pageable);// book 2
assertEquals(1, specialBook.getTotalElements());
assertEquals(book2, specialBook.getContent().iterator().next().getContent()); // The Innocents Abroad
}
ご了承くださいCase 3
上に、検索テキストはspecila
代わりに.しかし、私がセットしたので、それは予想通りに働きます.fuzziness(Fuzziness.ONE)
クエリビルダで.
ログ記録
JSONクエリを開発環境にログオンし、適切なクエリを作成するのに便利です.このログは、application.properties
logging.level.org.springframework.data.elasticsearch.client.WIRE=trace
今私が走るときsearchBook
テストメソッドでは、以下のようにログファイルのエラスティックサーチクエリを見ることができます.
{
"from": 0,
"size": 10,
"query": {
"multi_match": {
"query": "special",
"fields": [
"authors.name^1.0",
"name^3.0",
"summary^1.0"
],
"type": "best_fields",
"operator": "OR",
"slop": 0,
"fuzziness": "1",
"prefix_length": 2,
"max_expansions": 50,
"zero_terms_query": "NONE",
"auto_generate_synonyms_phrase_query": true,
"fuzzy_transpositions": true,
"boost": 1.0
}
},
"post_filter": {
"bool": {
"adjust_pure_negative": true,
"boost": 1.0
}
},
"version": true,
"sort": [
{
"name.raw": {
"order": "asc"
}
}
]
}
結論
このブログ記事では、次のトピックを取り上げます
@Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
Page<Book> findByName(String name, Pageable pageable);
@Service
public class BookService {
@Getter
@Setter
@Accessors(chain = true)
@ToString
public static class BookSearchInput {
private String searchText;
private BookFilter filter;
}
@Getter
@Setter
@Accessors(chain = true)
@ToString
public static class BookFilter {
private String authorName;
}
@Autowired
private ElasticsearchOperations operations;
public SearchPage<Book> searchBooks(BookSearchInput searchInput, Pageable pageable) {
// query
QueryBuilder queryBuilder;
if(searchInput == null || isEmpty(searchInput.getSearchText())) {
// search text is empty, match all results
queryBuilder = QueryBuilders.matchAllQuery();
} else {
// search text is available, match the search text in name, summary, and authors.name
queryBuilder = QueryBuilders.multiMatchQuery(searchInput.getSearchText())
.field("name", 3)
.field("summary")
.field("authors.name")
.fuzziness(Fuzziness.ONE) //fuzziness means the edit distance: the number of one-character changes that need to be made to one string to make it the same as another string
.prefixLength(2);//The prefix_length parameter is used to improve performance. In this case, we require that the first three characters should match exactly, which reduces the number of possible combinations.;
}
// filter by author name
BoolQueryBuilder filterBuilder = boolQuery();
if(searchInput.getFilter() != null && isNotEmpty(searchInput.getFilter().getAuthorName())){
filterBuilder.must(termQuery("authors.name.raw", searchInput.getFilter().getAuthorName()));
}
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(queryBuilder)
.withFilter(filterBuilder)
.withPageable(pageable)
.build();
SearchHits<Book> hits = operations.search(query, Book.class);
return SearchHitSupport.searchPageFor(hits, query.getPageable());
}
}
@Test
void searchBook() {
// Define page request: return the first 10 results. Sort by book's name ASC
Pageable pageable = PageRequest.of(0, 10, Direction.ASC, "name.raw");
// Case 1: search all books: should return 3 books
assertEquals(3, bookService.searchBooks(new BookSearchInput(), pageable)
.getTotalElements());
// Case 2: filter books by author Mark Twain: Should return [book2, book1]
SearchPage<Book> booksByAuthor = bookService.searchBooks(
new BookSearchInput().setFilter(new BookFilter().setAuthorName("Mark Twain")),
pageable); // sort by book name asc
assertEquals(2, booksByAuthor.getTotalElements());
Iterator<SearchHit<Book>> iterator = booksByAuthor.iterator();
assertEquals(book2, iterator.next().getContent()); // The Innocents Abroad
assertEquals(book1, iterator.next().getContent()); // The Mysterious Stranger
// Case 3: search by text 'special': Should return book 2 because it has summary containing 'special'
// one typo in the search text: (specila) is accepted thanks to `fuziness`
SearchPage<Book> specialBook = bookService
.searchBooks(new BookSearchInput().setSearchText("specila"), pageable);// book 2
assertEquals(1, specialBook.getTotalElements());
assertEquals(book2, specialBook.getContent().iterator().next().getContent()); // The Innocents Abroad
}
JSONクエリを開発環境にログオンし、適切なクエリを作成するのに便利です.このログは、
application.properties
logging.level.org.springframework.data.elasticsearch.client.WIRE=trace
今私が走るときsearchBook
テストメソッドでは、以下のようにログファイルのエラスティックサーチクエリを見ることができます.{
"from": 0,
"size": 10,
"query": {
"multi_match": {
"query": "special",
"fields": [
"authors.name^1.0",
"name^3.0",
"summary^1.0"
],
"type": "best_fields",
"operator": "OR",
"slop": 0,
"fuzziness": "1",
"prefix_length": 2,
"max_expansions": 50,
"zero_terms_query": "NONE",
"auto_generate_synonyms_phrase_query": true,
"fuzzy_transpositions": true,
"boost": 1.0
}
},
"post_filter": {
"bool": {
"adjust_pure_negative": true,
"boost": 1.0
}
},
"version": true,
"sort": [
{
"name.raw": {
"order": "asc"
}
}
]
}
結論
このブログ記事では、次のトピックを取り上げます
Reference
この問題について(スプリングデータのエラスティックサーチ), 我々は、より多くの情報をここで見つけました https://dev.to/vuongddang/getting-started-with-spring-data-elasticsearch-198hテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol