二十二、SpringBoot之检索-Elasticsearch

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_29479041/article/details/83383970

一、检索

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

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

二、概念

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

三、测试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 elasticsearch elasticsearch
3.1.x 6.2.2
3.0.x 5.5.0
2.1.x 2.4.0
2.0.x 2.2.0
1.3.x 1.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)

猜你喜欢

转载自blog.csdn.net/qq_29479041/article/details/83383970