二十二、SpringBoot之检索-Elasticsearch

xiaoxiao2025-09-12  80

一、检索

我们的应用经常需要添加检索功能,开源的 ElasticSearch 是目前全文搜索引擎的首选。他可以快速的存储、搜索和分析海量数据。Spring Boot通过整合Spring Data ElasticSearch为我们提供了非常便捷的检索功能支持;

Elasticsearch是一个分布式搜索服务,提供Restful API,底层基于Lucene,采用多shard(分片)的方式保证数据安全,并且提供自动resharding的功能,github 等大型的站点也是采用了ElasticSearch作为其搜索服务,

二、概念

以员工文档的形式存储为例:一个文档代表一个员工数据。存储数据到 ElasticSearch 的行为叫做索引 ,但在索引一个文档之前,需要确定将文档存储在哪里。一个 ElasticSearch 集群可以包含多个索引 ,相应的每个索引可以包含多个类型。这些不同的类型存储着多个文档 ,每个文档又有多个属性 。跟mysql有类似关系: 索引-数据库类型-数据库中的表文档-表中的记录(行)属性-表中的列

三、测试ElasticSearch

使用Postman测试,Postman下载地址

《Elasticsearch 权威指南》中文版

1、索引文档

对于雇员目录,我们将做如下操作:

每个雇员索引一个文档,包含该雇员的所有信息。每个文档都将是 employee 类型 。该类型位于 索引 megacorp 内。该索引保存在我们的 Elasticsearch 集群中。 //megacorp 索引名称 //employee 类型名称 //1 特定雇员的ID PUT /megacorp/employee/1 { "first_name" : "John", "last_name" : "Smith", "age" : 25, "about" : "I love to go rock climbing", "interests": [ "sports", "music" ] } PUT /megacorp/employee/2 { "first_name" : "Jane", "last_name" : "Smith", "age" : 32, "about" : "I like to collect rock albums", "interests": [ "music" ] } PUT /megacorp/employee/3 { "first_name" : "Douglas", "last_name" : "Fir", "age" : 35, "about": "I like to build cabinets", "interests": [ "forestry" ] }

用同样的步骤索引三个雇员文档

2、检索文档

目前我们已经在 Elasticsearch 中存储了一些数据, 接下来就能专注于实现应用的业务需求了。第一个需求是可以检索到单个雇员的数据。这在 Elasticsearch 中很简单。简单地执行 一个 HTTP GET 请求并指定文档的地址——索引库、类型和ID。 使用这三个信息可以返回原始的 JSON 文档:

3、删除文档

4、检测是否存在这个文档

5、修改文档

将 HTTP 命令由 PUT 改为 GET 可以用来检索文档,同样的,可以使用 DELETE 命令来删除文档,以及使用 HEAD 指令来检查文档是否存在。如果想更新已存在的文档,只需再次 PUT 。

6、轻量搜索

一个 GET 是相当简单的,可以直接得到指定的文档。 现在尝试点儿稍微高级的功能,比如一个简单的搜索!第一个尝试的几乎是最简单的搜索了。我们使用下列请求来搜索所有雇员:

7、查询字符串 (_query-string_) 搜索

接下来,尝试下搜索姓氏为 ``Smith`` 的雇员。为此,我们将使用一个高亮 搜索,很容易通过命令行完成。这个方法一般涉及到一个 查询字符串 (_query-string_) 搜索,因为我们通过一个URL参数来传递查询信息给搜索接口:

8、使用查询表达式搜索

Query-string 搜索通过命令非常方便地进行临时性的即席搜索 ,但它有自身的局限性(参见 轻量 搜索 )。Elasticsearch 提供一个丰富灵活的查询语言叫做 查询表达式 , 它支持构建更加复杂和健壮的查询。领域特定语言 (DSL), 指定了使用一个 JSON 请求。我们可以像这样重写之前的查询所有 Smith 的搜索 :

9、更复杂的搜索

现在尝试下更复杂的搜索。 同样搜索姓氏为 Smith 的雇员,但这次我们只需要年龄大于 30 的。查询需要稍作调整,使用过滤器 filter ,它支持高效地执行一个结构化查询。

10、全文搜索

截止目前的搜索相对都很简单:单个姓名,通过年龄过滤。现在尝试下稍微高级点儿的全文搜索——一项 传统数据库确实很难搞定的任务。搜索下所有喜欢攀岩(rock climbing)的雇员:

11、短语搜索

找出一个属性中的独立单词是没有问题的,但有时候想要精确匹配一系列单词或者短语 。 比如, 我们想执行这样一个查询,仅匹配同时包含 “rock” 和 “climbing” ,并且 二者以短语 “rock climbing” 的形式紧挨着的雇员记录。为此对 match 查询稍作调整,使用一个叫做 match_phrase 的查询:

12、高亮搜索

许多应用都倾向于在每个搜索结果中 高亮 部分文本片段,以便让用户知道为何该文档符合查询条件。在 Elasticsearch 中检索出高亮片段也很容易。再次执行前面的查询,并增加一个新的 highlight 参数:

三、整合ElasticSearch

1、Jest操作ElasticSearch

引入io.searchbox/jest

Jest Apache HC Jar

<!-- https://mvnrepository.com/artifact/io.searchbox/jest --> <dependency> <groupId>io.searchbox</groupId> <artifactId>jest</artifactId> <version>5.3.4</version> </dependency> application.yml配置 spring.elasticsearch.jest.uris=http://192.168.3.224:9200 bean public class Article { @JestId //标示id是主键 private Integer id; private String author; private String title; private String content; test @Autowired JestClient jestClient; @Test public void contextLoads() { //1、给ES中索引(保存)一个文档; Article article = new Article(); article.setId(1); article.setTitle("好消息"); article.setAuthor("张三"); article.setContent("HelloWorld"); //构建一个索引功能 Index index = new Index.Builder(article).index("atguigu").type("news").build(); try { //执行 jestClient.execute(index); } catch (IOException e) { e.printStackTrace(); } } //测试搜索 @Test public void search() { //2、查询表达式 String json = "{\n" + " \"query\" : {\n" + " \"match\" : {\n" + " \"content\" : \"HelloWorld\"\n" + " }\n" + " }\n" + "}"; //构建一个搜索功能 Search search = new Search.Builder(json).addIndex("atguigu").addType("news").build(); try { //执行 SearchResult result = jestClient.execute(search); System.out.println(result.getJsonString()); } catch (IOException e) { e.printStackTrace(); } } 测试结果

2、Spring Data操作ElasticSearch

spring-data elasticsearch官方文档

引入spring-boot-starter-data-elasticsearch <!-- SpringBoot默认使用SpringData ElasticSearch模块进行操作的 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> 安装Spring Data 对应版本的ElasticSearch

如果版本不适配:

1)升级SpringBoot版本

2)安装对应版本的ES(使用docker 安装)

spring data elasticsearchelasticsearch3.1.x6.2.23.0.x5.5.02.1.x2.4.02.0.x2.2.01.3.x1.5.2 application.yml配置 spring.data.elasticsearch.cluster-name=elasticsearch spring.data.elasticsearch.cluster-nodes=192.168.3.226:9300

bean //indexName索引名 type索引类型 @Document(indexName = "atguigu",type="book") public class Book { private Integer id; private String bookName; private String author; ElasticsearchRepository public interface BookRepository extends ElasticsearchRepository<Book,Integer> { //参考官方文档 public List<Book> findByBookNameLike(String bookName); } test @Autowired BookRepository bookRepository; @Test public void test02() { // Book book = new Book(); // book.setId(1); // book.setBookName("西游记"); // book.setAuthor("吴承恩"); // bookRepository.index(book); for(Book book : bookRepository.findByBookNameLike("游")){ System.out.println(book); } } 测试结果

