目录
部分图片来自百战程序员
1、正排索引和倒排索引
索引:索引是将数据中的一部分信息提取出来,重新组织成一定的数据结构,我们可以根据该结构进行快速搜索,这样的就够称之为索引。索引即目录,例如词典会将字的拼音提取出来做目录,通过目录可以快速找到字的位置,索引分为正排索引和倒排索引
正排索引:将文档id建立为索引,通过id快速查找数据,就像数据库中的主键就会建立正排索引
倒排索引:倒排索引就不是通过id建立索引了,而是通过提取数据中的关键字,然后将关键字建立为索引,通过匹配的关键字去查询数据
2、什么是Elasticsearch
- Elasticsearch 是一个免费且开放的分布式搜索和分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。
- Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 RESTFUL 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件;
- Elastic Stack 是一套适用于数据采集、扩充、存储、分析和可视化的免费开源工具。人们通常将 Elastic Stack 称为 ELK Stack(代指 Elasticsearch、Logstash 和 Kibana),目前 Elastic Stack 包括一系列丰富的轻量型数据采集代理,这些代理统称为 Beats,可用来向 Elasticsearch 发送数据
3、es核心概念
索引:
索引对应的就是数据库中的表
文档:
文档对应的就是表中的一条数据
域:
域名对应的就是字段
4、安装es和可视化工具Kibana
es下载路径:
https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.8.2-linux-x86_64.tar.gz
kibana下载路径:
https://artifacts.elastic.co/downloads/kibana/kibana-8.8.2-linux-x86_64.tar.gz
大家自行下载上传到虚拟机中或者直接使用wget拉取(这里要提醒的是es和kibana的版本必须一致否则会有错误)
Elasticsearch启动:
1、关闭防火墙
systemctl stop firewalld.service
2、配置最大可创建文件数大小
#打开系统文件:
vim /etc/sysctl.conf
#添加以下配置:
vm.max_map_count=655360
#配置生效:
sysctl -p
3、由于es不能以root运行,我们需要创建一个非root用户
#创建es用户
useradd es
4、解压es
#解压es
tar -zxvf 文件目录名
#修改文件属主
chown -R es:es 文件目录路径
5、启动es
#切换用户
su es
#启动elasticsearch,进入到elasticsearch的bin目录下
./elasticsearch
Kibana启动:
1、解压文件
tar -zxvf 文件目录名
2、配置kibana.yml
#进入kibana的config目录的kibana.yml文件,添加如下配置
#es的默认端口号是9200
server.host=虚拟机ip
elasticsearch.hosts=["http://运行es的虚拟机ip:端口号"]
3、运行kibana
#因为kibana不能以root用户运行,所以需要使用非root用户,我之前创建了一个es用户,
#所以我们直接使用es用户
#将文件所属改为es
chown -R es:es 文件目录路径
#切换用户
su es
#切换到kibana的bin目录,运行kibana
./kibana
4、运行之后访问http://kibana虚拟机ip:端口号
#kibana的默认端口是5601
5、访问 http://虚拟机ip:5601
访问成功过后我们就可以对es进行操作了
这个页面是通过restful风格api对es进行操作的
这个页面里的Index Management可以查看索引
5、原生操作es
索引操作
新增索引
1、建立没有结构的索引
PUT /索引名
示例:
PUT /student
2、建立有结构的索引
PUT /索引名
{
"mappings":{
"properties":{
"域名":{
"type":"字段类型",
"index":"是否创建索引",
"store":"是否存储",
"analyzer":"分词器"
},
"域名":{
......
}
}
}
}
示例:
PUT /student1
{
"mappings": {
"properties": {
"id":{
"type": "integer"
},
"name":{
"type":"text"
}
}
}
}
删除索引
DELETE /索引名
示例:
DELETE /student
文档操作
文档存储数据类似于set集合,其存储的id不可重复,
如果id重复则直接覆盖id里面所对应的数据,如果不写id则自动生成
新增文档
POST /索引名/_doc/[id]
示例:
POST /student1/_doc/1
{
"id":"1",
"name":"zhangsan"
}
修改文档
POST /索引名/_update/id值
示例:
POST /student1/_update/1
{
"doc":{
"name":"lisi"
}
}
删除文档
DELETE /索引名/_doc/id值
查询文档
GET /索引名/_doc/id值
示例:
GET /student1/_doc/1
查询所有文档
GET /索引名/_search
{
"query":{
"match_all":{}
}
}
分词器
默认分词器
ES文档的数据拆分成一个个有完整含义的关键词,并将关键词与文档对应,这样就可以通过关键词查询文档。要想正确的分词,需要选择合适的分词器。
standard analyzer:Elasticsearch默认分词器,根据空格和标点符号对英文进行分词,会进行单词的大小写转换。
默认分词器是英文分词器,对中文的分词是一字一词。
GET /_analyze
{
"text":测试语句,
"analyzer":分词器
}
IK分词器
IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。提供了两种分词算法:
- ik_smart:最少切分
- ik_max_word:最细粒度划分
安装IK分词器
关闭es服务
使用rz命令将ik分词器上传至虚拟机
注:ik分词器的版本要和es版本保持一致。解压ik分词器到elasticsearch的plugins目录下
unzip elasticsearch-analysis-ik-7.17.0.zip -d /usr/local/elasticsearch1/plugins/analysis-ik#切换用户es
su es
#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/
#启动ES服务:
./elasticsearch -d
GET /_analyze
{
"text":"测试语句",
"analyzer":"ik_smart/ik_max_word"
}
IK分词器词典
IK分词器根据词典进行分词,词典文件在IK分词器的config目录中。
- main.dic:IK中内置的词典。记录了IK统计的所有中文单词。
- IKAnalyzer.cfg.xml:用于配置自定义词库。
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">ext_dict.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">ext_stopwords.dic</entry>
</properties>
1、编辑ext_dict.dic文件(只需要在里面添加想要的关键词就好了)
2、编辑ext_stopwords.dic文件(只需要在里面添加不想要的关键词就好了)
3、测试分词效果
GET /_analyze
{
"text":"我爱英雄联盟",
"analyzer":"ik_max_word"
}
没有自定义词典的分词效果:
添加了自定义词典的分词效果:
IK拼音分词器
拼音分词器可以将中文分成对应的全拼,全拼首字母等。
安装拼音分词器
关闭es服务使用rz命令将拼音分词器上传至虚拟机
注:拼音分词器的版本要和es版本保持一致。
解压ik分词器到elasticsearch的plugins目录下
unzip elasticsearch-analysis-pinyin-7.17.0.zip -d /usr/local/elasticsearch1/plugins/analysis-pinyin
启动ES服务
su es
#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/
#启动ES服务:
./elasticsearch
GET /_analyze
{
"text":测试语句,
"analyzer":pinyin
}
自定义分词器
真实开发中我们往往需要对一段内容既进行文字分词,又进行拼音分词,此时我们需要自定义ik+pinyin分词器。
注意:两个分词器不是叠加的,而是各干各的,也就是说先对文档进行ik分词,然后再对文档进行pinyin分词。而不是对文档先ik分词,然后再对已经被ik分词的文档进行pinyin分词,也就是说倒排索引中会有两种不同的关键词,一种是通过ik分词器分词的,一种是通过pinyin分词器分词的
PUT /索引名
{
"settings" : {
"analysis" : {
"analyzer" : {
"ik_pinyin" : { //自定义分词器名
"tokenizer":"ik_max_word", // 基本分词器
"filter":"pinyin_filter" // 配置分词器过滤
}
},
"filter" : { // 分词器过滤时配置另一个分词器,相当于同时使用两个分词器
"pinyin_filter" : {
"type" : "pinyin", // 另一个分词器
// 拼音分词器的配置
"keep_separate_first_letter" : false, // 是否分词每个字的首字母
"keep_full_pinyin" : true, // 是否分词全拼
"keep_original" : true, // 是否保留原始输入
"remove_duplicated_term" : true // 是否删除重复项
}
}
}
},
"mappings":{
"properties":{
"域名1":{
"type":域的类型,
"store":是否单独存储,
"index":是否创建索引,
"analyzer":分词器
},
"域名2":{
...
}
}
}
}
示例:
PUT /product2
{
"settings": {
"analysis": {
"analyzer": {
"ik_pinyin":{
"tokenizer":"ik_max_word",
"filter":"pinyin_filter"
}
},
"filter": {
"pinyin_filter":{
"type":"pinyin",
"keep_separate_first_letter" : false,
"keep_full_pinyin" : true,
"keep_original" : true,
"remove_duplicated_term" : true
}
}
}
},
"mappings": {
"properties": {
"id":{
"type": "integer",
"store": true
},
"name":{
"type": "text",
"store": true,
"index": true,
"analyzer": "ik_pinyin"
}
}
}
}测试自定义分词器
GET /索引名/_analyze
{
"text": "你好百战程序员",
"analyzer": "ik_pinyin"
}
复杂查询
通过前面的操作我们学会了基本的es操作,但是在查询的时候我们的查询操作不可能会这么简单,我们肯定需要对于查询添加一些条件,所以我们这里就来学一下复杂查询
条件查询
1、match_all查询所有文档
GET /索引名/_search
{
"query":{
"match_all":{}
}
}
2、match全文检索(想要全文检索需要再创建索引的时候域的index设置为true)
这里需要注意的是如果使用match方式查询的话会先对关键字进行分词,然后最用分词后的数据进行查询
GET /索引名/_search
{
"query":{
"match":{
"域名":"关键字"
}
}
}
示例:
3、range范围查询
GET /索引名/_search
{
"query":{
"range":{
"域名":{
//大于等于1,小于等于2
"gte":1,
"lte":2
}
}
}
}
GET /索引名/_search
{
"query":{
"range":{
"域名":{
//大于1,小于3
"gt":1,
"lt":3
}
}
}
}
示例:
4、fuzziness纠错(最多只能纠错2位)
GET /索引名/_search
{
"query":{
"match":{
"域名":{
"query":"关键词",
"fuzziness":纠错个数
}
}
}
}
示例:
5、match_phrase短语检索,关键字不做任何分词,在搜索字段对应的倒排索引中精准匹配
GET /索引名/_search
{
"query":{
"match_phrase":{
"域名":"关键词"
}
}
}
示例:
6、terms词组检索,关键词不做任何分词,在搜索字段对应的倒排索引中精准匹配
GET /索引名/_search
{"query":{
"terms":{
"域名":[
"关键词",
"关键词",
...
]
}
}
}
排序查询
GET /索引名/_search
{
"query":{
"match":{
"域名":"关键词"
}
},
"sort":[
"域名":{
"order":"排序方式"
}
]
}
示例:
分页查询
GET /索引名/_search
{
"query":{
"match":{
"域名":"关键词"
}
},
"from":起始下标,
"size":每页个数
}
示例:
复合搜索
GET /索引名/_search
{
"query":{
"bool":{
// 必须满足的条件
"must":{
{
"搜索方式":{
"域名":"关键字"
}
},
{...}
},
//任意一个条件满足即可
"should":{
{
"搜索方式":{
"域名":"关键字"
}
},
{...}
},
//必须不满足的条件
"must_not":{
{
"搜索方式":{
"域名":"关键字"
}
},
{...}
}
}
}
}
示例:
高亮显示
GET /索引名/_search
{
"query":{
"match":{
"域名":"关键词"
}
},
"highlight":{
"fields":{
"高亮字段名":{
// 返回高亮数据的最大长度
"fragment_size":100,
// 返回结果最多可以包含几段不连续的文字
"number_of_fragments":5
}
},
"pre_tags":["前缀"],
"post_tags":["后缀"]
}
}
示例:
自动补全
自动补全的字段的类型必须是completion,所以我们需要新建一个索引,将对应的域的类型设置为completion
PUT /product
{
"mappings": {
"properties": {
"id":{
"type": "integer",
"index": true,
"store": true
},
"productName":{
"type": "completion"
},
"productDesc":{
"type": "text",
"index": true,
"store": true
}
}
}
}然后我们需要新增几条数据
POST /product/_doc
{
"id":1,
"productName":"elasticsearch1",
"productDesc":"elasticsearch1 is a good search engine"
}
POST /product/_doc
{
"id":2,
"productName":"elasticsearch2",
"productDesc":"elasticsearch2 is a good search engine"
}
POST /product/_doc
{
"id":3,
"productName":"elasticsearch3",
"productDesc":"elasticsearch3 is a good search engine"
}
自动补全
GET /索引名/_search
{
"suggest":{
"自定义名字":{
"prefix":"被补全的关键字",
"completion":{
"fields":"补全字段",
"skip_duplicates": true, // 忽略重复结果
"size": 10 //最多查询到的结果数
}
}
}
}
6、SpringDataEs操作es
项目搭建
创建SpringBoot项目,引入SpringDataEs依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置yml文件
spring:
elasticsearch:
uris: http://运行es的虚拟机IP:9200
创建实体类
一个实体类的所有对象都会存入ES的一个索引中,所以我们在创建实体类时关联ES索引。
//索引名叫product,启动SpringBoot的时候是否自动创建索引
@Document(indexName = "product",createIndex = true)
@Data
public class Product {
@Id
@Field(type = FieldType.Integer,store = true,index = true)
private Integer id;
@Field(type = FieldType.Text,store = true,index = true,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
private String productName;
@Field(type = FieldType.Text,store = true,index = true,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
private String productDesc;
}
@Document:标记在类上,标记实体类为文档对象,一般有如下属性:
indexName:对应索引的名称
createIndex:是否自动创建索引
@Id:标记在成员变量上,标记一个字段为主键,该字段的值会同步到ES该文档的id值。
@Field:标记在成员变量上x`标记为文档中的域,一般有如下属性:
type:域的类型
index:是否创建索引,默认是 true
store:是否单独存储,默认是 false
analyzer:分词器
searchAnalyzer:搜索时的分词器
Repository接口方法
创建Repository接口继承ElasticsearchRepository,接口提供了增删改查方法
(该接口有两个泛型,第一个泛型是实体类类型,第二个泛型是实体类的主键的类型)
import com.itbaizhan.esblog.pojo.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface ProductRepository extends ElasticsearchRepository<Product,Integer> {
}
测试接口:
import com.itbaizhan.esblog.pojo.Product;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Optional;
@SpringBootTest
public class ProductRepositoryTest {
@Autowired
private ProductRepository repository;
@Test
public void t1(){
//新增
Product product = new Product(1,"HUAWEI MATE 30","照亮你的美");
repository.save(product);
}
@Test
public void t2(){
//修改
Product product = new Product(1,"OPPO RENO 6 PRO","充电两分钟通话两小时");
repository.save(product);//因为id相同所以是修改,就像map集合一样
}
@Test
public void t3(){
//根据id获取文档
Optional<Product> optional = repository.findById(1);
Product product = optional.get();
System.out.println(product);
}
@Test
public void t4(){
//查询所有
Iterable<Product> products = repository.findAll();
products.forEach(System.out::println);
}
@Test
public void t5(){
//删除
Product product = new Product(1);
repository.delete(product);
}
}
DSL查询文档
接下来我们讲解SpringDataES支持的查询方式,首先准备一些文档数据:
// 添加一些数据
repository.save(new Product(2, "三体1", "三体1是优秀的科幻小说"));
repository.save(new Product(3, "三体2", "三体2是优秀的科幻小说"));
repository.save(new Product(4, "三体3", "三体3是优秀的科幻小说"));
repository.save(new Product(5, "elasticsearch", "elasticsearch是基于lucene开发的优秀的搜索引擎"));
使用Repository继承的方法查询文档
该方式我们之前已经讲解过了
使用DSL语句查询文档
ES通过json类型的请求体查询文档,方法如下:
GET /索引/_search
{
"query":{
搜索方式:搜索参数
}
}
query后的json对象称为DSL语句,我们可以在接口方法上使用@Query注解自定义DSL语句查询。
(?0代表占位符,一个?0匹配一个方法形式参数)
@Query("{" +
" \"match\": {" +
" \"productDesc\": \"?0\"" +
" }" +
" }")
List<Product> findByProductDescMatch(String keyword);
示例:
@Query("{" +
" \"match\": {" +
" \"productDesc\": {" +
" \"query\": \"?0\"," +
" \"fuzziness\": 1" +
" }" +
" }" +
"}")
List<Product> findByProductDescFuzzy(String keyword);
按照规则命名查询
按照规则命名方法进行查询
关键字 | 命名规则 | 解释 | 示例 |
and | FindByField1AndField2 | 根据Field1和Field2 获得数据 | FindByTitleAndContent |
or | FindByField1OrField2 | 根据Field1或Field2 获得数据 | FindByTitleOrContent |
is | FindByField | 根据Field获得数据 | FindByTitle |
not | FindByFieldNot | 根据Field获得补集数据 | FindByTitleNot |
between | FindByFieldBetween | 获得指定范围的数据 | FindByPriceBetween |
List<Product> findByProductName(String productName);
List<Product> findByProductNameOrProductDesc(String productName,String productDesc);
List<Product> findByIdBetween(Integer startId,Integer endId);
- 只需在Repository接口中按照SpringDataES的规则命名方法,该方法就能完成相应的查询。
- 规则:查询方法以findBy开头,涉及查询条件时,条件的属性用条件关键字连接。
分页查询
使用继承或自定义的方法时,在方法中添加Pageable类型的参数,
返回值为Page类型即可进行分页查询。
// 测试继承的方法:
@Test
public void testFindPage(){
// 参数1:页数,
// 参数2:每页条数
Pageable pageable = PageRequest.of(1, 3);
Page<Product> page = repository.findAll(pageable);
System.out.println("总条数"+page.getTotalElements());
System.out.println("总页数"+page.getTotalPages());
System.out.println("数据"+page.getContent());
}
// 自定义方法
Page<Product> findByProductDesc(String productDesc, Pageable pageable);
// 测试自定义方法
@Test
public void testFindPage2(){
Pageable pageable = PageRequest.of(1, 2);
Page<Product> page = repository.findByProductDescMatch("我喜欢三体", pageable);
System.out.println("总条数"+page.getTotalElements());
System.out.println("总页数"+page.getTotalPages());
System.out.println("数据"+page.getContent());
}
结果排序
使用继承或自定义的方法时,在方法中添加Sort类型的参数即可进行结果排序
// 结果排序
@Test
public void testFindSort(){
//第一个参数:排序的类型 第二个参数:根据哪个字段进行排序
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Iterable<Product> all = repository.findAll(sort);
for (Product product : all) {
System.out.println(product);
}
}
// 测试分页加排序
@Test
public void testFindPage2(){
Sort sort = Sort.by(Sort.Direction.DESC,"id");
Pageable pageable = PageRequest.of(0, 2,sort);
Page<Product> page = repository.findByProductDescMatch("我喜欢三体", pageable);
System.out.println("总条数"+page.getTotalElements());
System.out.println("总页数"+page.getTotalPages());
System.out.println("数据"+page.getContent());
}
template操作:
通过继承ElasticsearchRepository类我们可以很方便的进行增删改查,但是使用这种方式查询文档,无法复杂查询,也就是说只能通过id查询或者查询所有,无法通过关键字匹配查询,这就使得查询收到了很大的局限性,而使用SpringDataEs提供的工具类ElasticsearchRestTemplate操作es则可以解决该问题
操作索引
创建索引
ElasticsearchRestTemplate创建索引无法设置索引结构,所以并不推荐使用该方法创建索引
使用template操作索引,首先我们需要获取到该索引的操作对象,然后通过该操作对象进行索引操作
@SpringBootTest
public class TestTemplate {
@Autowired
private ElasticsearchRestTemplate template;
@Test
public void addIndex(){
IndexOperations ops = template.indexOps(Product.class);
ops.create();
}
}
删除文档
删除文档也是一样的只是调用的方法不同
@SpringBootTest
public class TestTemplate {
@Autowired
private ElasticsearchRestTemplate template;
@Test
public void delete(){
IndexOperations ops = template.indexOps(Product.class);
ops.delete();
}
}
增删改文档
template增删改文档和ElasticsearchRepository差不多,只是引用不同
@SpringBootTest
public class TestTemplate {
@Autowired
private ElasticsearchRestTemplate template;
@Test
public void addIndex(){
//新增索引
IndexOperations ops = template.indexOps(Product.class);
ops.create();
}
@Test
public void delete(){
//删除索引
IndexOperations ops = template.indexOps(Product.class);
ops.delete();
}
@Test
public void save(){
//新增文档
Product product = new Product(1,"HUAWEI MATE 30","照亮你的美");
template.save(product);
}
@Test
public void update(){
//修改文档
Product product = new Product(1,"OPPO RENO 6 PRO","充电五分钟,通话两小时");
template.save(product);
}
@Test
public void testDelete(){
//删除文档
template.delete("1",Product.class);
}
}
查询文档
使用template查询文档主要分为四步:
- 确定查询方式
- 构建查询条件
- 查询
- 处理查询结果
@Test
public void searchDocument(){
//查询文档
//1、确定查询方式
MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
//2、构建查询条件
NativeSearchQuery request = new NativeSearchQueryBuilder().withQuery(builder).build();
//3、查询
//参数一:查询条件
//参数二:索引对应的类对象
SearchHits<Product> hits = template.search(request, Product.class);
//4、处理查询结果
for (SearchHit<Product> hit : hits) {
System.out.println(hit.getContent());
}
}
复杂查询
通过template复杂条件查询可以实现动态的查询,根据是否传递某个参数动态修改dsl语句,查询数据
@Test
public void boolSearch(){
String productName = null;
String productDesc = null;
//1、确定查询方式
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (productName == null && productDesc == null){
//参数一:匹配的字段
//参数二:关键字
MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
boolQueryBuilder.must(queryBuilder);//必须匹配
}else if (productDesc != null && productDesc.length() > 0){
//参数一:匹配的字段
//参数二:关键字
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("productDesc", productDesc);
boolQueryBuilder.must(queryBuilder);//必须匹配
}else if (productName != null && productName.length() > 0){
//参数一:匹配的字段
//参数二:关键字
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("productName", productName);
boolQueryBuilder.must(queryBuilder);//必须匹配
}
//2、构建查询条件
NativeSearchQuery request = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).build();
//3、查询
SearchHits<Product> searchHits = template.search(request, Product.class);
//4、处理查询结果
for (SearchHit<Product> hit : searchHits) {
System.out.println(hit.getContent());
}
}
分页排序
template分页排序通过构建分页对象Pageable,设置第几页以及每页条数,然后将分页条件放入查询条件中,查询完成之后需要自己手动构造分页条件PageImpl,然后通过返回的Page对象就可以使用分页数据了
@Test
public void limit(){
//查询文档
//1、确定查询方式
MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
// MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("productName", "oppo");
//2、构建查询条件
//构建分页条件
Pageable pageable = PageRequest.of(0, 1);
NativeSearchQuery request = new NativeSearchQueryBuilder()
.withQuery(queryBuilder)
.withPageable(pageable)
.build();
//3、查询
SearchHits<Product> hits = template.search(request, Product.class);
//4、处理查询结果
//将结果封装为Page对象
List<Product> list = new ArrayList<>();
for (SearchHit<Product> hit : hits) {
Product product = hit.getContent();
list.add(product);
}
//参数一:具体数据
//参数二:分页条件
//参数三:总条数
Page<Product> page = new PageImpl<>(list, pageable, hits.getTotalHits());
System.out.println("每页条数:"+page.getTotalElements());
System.out.println("总页数:"+page.getTotalPages());
System.out.println("数据:"+page.getContent());
}
结果排序
template结果排序是通过构建结果排序对象,指定排序字段和排序方式,然后再构建查询条件的时候传入
@Test
public void sort(){
//查询文档
//1、确定查询方式
MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
// MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("productName", "oppo");
//2、构建查询条件
//构建结果排序对象
FieldSortBuilder sortBuilder = SortBuilders.fieldSort("id").order(SortOrder.DESC);
NativeSearchQuery request = new NativeSearchQueryBuilder()
.withQuery(builder)
.withSorts(sortBuilder)
.build();
//3、查询
SearchHits<Product> hits = template.search(request, Product.class);
//4、处理查询结果
for (SearchHit<Product> hit : hits) {
System.out.println(hit.getContent());
}
}
7、实战案例
实现功能
本次案例我们要实现的是高亮字段以及自动补全的功能
项目搭建
创建索引
这一段DSL语句的意思是:创建一个分词器,既有pinyin分词器的作用又有ik分词器的作用。为索引添加结构,创建域以及指定各个域的类型,并且给tags域的类型设置为completion,因为后面我们要给该字段进行自动补全功能的实现
PUT /news
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"ik_pinyin": {
"tokenizer": "ik_smart",
"filter": "pinyin_filter"
},
"tag_pinyin": {
"tokenizer": "keyword",
"filter": "pinyin_filter"
}
},
"filter": {
"pinyin_filter": {
"type": "pinyin",
"keep_joined_full_pinyin": true,
"keep_original": true,
"remove_duplicated_term": true
}
}
}
},
"mappings": {
"properties": {
"id": {
"type": "integer",
"index": true
},
"title": {
"type": "text",
"index": true,
"analyzer": "ik_pinyin",
"search_analyzer": "ik_smart"
},
"content": {
"type": "text",
"index": true,
"analyzer": "ik_pinyin",
"search_analyzer": "ik_smart"
},
"url": {
"type": "keyword",
"index": true
},
"tags": {
"type": "completion",
"analyzer": "tag_pinyin",
"search_analyzer": "tag_pinyin"
}
}
}
}
将mysql的数据同步到es中
先在mysql中添加news表的数据:
百度网盘链接
提取码:7bqy自行下载logstash文件,该文件可以将mysql中的数据同步到es中
1、解压logstash-7.17.0-windows-x86_64.zip
logstash要和elastisearch版本一致
2、在解压路径下的/config中创建mysql.conf文件,文件写入以下脚本内容:
input {
jdbc {
jdbc_driver_library => "E:\新课\Elasticsearch\软件\案例\mysql-connector-java-5.1.37-bin.jar" //找到jdbc驱动包
jdbc_driver_class => "com.mysql.jdbc.Driver" //找到驱动类
"jdbc:mysql:///news?useUnicode=true&characterEncoding=utf-8&useSSL=false" //找到指定的数据库,这后面的参数是一定要加的,不加会报错
jdbc_user => "root" //数据库用户名
jdbc_password => "root" //数据库密码
schedule => "* * * * *" //多长时间同步一次(这里是每分钟同步一次,如果logstash一直运行则每分钟一直同步)
jdbc_default_timezone => "Asia/Shanghai" //时区
statement => "SELECT * FROM news;" //执行的sql
}
}
filter { //查到数据库后的操作
mutate {
split => {"tags" => ","} //针对tags字段进行操作,通过逗号分割字段内容转为一个数组
}
}
output {
elasticsearch {
hosts => ["192.168.0.187:9200","192.168.0.187:9201","192.168.0.187:9202"] //es集群
index => "news" //索引名字
document_id => "%{id}" //从数据库查询到的id列作为索引里的文档id
}
}
3、在解压路径下打开cmd黑窗口,运行命令:
bin\logstash -f config\mysql.conf
4、测试自动补齐
GET /news/_search
{
"suggest": {
"my_suggest": {
"prefix": "li",
"completion": {
"field": "tags",
"skip_duplicates": true,
"size": 10
}
}
}
}
一张表对应的就是一个对象,一个索引就是一张表,那么一个索引就对应了一个对象,所以我们需要创建News对象,属性对应的就是索引中的各个域
@Document(indexName = "news",createIndex = false) @Data @NoArgsConstructor @AllArgsConstructor public class News { @Id @Field private Integer id; @Field private String title; @Field private String content; @Field private String keyword; @CompletionField //Completion属性比较特殊需要使用@CompletionField修饰 @Transient private Completion tags; }
编写Repository层
@Repository
public interface NewsRepository extends ElasticsearchRepository<News,Integer> {
}
编写Service层
实现自动补全功能
1、创建补全请求
2、创建补全条件并且添加补全条件
3、查询
4、处理查询结果
@Service public class NewsService { @Autowired private ElasticsearchRestTemplate template; public List<String> suggestion(String keyword){ //1、创建补全请求 SuggestBuilder suggestBuilder = new SuggestBuilder(); //2、创建补全条件 SuggestionBuilder suggestionBuilder = SuggestBuilders .completionSuggestion("tags") //补全字段 .prefix(keyword) //补全前缀 .skipDuplicates(true) //去除重复 .size(10); //最大补全大小 //添加补全条件 suggestBuilder.addSuggestion("my_suggestion",suggestionBuilder); //3、查询 SearchResponse response = template.suggest(suggestBuilder, IndexCoordinates.of("news")); //4、处理查询结果 List<String> collect = response .getSuggest() .getSuggestion("my_suggestion") .getEntries() .get(0) .getOptions() .stream() .map(Suggest.Suggestion.Entry.Option::getText) .map(Text::toString) .collect(Collectors.toList()); return collect; } }
实现高亮字段功能
1、添加高亮查询方法
2、高亮查询
3、处理查询结果,将高亮字段设置到原始数据中
实现这个高亮字段功能需要先在repository接口中添加以findBy规则命名的方法,并且添加@HighLight注解
//该注解的作用就是设置需要高亮查询的字段
//该方法是按照SpringDataES规则命名,该方法可以通过对上传的title和Content参数进行match查询
//该方法的返回值是SearchHit的容器,具体看图
//SearchHit里的结构,如下图
@Repository public interface NewsRepository extends ElasticsearchRepository<News,Integer> { @Highlight(fields = {@HighlightField(name = "title"),@HighlightField(name = "content")}) public List<SearchHit<News>> findByTitleMatchesOrContentMatches(String title, String content); }
实现功能
public List<News> highLight(String keyword){ //构建新的news集合,该集合是有高亮字段的 List<News> news = new ArrayList<>(); //高亮查询 List<SearchHit<News>> hits = repository.findByTitleMatchesOrContentMatches(keyword, keyword); //处理查询结果 for (SearchHit<News> hit : hits) { News content = hit.getContent();//返回的是没有高亮字段的News //获取高亮字段 Map<String, List<String>> highlightFields = hit.getHighlightFields(); //如果有高亮字段匹配则将高亮字段设置进去 if (highlightFields.get("title") != null){ content.setTitle(highlightFields.get("title").get(0)); } //如果有高亮字段匹配则将高亮字段设置进去 if (highlightFields.get("content") != null){ content.setContent(highlightFields.get("content").get(0)); } news.add(content); } return news; }
//这里的get(0)只是为了方便测试,但是如果一条文档的域的值有多个高亮字段匹配的话,则会分开存入集合中
//这是因为es在进行高亮搜索的时候,如果一条文档啊的域的值有多个高亮字段匹配,会以数组的方式分开存放,所以在使用
//SpringDataEs操作es的时候就会以集合的方式存入
编写Controller层
@RequestMapping所写的路径和方法参数名必须与我的匹配,因为后面提供的前端页面需要使用到
@RestController
public class NewsController {
@Autowired
private NewsService newsService;
@RequestMapping("/autoSuggest")
public List<String> suggest(String term){
return newsService.suggestion(term);
}
@RequestMapping("/highLightSearch")
public List<News> highlight(String term){
return newsService.highLight(term);
}
}
编写前端页面
通过该链接获取前端资源,获取之后将该资源放到static目录中
百度网盘链接:https://pan.baidu.com/s/1SgcdqzdWC_530nWjJReLFA?pwd=tt5r
提取码:tt5r
启动项目访问localhost:8080/news.html
测试自动补全:
测试高亮字段: