Elasticsearch与Solr类似,同样是一个基于Lucene的开源的分布式搜索引擎。当年由于Lucene的Java API比较难用,于是Shay Banon就开发 出一个叫作Compass的框架来对Lucene进行封装。Compass框架用起来十分方便,后来发现在2009年之后,Compass项目就不更新了。因为Shay Banon用Elasticsearch取代了Compass。由于Compass只是一个Java框架,所以必须掌握Java编程才能使用Compass;而Elasticsearch则是一个独立应用,它提供了RESTful的操作接口,因此不管用什么编程语言, 即使不会编程,也可使用Elasticsearch,只要会用Postman或curl发送请求即可。
(1)登录官网(地址)下载Elasticsearch,版本对照关系(地址)
Elasticsearch启动报错:
warning: ignoring JAVA_HOME=C:\Program Files\Java\jdk1.8.0_191; using bundled JDK。原因是JDK和Elasticsearch版本不对应。warning: usage of JAVA_HOME is deprecated, use ES_JAVA_HOME。原因是 elasticsearch 7系列版本以上都是自带的jdk,可以在es的bin目录下找到elasticsearch-env.bat这个文件,配置es的jdk。官方推荐使用es自带的jdk。(2)下载后得到Elasticsearch
➢ bin:该目录下包含Elasticsearch的各种工具命令。
➢ config:该目录下包含Elasticsearch的各种配置文件,尤其是elasticsearch.yml和jvm.options两个配置文件很重要,其中elasticsearch.yml用于配置Elasticsearch,jvm.options用于配置JVM的堆内存,垃圾回收机制等选项。
➢ jdk:该目录下包含一份最新的JDK。
➢ lib:该目录下保存Elasticsearch的核心JAR包及依赖的第三方JAR包。
➢ logs:日志目录。
➢ plugins:Elasticsearch的插件目录。(3)配置环境变量
JAVA_HOME:E:\Java\jdk-1.11
PATH:E:\Elasticsearch(4)修改elasticsearch.yml文件
cluster.name: my-application #配置集群名
node.name: node-1 #配置节点名
#network.host: 192.168.0.1 #配置Elasticsearch绑定的IP地址
#http.port: 9200 #监听端口Elasticsearch的集群配置非常简单,在同一个局域网内的多个节点(多个Elasticsearch服务器)上只要指定了相同的 cluster.name,它们都会自动加入同一个集群。因此,一个节点只要设置了cluster.name就能加入集群,成为集群的一部分。
(5)启动Elasticsearch
访问(http://localhost:9200)发送GET请求,输出name,cluster_name就是前面配置的节点名和集群名,也可以看到Elasticsearch的版本信息。则表明Elasticsearch启动成功。
如果想就这样使用Elasticsearch,当然也是可以的,但很明显安全性不够,下面为Elasticsearch启用SSL支持,以及配置用户名,密码。
(1)修改config目录下的elasticsearch.yml文件,添加内容。
# ---------------------------------- Security ----------------------------------
xpack.security.enabled: true(2)启动Elasticsearch,打开新CMD然后设置密码。
Elasticsearch内置了用于不同目的几个用户,故此处要依次为每个用户设置密码,每个密码都要设置两次。
- elastic:超级用户。
- kibana:Kibana通过该用户连接Elasticsearch。
- logstash_system:Logstash将监控信息存储到Elasticsearch中时使用该用户。
- beats_system:Beats在Elasticsearch中存储监视信息时使用该用户。
- apm_system:APM服务器在Elasticsearch中存储监视信息时使用该用户。
- remote_monitoring_user:Metricbeat用户在Elasticsearch中收集和存储监视信息时使用该用户。
【Elastisearch、RDBMS、Solr基本对应关系】
RDBMS Elasticsearch Solr Table Index Core(Collection) row(行) Document(文档) Document(文档) column(列) Field(字段) Field(字段)
(1)添加Index
curl -k -u elastic:123456 -X PUT http://localhost:9200/test
PS:不允许使用大写
(2)查看Index
curl -k -u elastic:123456 http://localhost:9200/_cat/indices
结果以点 (.) 开头的Index是Elasticsearch内置的Index。
(3)删除Index
curl -k -u elastic:123456 -X DELETE http://localhost:9200/test
(4)替换默认分词器:Elasticsearch 内置了一些分词器,但这些分词器对中文支持并不 好,这里可选择使用 IK 分词器来处理中文分词(地址),注意版本对应关系。
elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.16.3/elasticsearch-analysis-ik-7.16.3.zip
安装完成后,IK分词器会被自动安装到Elasticsearch的plugins目录下,还会在config目录下创建一个analysis-ik子目录,用于保存IK分词器的配置文件。为Elasticsearch安装任何插件后都需要重启Elasticsearch服务器来加载IK分词器。然后在当前目录下(命令行提示符“>”前的路径)下创建一个配置文件:
{"settings": {"analysis": {"analyzer": {"default": {"tokenizer": "ik_max_word"}}}}
}上面文件指定为Index设置默认的中文分词器:ik_max_word,该分词器由IK分词器提供,它还提供了一个名为“ik_smart”的中文分词器。
curl -k -u elastic:123456 -X PUT http://localhost:9200/test -d @test.json -H "Content-Type:application/json"-H:设置Content-Type请求头的值为“application/json”;
-d:用于读取配置文件的内容作为请求数据。
- 在命令行所在的当前路径下定义一个test.json文件,该JSON文件指定使用ik_max_word分词器,text属性指定要测试分 词的文本内容。
{"analyzer": "ik_max_word","text": "Elasticsearch与Solr类似,同样是一个基于Lucene的开源的分布式搜索引擎。"
}
- 运行如下命令
curl -k -u elastic:123456 -X POST http://localhost:9200/test/_analyze?pretty=true -d @test.json -H "Content-Type:application/json"{"tokens" : [{"token" : "elasticsearch","start_offset" : 0,"end_offset" : 13,"type" : "ENGLISH","position" : 0},{"token" : "与","start_offset" : 13,"end_offset" : 14,"type" : "CN_CHAR","position" : 1},......]
}每个词都被称作一个token,每个token都对应如下属性:
- start_offset:起始位置。
- end_offset:结束位置。
- type:类型。
- position:词的位置。
(1)添加文档:在命令行所在的当前路径下定义一个book.json文件,内容如下:
{"name": "呐喊","description": "生动地塑造了狂人、孔乙己、阿Q等一批不朽的艺术形象,深刻反映了19世纪末到20世纪20年代间中国社会生活的现状,有力揭露和鞭挞了封建旧恶势力,表达了作者渴望变革,为时代呐喊,希望唤醒国民的思想。","price": 35
}
curl -k -u elastic:123456 -X POST http://localhost:9200/test/book/1 -d @book.json -H "Content-Type:application/json"
{"_index":"test","_type":"book","_id":"1","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":0,"_primary_term":1}book:就是type
1:就是被添加文档的ID,这个ID其实是字符串,因此也可指定为“abc”(2)查看Index下所有文档:命令中的pretty=true是一个很常见的参数,用于让Elasticsearch生成格式良好的响应。从该命令可以看出,查看Index下的所有文档,只要在该Index后添加“_search”即可。
curl -k -u elastic:123456 http://localhost:9200/test/_search?pretty=true
{"took" : 717,"timed_out" : false,"_shards" : {"total" : 1,"successful" : 1,"skipped" : 0,"failed" : 0},"hits" : {"total" : {"value" : 1,"relation" : "eq"},"max_score" : 1.0,"hits" : [{"_index" : "test","_type" : "book","_id" : "1","_score" : 1.0,"_source" : {"name" : "呐喊","description" : "生动地塑造了狂人、孔乙己、阿Q等一批不朽的艺术形象,深刻反映了19世纪末到20世纪20年代间中国社会生活的现状,有力揭露和鞭挞了封建旧恶势力,表达了作者渴望变革,为时代呐喊,希望唤醒国民的思想。","price" : 35}}]}
}(3)查看Index下指定ID的文档:
curl -k -u elastic:123456 http://localhost:9200/test/book/1?pretty=true
{"_index" : "test","_type" : "book","_id" : "1","_version" : 1,"_seq_no" : 0,"_primary_term" : 1,"found" : true,"_source" : {"name" : "呐喊","description" : "生动地塑造了狂人、孔乙己、阿Q等一批不朽的艺术形象,深刻反映了19世纪末到20世纪20年代间中国社会生活的现状,有力揭露和鞭挞了封建旧恶势力,表达了作者渴望变革,为时代呐喊,希望唤醒国民的思想。","price" : 35}
}(4)删除指定ID的文档:
curl -k -u elastic:123456 -X DELETE http://localhost:9200/test/book/1
{"_index":"test","_type":"book","_id":"1","_version":2,"result":"deleted","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":1,"_primary_term":1}(5)全文检索:执行全文检索同样是向 Index 后加“_search”的 URL 地址发送请求,只不过需要添加 JSON格式的请求数据而已。
{"query": {"match": {"description": "孔乙己"}}
}Elasticsearch自己的查询语法(地址),它要求查询参数满足JSON格式,其中query属性的值才是实际的查询参数:
- match 表明使用普通关键词查询
- regexp 表示正则表达式查询
- fuzzy表示模糊查询
- prefix 表示前缀查询
- wildcard表示通配符查询
- range表示范围查询
- query_string定义查询字符串
curl -k -u elastic:123456 http://localhost:9200/test/_search?pretty=true -d @search.json -H "Content-Type:application/json"
(6)根据查询删除:如果要根据查询条件来删除文档,只要向Index后加“_delete_by_que ry”的URL地址发送POST请求即可。
curl -k -u elastic:123456 -X POST http://localhost:9200/test/_delete_by_query?pretty=true -d @search.json -H "Content-Type:application/json"
如果打算使用 Elasticsearch 自带的 RestClient 来操作 Elasticsearch,甚至不需要添加 spring-boot-starter-data-elasticsearch依赖,则直接使用最基本的spring-boot-starter依赖和Elasticsearch提供的RestClient依赖即可。
Elasticsearch官方提供的RestClient分为两种:
- 高级RestClient(推荐):开发者面向Index,文档等高层次的API编程,因此更加简单,方便。通常建议以高级 RestClient 为主,只有当高级 RestClient 实在搞不定时,才考虑使用低级RestClient。
- 低级RestClient:开发者直接面向底层 RESTful 接口编程,发送最原始的请求参数,Elasticsearch 服务器也返回最原始的响应,这种方式需要开发者自行处理请求,响应的序列化和反序列化,相当麻烦,但灵活性最好。
Spring Boot只要检测到类路径下有 elasticsearch-rest-high-level-client 依赖(无须使用Spring Data Elasticsearch),Spring Boot 就会在容器中创建一个自动配置的 RestHighLevelClient,它就是Elasticsearch的高级RestClient。如果想使用低级RestClient,只要调用它的 getLowLevelClient() 方法即可返回 ResLowLevelClient,它就是Elasticsearch的低级RestClient。
如果需要对 RestClient 进行定制,则可在容器中部署一个或多个 RestClientBuilderCustomizerBean,该 Bean 的 customize() 方法即可对 RestClientBuilder、HttpAsyncClientBuilder、RequestConfig.Builder进行定制, 这些定制最终将作用于Elasticsearch的RestClient。
当容器中有了自动配置的RestHighLevelClient之后,容器可通过依赖注入将它注入其他任何组件(主要是DAO组件),接下来该组件可通过它的如下方法来操作Elasticsearch索引库:
- count(CountRequest countRequest,RequestOptions options): 查询符合条件的文档数量。
- countAsync(CountRequest countRequest,RequestOptions option s,ActionListener
listener): 以异步方式查询符合条件的文档数量,其中 listener 参数负责处理异步查询的结果。- delete(DeleteRequest deleteRequest,RequestOptions options): 根据ID删除文档。
- deleteAsync(DeleteRequest deleteRequest,RequestOptions option s,ActionListener
listener): 以异步方式根据ID删除文档,其中listener参数负责处理异步删除的结果。- deleteByQuery(DeleteByQueryRequest deleteByQueryRequest,RequestOptions options):删除符合查询条件的文档。
- deleteByQueryAsync(DeleteByQueryRequest deleteByQueryRequest,RequestOptions options,ActionListener
listener): 以异步方式删除符合查询条件的文档,其中listener参数负责处理异步删除的结果。- exists(GetRequest getRequest,RequestOptions options):判断指定ID对应的文档是否存在。
- existsAsync(GetRequest getRequest,RequestOptions options,ActionListener
listener): 以异步方式判断指定ID对应的文档是否存在。- get(GetRequest getRequest,RequestOptions options):根据ID 获取文档。
- getAsync(GetRequest getRequest,RequestOptions options,ActionListener
listener): 以异步方式根据ID获取文档。- index(IndexRequest indexRequest,RequestOptions options):创建索引或文档。
- indexAsync(IndexRequest indexRequest,RequestOptions options,ActionListener
listener): 以异步方式创建索引或文档。- mget(MultiGetRequest multiGetRequest,RequestOptions option s):根据多个ID获取多个文档。
- mgetAsync(MultiGetRequest multiGetRequest,RequestOptions options,ActionListener
listener): 以异步方式根据多个ID获取多个文档。- msearch(MultiSearchRequest multiSearchRequest,RequestOptions options):根据多个查询条件返回文档。
- msearchAsync(MultiSearchRequest multiSearchRequest,RequestOptions options,ActionListener
listener): 以异步方式根据多个查询条件返回文档。- search(SearchRequest searchRequest,RequestOptions option s):查询文档。
- searchAsync(SearchRequest searchRequest,RequestOptions options,ActionListener
listener): 以异步方式查询文档。- update(UpdateRequest updateRequest , RequestOptions options):根据ID更新文档。
- updateAsync(UpdateRequest updateRequest,RequestOptions options,ActionListener
listener): 以异步方式根据ID更新文档。- updateByQuery(UpdateByQueryRequest updateByQueryRequest, RequestOptions options):更新符合条件的所有文档。
- updateByQueryAsync(UpdateByQueryRequest updateByQueryRequest,RequestOptions options,ActionListener
listener): 以异步方式更新符合条件的所有文档。此外,它还提供了大量 xxx() 方法来返回对应的 XxxClient,如 asyncSearch() 方法返回AsyncSearchClient,cluster() 方法返回 ClusterClient,eql() 方法返回 EqlClient,indices()方法返回IndicesClient……这些XxxClient又提供了大量的方法来执行相应的操作。
(1)添加 elasticsearch-rest-high-level-client 依赖
org.elasticsearch.client elasticsearch-rest-high-level-client (2)修改 application.properties
# 指定Elasticsearch服务器的地址
spring.elasticsearch.rest.uris=http://127.0.0.1:9200
spring.elasticsearch.rest.read-timeout=10s
# 配置用户名和密码
spring.elasticsearch.rest.username=elastic
spring.elasticsearch.rest.password=123456(3)Controller
package com.example.springboot.Controller;import org.elasticsearch.Assertions;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.IOException;@RestController
public class HelloController {@Autowiredprivate RestHighLevelClient restHighClient;@RequestMapping("CreateIndex")public boolean testCreateIndex() throws IOException {// 定义创建Index的设置,和前面test.json文件的内容相同// 设置该Index的默认分词器是ik_max_wordvar json = "{\n" +" \"settings\": {\n" +" \"analysis\": {\n" +" \"analyzer\": {\n" +" \"default\": {\"tokenizer\": \"ik_max_word\"}\n" +" }\n" +" }\n" +" }\n" +"}\n";var indexRequest = new CreateIndexRequest("books").source(json, XContentType.JSON);AcknowledgedResponse resp = restHighClient.indices().create(indexRequest, RequestOptions.DEFAULT);return resp.isAcknowledged();}@RequestMapping("DeleteIndex")public boolean testDeleteIndex(String index) throws IOException {var indexRequest = new DeleteIndexRequest(index);AcknowledgedResponse resp = restHighClient.indices().delete(indexRequest, RequestOptions.DEFAULT);return resp.isAcknowledged();}@RequestMapping("SaveDocument")public void testSaveDocument(String index, Integer id, String name, String description, Double price) throws IOException {IndexRequest request = new IndexRequest(index).id(id + "").source("name", name, "description", description, "price", price);IndexResponse resp = restHighClient.index(request, RequestOptions.DEFAULT);System.out.println(resp);}@RequestMapping("GetDocument")public void testGetDocument(String index, Integer id) throws IOException {var request = new GetRequest(index).id(id + "");GetResponse resp = restHighClient.get(request, RequestOptions.DEFAULT);System.out.println(resp.getSource());}@RequestMapping("Search")public void testSearch(String index, String field, String term) throws IOException {var builder = new SearchSourceBuilder();if (term != null && term.contains("*")) {builder.query(QueryBuilders.wildcardQuery(field, term));} else {builder.query(QueryBuilders.matchQuery(field, term));}var request = new SearchRequest(index).source(builder);SearchResponse resp = restHighClient.search(request, RequestOptions.DEFAULT);SearchHits hits = resp.getHits();hits.forEach(System.out::println);}@RequestMapping("DeleteDocument")public void testDeleteDocument(String index, Integer id) throws IOException {var request = new DeleteRequest(index).id(id + "");DeleteResponse resp = restHighClient.delete(request, RequestOptions.DEFAULT);System.out.println(resp.status());}
}testSearch()方法测试全文检索功能,该方法对传入的 term 关键 词进行判断,如果该关键词包含星号( * ),就使用通配符查询(Wildc ard Query),否则就使用普通查询。
由于Elasticsearch官方并未提供反应式的RestClient,因此Spring Data Elasticsearch额外补充了一个 ReactiveElasticsearchClient,用于提供反应式 API 支持。ReactiveElasticsearchClient 相当于RestHighLevelClient的反应式版本,因此它们二者的功能基本相似。只不过在调用ReactiveElasticsearchClient的方法时无须传入RequestOptions参数,且其方法的返回值都是Flux或Mono (反应式API),因此下面程序使用了blockOptional(),toIterable() 来保证反应式API能执行完成。
ReactiveElasticsearchClient 是基于 WebFlux 的 WebClient 的,因此如果要使用反应式的RestClient,还需要添加Spring WebFlux依赖。
org.springframework.boot spring-boot-starter-data-elasticsearch org.springframework.boot spring-boot-starter-webflux
# 指定Elasticsearch服务器的地址
spring.data.elasticsearch.client.reactive.endpoints=127.0.0.1:9200
spring.data.elasticsearch.client.reactive.use-ssl=false
spring.elasticsearch.rest.read-timeout=10s
# 配置用户名和密码
spring.elasticsearch.rest.username=elastic
spring.elasticsearch.rest.password=123456当容器中有了自动配置的ReactiveElasticsearchClient之后,接下来即可将它依赖注入其他任何组件。
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.delete.*;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.index.*;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.*;import java.io.IOException;
import java.util.function.Consumer;@RestController
public class HelloController {@Autowiredprivate ReactiveElasticsearchClient reactiveClient;@RequestMapping("CreateIndex")public boolean testCreateIndex() throws IOException {// 定义创建Index的设置,和前面test.json文件的内容相同// 设置该Index的默认分词器是ik_max_wordvar json = "{\n" +" \"settings\": {\n" +" \"analysis\": {\n" +" \"analyzer\": {\n" +" \"default\": {\"tokenizer\": \"ik_max_word\"}\n" +" }\n" +" }\n" +" }\n" +"}\n";CreateIndexRequest indexRequest = new CreateIndexRequest("books").source(json, XContentType.JSON);Monoresp = reactiveClient.indices().createIndex(indexRequest);return resp.blockOptional().isPresent();}@RequestMapping("DeleteIndex")public boolean testDeleteIndex(String index) throws IOException {var indexRequest = new DeleteIndexRequest(index);Mono resp = reactiveClient.indices().deleteIndex(indexRequest);return resp.blockOptional().isPresent();}@RequestMapping("SaveDocument")public void testSaveDocument(String index, Integer id, String name, String description, Double price) throws IOException {IndexRequest request = new IndexRequest(index).id(id + "").source("name", name, "description", description, "price", price);Mono resp = reactiveClient.index(request);resp.blockOptional().ifPresent(System.out::println);}@RequestMapping("GetDocument")public void testGetDocument(String index, Integer id) throws IOException {var request = new GetRequest(index).id(id + "");Mono resp = reactiveClient.get(request);resp.blockOptional().ifPresent(e -> System.out.println(e.getSource()));}@RequestMapping("Search")public void testSearch(String index, String field, String term) throws IOException {var builder = new SearchSourceBuilder();if (term != null && term.contains("*")) {builder.query(QueryBuilders.wildcardQuery(field, term));} else {builder.query(QueryBuilders.matchQuery(field, term));}var request = new SearchRequest(index).source(builder);Flux resp = reactiveClient.search(request);resp.toIterable().forEach(System.out::println);}@RequestMapping("DeleteDocument")public void testDeleteDocument(String index, Integer id) throws IOException {var request = new DeleteRequest(index).id(id + "");Mono resp = reactiveClient.delete(request);resp.blockOptional().ifPresent(e -> System.out.println(e.status()));}
}
- 如果Spring Boot在类加载路径下检测到Spring Data Elasticsearch,Spring Boot就会在容器中自动配置一个ElasticsearchRestTemplate(注意不是ElasticsearchTemplate,ElasticsearchTemplate已经过时了)。ElasticsearchRestTemplate底层依赖于容器中自动配置的RestHighLevelClient。
- 如果Spring Boot在类加载路径下同时检测到Spring Data Elasticsearch和Spring WebFlux,Spring Boot就会在容器中自动配置一个ReactiveElasticsearchTemplate。ReactiveElasticsearchTemplate底层依赖于容器中自动配置的 ReactiveElasticsearchClient。正如 ReactiveElasticsearchClient 是RestHighLevelClient的反应式版本,ReactiveElasticsearchTemplate则是ElasticsearchRestTemplate的反应式版本。
与RestHighLevelClient、ReactiveElasticsearchClient 相比,ElasticsearchRestTemplate、ReactiveElasticsearchTemplate 能以更加面向对象的方法来操作 Elasticsearch 索引库,这些Xxx Template的方法操作的是实体对象,而Spring Data Elasticsearch会自动将面向实体对象的操作转化为对索引库的操作。
由于Spring Data是高层次的抽象,而Spring Data Elasticsearch只是属于底层的具体实现,因此Spring Data Elasticsearch也提供了与前面Spring Data完全一致的操作。
Spring Data Elasticsearch大致包括如下几方面功能:
- DAO接口只需继承CrudRepository或ReactiveCrudRepository,Spring Data Elasticsearch能为DAO组件提供实现类。
- Spring Data Elasticsearch支持方法名关键字查询,只不过Elasticsearch查询都是全文检索查询。
- Spring Data Elasticsearch同样支持DAO组件添加自定义的查询方法—通过添加额外的接口,并为额外的接口提供实现类,Spring Data Elasticsearch就能将该实现类中的方法“移植”到DAO组件中。
Spring Data Elasticsearch的 Repository 操作的数据类同样使用@Document和@Field注解修饰,其中@Document修饰的实体类被映射到文档, 使用该注解时可指定如下两个常用属性。
- indexName:指定该实体类被映射到哪个Index。
- createIndex:指定是否根据实体类创建Index。
@Field修饰的属性则被映射到索引文档的Field,使用该注解时可指定如下常用属性。
- name:指定该属性被映射到索引文档的哪个Field,如果不指定该属性,则默认基于同名映射。
- analyzer:指定该Field所使用的分词器。
- searchAnalyzer:指定对该Field执行搜索时所使用的分词器。
Maven
org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-data-elasticsearch org.springframework.boot spring-boot-starter-web application.properties
# 指定Elasticsearch服务器的地址
spring.elasticsearch.rest.uris=https://127.0.0.1:9200
spring.elasticsearch.rest.read-timeout=10s
# 配置用户名和密码
spring.elasticsearch.rest.username=elastic
spring.elasticsearch.rest.password=123456Pojo:该 Book 类使用了@Document(index Name="springboot",createIndex=true) 修饰,这说明该类被映射到名为 “books” 的Index,Spring Data Elasticsearch可根据该实体类自动创建 Index—如果该Index不存在的话。
package com.example.springboot.Pojo;import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;// createIndex指定自动创建索引
@Document(indexName = "books", createIndex = true)
public class Book {@Idprivate Integer id;@Field(analyzer = "ik_max_word", searchAnalyzer = "ik_smart")private String name;@Field(analyzer = "ik_max_word", searchAnalyzer = "ik_smart")private String description;@Field(analyzer = "ik_max_word", searchAnalyzer = "ik_smart")private Double price;//构造器、setter、getter
}Dao
package com.example.springboot.Dao;import com.example.springboot.Pojo.Book;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.repository.CrudRepository;
import java.util.List;public interface BookDao extends CrudRepository, BookCustomDao {// 方法名关键字查询List findByName(String name);List findByIdIn(List list);List findByPriceBetween(double start, double end);List findByDescriptionMatches(String descPattern);// 使用@Query定义查询语句@Query("{ \"match\": { \"?0\": \"?1\" } } ")
// @Query("{ \"query_string\": { \"query\": \"?0:?1\" } } ")ListfindByQuery1(String field, String term);
}
package com.example.springboot.Dao;import com.example.springboot.Pojo.Book;
import java.util.List;public interface BookCustomDao {ListcustomQuery1(String name, String description);
}
package com.example.springboot.Dao;import com.example.springboot.Pojo.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import java.util.ArrayList;
import java.util.List;public class BookCustomDaoImpl implements BookCustomDao {&#64;Autowiredprivate ElasticsearchRestTemplate restTemplate;&#64;Overridepublic ListcustomQuery1(String name, String description) {// 以面向对象的方式定义查询语句Criteria criteria &#61; new Criteria("name").is(name).and("description").is(description);// 创建CriteriaQueryQuery query &#61; new CriteriaQuery(criteria);SearchHits hits &#61; restTemplate.search(query, Book.class);List books &#61; new ArrayList<>();hits.forEach(hit -> books.add(hit.getContent()));return books;}
}Controller
package com.example.springboot.Controller;
import com.example.springboot.Dao.BookDao;
import com.example.springboot.Pojo.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;&#64;RestController
public class HelloController {&#64;Autowiredprivate BookDao bookDao;&#64;RequestMapping("/Save")public void testSave(Integer id, String name, String description, Double price) {var book &#61; new Book(id, name, description, price);bookDao.save(book);}&#64;RequestMapping("/Delete")public void testDelete() {// 删除id为3的Book对象bookDao.deleteById(3);}&#64;RequestMapping("/FindByName")public void testFindByName(String name) {bookDao.findByName(name).forEach(System.out::println);}&#64;RequestMapping("/FindByIdIn")public void testFindByIdIn(Integer id1, Integer id2) {bookDao.findByIdIn(List.of(id1, id2)).forEach(System.out::println);}&#64;RequestMapping("/FindByPriceBetween")public void testFindByPriceBetween(double start, double end) {bookDao.findByPriceBetween(start, end).forEach(System.out::println);}&#64;RequestMapping("/FindByDescriptionMatches")public void testFindByDescriptionMatches(String descPattern) {bookDao.findByDescriptionMatches(descPattern).forEach(System.out::println);}&#64;RequestMapping("/FindByQuery1")public void testFindByQuery1(String field, String term) {bookDao.findByQuery1(field, term).forEach(System.out::println);}&#64;RequestMapping("/CustomQuery1")public void testCustomQuery1(String name, String description) {bookDao.customQuery1(name, description).forEach(System.out::println);}
}