众里寻他千百度java
搜索是ES的核心,本节讲解一些基本的简单的搜索。node
掌握ES搜索查询的RESTful的API犹如掌握关系型数据库的SQL语句,尽管Java客户端API为咱们不须要咱们去实际编写RESTful的API,但在生产环境中,免不了在线上执行查询语句作数据统计供产品经理等使用。git
首先建立一个名为user的Index,并建立一个student的Type,Mapping映射一共有以下几个字段:程序员
建立名为user的Index PUT http://localhost:9200/user
github
建立名为student的Type,且指定字段name和address的分词器为ik_smart
。spring
POST http://localhost:9200/user/student/_mapping { "properties":{ "name":{ "type":"text", "analyzer":"ik_smart" }, "age":{ "type":"short" } } }
通过上一章分词的学习咱们把text
类型都指定为ik_smart
分词器。数据库
插入如下数据。json
POST localhost:9200/user/student { "name":"kevin", "age":25 }
POST localhost:9200/user/student { "name":"kangkang", "age":26 }
POST localhost:9200/user/student { "name":"mike", "age":22 }
POST localhost:9200/user/student { "name":"kevin2", "age":25 }
POST localhost:9200/user/student { "name":"kevin yu", "age":21 }
GET http://localhost:9200/user/student/_search?pretty
springboot
查看索引user的student类型数据,获得刚刚插入的数据返回:数据结构
ES查询主要分为term
精确搜索、match
模糊搜索。
咱们用term
搜索name为“kevin”的数据。
POST http://localhost:9200/user/student/_search?pretty { "query":{ "term":{ "name":"kevin" } } }
既然term
是精确搜索,按照非关系型数据库的理解来说就等同于=
,那么搜索结果也应该只包含1条数据。然而出乎意料的是,搜索结果出现了两条数据:name="kevin"和name="keivin yu",这看起来彷佛是进行的模糊搜索,但又没有搜索出name="kevin2"的数据。咱们先继续观察match
的搜索结果。
一样,搜索name为“kevin”的数据。
POST http://localhost:9200/user/student/_search?pretty { "query":{ "match":{ "name":"kevin" } } }
match
的搜索结果居然仍然是两条数据:name="kevin"和name="keivin yu"。一样,name="kevin2"也没有出如今搜索结果中。
缘由在于term
和match
的精确和模糊针对的是搜索词而言,term
搜索不会将搜索词进行分词后再搜索,而match
则会将搜索词进行分词后再搜索。例如,咱们对name="kevin yu"进行搜索,因为term
搜索不会对搜索词进行搜索,因此它进行检索的是"kevin yu"这个总体,而match
搜索则会对搜索词进行分词搜索,因此它进行检索的是包含"kevin"和"yu"的数据。而name字段是text
类型,且它是按照ik_smart
进行分词,就算是"kevin yu"这条数据因为被分词后变成了"kevin"和"yu",因此term
搜索不到任何结果。
若是必定要用term
搜索name="kevin yu",结果出现"kevin yu",办法就是在定义映射Mapping时就为该字段设置一个keyword
类型。
为了下文的顺利进行,删除DELETE http:localhost:9200/user/student
从新按照开头建立索引以及插入数据吧。惟一须要修改的是在定义映射Mapping时,name字段修改成以下所示:
{ "properties":{ "name":{ "type":"text", "analyzer":"ik_smart", "fields":{ "keyword":{ "type":"keyword", "ignore_abore":256 } } }, "age":{ "type":integer } } }
待咱们从新建立好索引并插入数据后,此时再按照term
搜索name="kevin yu"。
POST http://localhost:9200/user/student/_search { "query":{ "term":{ "name.keyword":"kevin yu" } } }
返回一条name="kevin yu"的数据。按照match
搜索一样出现name="kevin yu",由于name.keyword不管如何都不会再分词。
在已经创建索引且定义好映射Mapping的状况下,若是直接修改name字段,此时能修改为功,可是却没法进行查询,这与ES底层实现有关,若是必定要修改要么是新增字段,要么是重建索引。
因此,与其说match
是模糊搜索,倒不如说它是分词搜索,由于它会将搜索关键字分词;与其将term
称之为模糊搜索,倒不如称之为不分词搜索,由于它不会将搜索关键字分词。
match
查询还有不少更为高级的查询方式:match_phrase
短语查询,match_phrase_prefix
短语匹配查询,multi_match
多字段查询等。将在复杂搜索一章中详细介绍。
wildcard
通配符查询。
POST http://localhost:9200/user/student/_search?pretty { "query": { "wildcard": { "name": "*kevin*" } } }
ES返回结果包括name="kevin",name="kevin2",name="kevin yu"。
fuzzy也是一个模糊查询,它看起来更加”智能“。它相似于搜狗输入法中容许语法错误,但仍能搜出你想要的结果。例如,咱们查询name等于”kevin“的文档时,不当心输成了”kevon“,它仍然能查询出结构。
POST http://localhost:9200/user/student/_search?pretty { "query": { "fuzzy": { "name": "kevin" } } }
ES返回结果包括name="kevin",name="kevin yu"。
上文介绍了单个条件下的简单搜索,而且介绍了相关的精确和模糊搜索(分词与不分词)。这部分将介绍多个条件下的简单搜索。
当搜索须要多个条件时,条件与条件之间的关系有”与“,”或“,“非”,正如非关系型数据库中的”and“,”or“,“not”。
在ES中表示”与“关系的是关键字must
,表示”或“关系的是关键字should
,还有表示表示”非“的关键字must_not
。
must
、should
、must_not
在ES中称为bool
查询。当有多个查询条件进行组合查询时,此时须要上述关键字配合上文提到的term
,match
等。
term
,搜索关键字不分词)name="kevin"且age="25"的学生。POST http://localhost:9200/user/student/_search?pretty { "query":{ "bool":{ "must":[{ "term":{ "name.keyword":"kevin" } },{ "term":{ "age":25 } }] } } }
返回name="kevin"且age="25"的数据。
term
,搜索关键字不分词)name="kevin"或age="21"的学生。POST http://localhost:9200/user/student/_search?pretty { "query":{ "bool":{ "should":[{ "term":{ "name.keyword":"kevin" } },{ "term":{ "age":21 } }] } } }
返回name="kevin",age=25和name="kevin yu",age=21的数据
term
,搜索关键字不分词)name!="kevin"且age="25"的学生。POST http://localhost:9200/user/student/_search?pretty { "query":{ "bool":{ "must":[{ "term":{ "age":25 } }], "must_not":[{ "term":{ "name.keyword":"kevin" } }] } } }
返回name="kevin2"的数据。
若是查询条件中同时包含must
、should
、must_not
,那么它们三者是"且"的关系
多条件查询中查询逻辑(must
、should
、must_not
)与查询精度(term
、match
)配合能组合成很是丰富的查询条件。
上文中讲到了精确查询、模糊查询,已经"且","或","非"的查询。基本上都是在作等值查询,实际查询中还包括,范围(大于小于)查询(range
)、存在查询(exists
)、~不存在查询(。missing
)
范围查询关键字range
,它包括大于gt
、大于等于gte
、小于lt
、小于等于lte
。
POST http://localhost:9200/user/student/_search?pretty { "query":{ "range":{ "age":{ "gt":25 } } } }
返回name="kangkang"的数据。
POST http://localhost:9200/user/search/_search?pretty { "query":{ "range":{ "age":{ "gte":21, "lt":25 } } } }
查询age >= 21 且 age < 26且name="kevin"的学生
POST http://localhost:9200/user/search/_search?pretty { "query":{ "bool":{ "must":[{ "term":{ "name":"kevin" } },{ "range":{ "age":{ "gte":21, "lt":25 } } }] } } }
存在查询意为查询是否存在某个字段。
查询存在name字段的数据。
POST http://localhost:9200/user/student/_search?pretty { "query":{ "exists":{ "field":"name" } } }
不存在查询顾名思义查询不存在某个字段的数据。在之前ES有missing
表示查询不存在的字段,后来的版本中因为must not
和exists
能够组合成missing
,故去掉了missing
。
查询不存在name字段的数据。
POST http://localhost:9200/user/student/_search?pretty { "query":{ "bool":{ "must_not":{ "exists":{ "field":"name" } } } } }
谈到ES的分页永远都绕不开深分页的问题。但在本章中暂时避开这个问题,只说明在ES中如何进行分页查询。
ES分页查询包含from
和size
关键字,from
表示起始值,size
表示一次查询的数量。
POST http://localhost:9200/user/student/_search?pretty
返回文档总数。
match
,搜索关键字不分词)name="kevin"POST http://localhost:9200/user/student/_search?pretty { "query":{ "match":{ "name":"kevin" } }, "from":0, "size":1 }
结合文档总数便可返回简单的分页查询。
分页查询中每每咱们也须要对数据进行排序返回,MySQL中使用order by
关键字,ES中使用sort
关键字指定排序字段以及降序升序。
POST http://localhost:9200/user/student/_search?pretty { "query":{ "range":{ "age":{ "gte":21, "lte":26 } } }, "from":0, "size":1, "sort":{ "age":{ "order":"desc" } } }
ES默认升序排列,若是不指定排序字段的排序),则sort
字段可直接写为"sort":"age"
。
ES提供了多种方式使用Java客户端:
目前经常使用的是TransportClient
方式链接ES服务。但ES官方表示,在将来TransportClient
会被永久移除,只保留RestClient
方式。
一样,Spring Boot官方也提供了操做ES的方式Spring Data ElasticSearch
。本章节将首先介绍基于Spring Boot所构建的工程经过Spring Data ElasticSearch
操做ES,再介绍一样是基于Spring Boot所构建的工程,但使用ES提供的TransportClient
操做ES。
本节完整代码(配合源码使用更香):https://github.com/yu-linfeng/elasticsearch6.x_tutorial/tree/master/code/spring-data-elasticsearch
使用Spring Data ElasticSearch
后,你会发现一切变得如此简单。就连链接ES服务的类都不须要写,只须要配置一条ES服务在哪儿的信息就能开箱即用。
做为简单的API和简单搜索两章节的启下部分,本节示例仍然是基于上一章节的示例。
经过IDEA建立Spring Boot工程,而且在建立过程当中选择Spring Data ElasticSearch
,主要步骤以下图所示:
第一步,建立工程,选择Spring Initializr
。
第二步,选择SpringBoot的依赖NoSQL -> Spring Data ElasticSearch
。
建立好Spring Data ElasticSearch的Spring Boot工程后,按照ES惯例是定义Index以及Type和Mapping。在Spring Data ElasticSearch
中定义Index、Type以及Mapping很是简单。ES文档数据实质上对应的是一个数据结构,也就是在Spring Data ElasticSearch
要咱们把ES中的文档数据模型与Java对象映射关联。
定义StudentPO对象,对象中定义Index以及Type,Mapping映射咱们引入外部json文件(json格式的Mapping就是在简单搜索一章中定义的Mapping数据)。
package com.coderbuff.es.easy.domain; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.Mapping; import java.io.Serializable; /** * ES mapping映射对应的PO * Created by OKevin on 2019-06-26 22:52 */ @Getter @Setter @ToString @Document(indexName = "user", type = "student") @Mapping(mappingPath = "student_mapping.json") public class StudentPO implements Serializable { private String id; /** * 姓名 */ private String name; /** * 年龄 */ private Integer age; }
Spring Data ElasticSearch
为咱们屏蔽了操做ES太多的细节,以致于真的就是开箱即用,它操做ES主要是经过ElasticsearchRepository
接口,咱们在定义本身具体业务时,只须要继承它,扩展本身的方法。
package com.coderbuff.es.easy.dao; import com.coderbuff.es.easy.domain.StudentPO; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; /** * Created by OKevin on 2019-06-26 23:45 */ @Repository public interface StudentRepository extends ElasticsearchRepository<StudentPO, String> { }
ElasticsearchTemplate
能够说是Spring Data ElasticSearch
最为重要的一个类,它对ES的Java API进行了封装,建立索引等都离不开它。在Spring中要使用它,必然是要先注入,也就是实例化一个bean。而Spring Data ElasticSearch
早为咱们作好了一切,只须要在application.properties
中定义spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
,就可大功告成(网上有人的教程还在使用applicationContext.xml定义一个bean,事实证实,受到了Spring多年的“毒害”,Spring Boot远比咱们想象的智能)。
单元测试建立Index、Type以及定义Mapping。
package com.coderbuff.es; import com.coderbuff.es.easy.domain.StudentPO; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class SpringDataElasticsearchApplicationTests { @Autowired private ElasticsearchTemplate elasticsearchTemplate; /** * 测试建立Index,type和Mapping定义 */ @Test public void createIndex() { elasticsearchTemplate.createIndex(StudentPO.class); elasticsearchTemplate.putMapping(StudentPO.class); } }
使用GET http://localhost:9200/user
请求命令,可看到经过Spring Data ElasticSearch
建立的索引。
索引建立完成后,接下来就是定义操做student文档数据的接口。在StudentService
接口的实现中,经过组合StudentRepository
类对ES进行操做。StudentRepository
类继承了ElasticsearchRepository
接口,这个接口的实现已经为咱们提供了基本的数据操做,保存、修改、删除只是一句代码的事。就算查询、分页也为咱们提供好了builder类。"最难"的实际上不是实现这些方法,而是如何构造查询参数SearchQuery
。建立SearchQuery
实例,有两种方式:
NativeSearchQueryBuilder
类,经过链式调用构造查询参数。NativeSearchQuery
类,经过构造方法传入查询参数。这里以"不分页range范围和term查询age>=21且age<26且name=kevin"为例。
SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.boolQuery() .must(QueryBuilders.rangeQuery("age").gte(21).lt(26)) .must(QueryBuilders.termQuery("name", "kevin"))).build();
搜索条件的构造必定要对ES的查询结构有比较清晰的认识,若是是在了解了简单的API和简单搜索两章的前提下,学习如何构造多加练习必定能掌握。这里就不一一验证前面章节的示例,必定要配合代码使用练习(https://github.com/yu-linfeng/elasticsearch6.x_tutorial/tree/master/code/spring-data-elasticsearch)
ES的Java API很是普遍,一种操做可能会有好几种写法。Spring Data ElasticSearch其实是对ES Java API的再次封装,从使用上将更加简单。
本节请直接对照代码学习使用,若是要讲解ES的Java API那将是一个十分庞大的工做,https://github.com/yu-linfeng/elasticsearch6.x_tutorial/tree/master/code/transportclient-elasticsearch
关注公众号:CoderBuff,回复“es”获取《ElasticSearch6.x实战教程》完整版PDF。