查询地址:192.168.3.244:9200/atguigu/book/1

四、 原理

SpringBoot默认支持两种技术来和ES交互

1、Jest(默认不生效)

@Configuration @ConditionalOnClass(JestClient.class)//需要导入Jest的工具包import io.searchbox.client.JestClient; @EnableConfigurationProperties(JestProperties.class) @AutoConfigureAfter(GsonAutoConfiguration.class) public class JestAutoConfiguration {

2、SpringData ElasticSearch(ES版本有可能不合适)

1.设置Client节点信息 @Configuration @ConditionalOnClass({ Client.class, TransportClientFactoryBean.class }) @ConditionalOnProperty(prefix = "spring.data.elasticsearch", name = "cluster-nodes", matchIfMissing = false) @EnableConfigurationProperties(ElasticsearchProperties.class) public class ElasticsearchAutoConfiguration { @Bean @ConditionalOnMissingBean public TransportClient elasticsearchClient() throws Exception { TransportClientFactoryBean factory = new TransportClientFactoryBean(); factory.setClusterNodes(this.properties.getClusterNodes()); factory.setProperties(createProperties()); factory.afterPropertiesSet(); return factory.getObject(); } 2.ElasticsearchTemplate 操作ES; @Configuration @ConditionalOnClass({ Client.class, ElasticsearchTemplate.class }) @AutoConfigureAfter(ElasticsearchAutoConfiguration.class) public class ElasticsearchDataAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnBean(Client.class) public ElasticsearchTemplate elasticsearchTemplate(Client client, ElasticsearchConverter converter) { try { return new ElasticsearchTemplate(client, converter); } catch (Exception ex) { throw new IllegalStateException(ex); } } 3.编写一个ElasticsearchRepository的子接口来操作ES; @Configuration @ConditionalOnClass({ Client.class, ElasticsearchRepository.class }) @ConditionalOnProperty(prefix = "spring.data.elasticsearch.repositories", name = "enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnMissingBean(ElasticsearchRepositoryFactoryBean.class) @Import(ElasticsearchRepositoriesRegistrar.class) public class ElasticsearchRepositoriesAutoConfiguration {

五、遇到一个问题未解决

NoNodeAvailableException[None of the configured nodes are available: [{#transport#-1}{dPDqE7YzR0-Y1skQ5H6suQ}{192.168.3.226}{192.168.3.226:9301}] ] at org.elasticsearch.client.transport.TransportClientNodesService.ensureNodesAreAvailable(TransportClientNodesService.java:347) at org.elasticsearch.client.transport.TransportClientNodesService.execute(TransportClientNodesService.java:245) at org.elasticsearch.client.transport.TransportProxyClient.execute(TransportProxyClient.java:59) at org.elasticsearch.client.transport.TransportClient.doExecute(TransportClient.java:366) at org.elasticsearch.client.support.AbstractClient.execute(AbstractClient.java:408) at org.elasticsearch.action.ActionRequestBuilder.execute(ActionRequestBuilder.java:80) at org.elasticsearch.action.ActionRequestBuilder.execute(ActionRequestBuilder.java:54) at org.springframework.data.elasticsearch.core.ElasticsearchTemplate.index(ElasticsearchTemplate.java:571) at org.springframework.data.elasticsearch.repository.support.AbstractElasticsearchRepository.save(AbstractElasticsearchRepository.java:156) at org.springframework.data.elasticsearch.repository.support.AbstractElasticsearchRepository.index(AbstractElasticsearchRepository.java:175) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:377) at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200) at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:641) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:605) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:590) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) at com.sun.proxy.$Proxy73.index(Unknown Source) at com.atguigu.elastic.Springboot03ElasticApplicationTests.test02(Springboot03ElasticApplicationTests.java:26) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73) at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

 

转载请注明原文地址: https://www.6miu.com/read-5036199.html

最新回复(0)