目录
1、正排索引和倒排索引
2、什么是Elasticsearch
3、es核心概念
索引:
文档:
域:
4、安装es和可视化工具Kibana
5、原生操作es
索引操作
新增索引
删除索引
文档操作
新增文档
修改文档
删除文档
查询文档
查询所有文档
分词器
默认分词器
IK分词器
IK拼音分词器
自定义分词器
复杂查询
条件查询
排序查询
分页查询
复合搜索
高亮显示
自动补全
6、SpringDataEs操作es
项目搭建
创建实体类
Repository接口方法
DSL查询文档
按照规则命名查询
分页查询
结果排序
template操作:
操作索引
创建索引
删除文档
增删改文档
查询文档
复杂查询
分页排序
结果排序
7、实战案例
实现功能
项目搭建
编写Repository层
编写Service层
编写Controller层
编写前端页面
部分图片来自百战程序员
索引:索引是将数据中的一部分信息提取出来,重新组织成一定的数据结构,我们可以根据该结构进行快速搜索,这样的就够称之为索引。索引即目录,例如词典会将字的拼音提取出来做目录,通过目录可以快速找到字的位置,索引分为正排索引和倒排索引
正排索引:将文档id建立为索引,通过id快速查找数据,就像数据库中的主键就会建立正排索引
倒排索引:倒排索引就不是通过id建立索引了,而是通过提取数据中的关键字,然后将关键字建立为索引,通过匹配的关键字去查询数据
索引对应的就是数据库中的表
文档对应的就是表中的一条数据
域名对应的就是字段
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进行操作的
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":分词器
}
IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。提供了两种分词算法:
安装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目录中。
1、编辑ext_dict.dic文件(只需要在里面添加想要的关键词就好了)
2、编辑ext_stopwords.dic文件(只需要在里面添加不想要的关键词就好了)
3、测试分词效果
GET /_analyze
{
"text":"我爱英雄联盟",
"analyzer":"ik_max_word"
}
没有自定义词典的分词效果:
添加了自定义词典的分词效果:
拼音分词器可以将中文分成对应的全拼,全拼首字母等。
安装拼音分词器
关闭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 //最多查询到的结果数
}
}
}
}
创建SpringBoot项目,引入SpringDataEs依赖
org.springframework.boot
spring-boot-starter-data-elasticsearch
配置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接口继承ElasticsearchRepository,接口提供了增删改查方法
(该接口有两个泛型,第一个泛型是实体类类型,第二个泛型是实体类的主键的类型)
import com.itbaizhan.esblog.pojo.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface ProductRepository extends ElasticsearchRepository {
}
测试接口:
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 optional = repository.findById(1);
Product product = optional.get();
System.out.println(product);
}
@Test
public void t4(){
//查询所有
Iterable products = repository.findAll();
products.forEach(System.out::println);
}
@Test
public void t5(){
//删除
Product product = new Product(1);
repository.delete(product);
}
}
接下来我们讲解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
findByProductDescMatch(String keyword);
示例:
@Query("{" +
" \"match\": {" +
" \"productDesc\": {" +
" \"query\": \"?0\"," +
" \"fuzziness\": 1" +
" }" +
" }" +
"}")
List
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
List
List
使用继承或自定义的方法时,在方法中添加Pageable类型的参数,
返回值为Page类型即可进行分页查询。
// 测试继承的方法:
@Test
public void testFindPage(){
// 参数1:页数,
// 参数2:每页条数
Pageable pageable = PageRequest.of(1, 3);
Page page = repository.findAll(pageable);
System.out.println("总条数"+page.getTotalElements());
System.out.println("总页数"+page.getTotalPages());
System.out.println("数据"+page.getContent());
}
// 自定义方法
Page findByProductDesc(String productDesc, Pageable pageable);
// 测试自定义方法
@Test
public void testFindPage2(){
Pageable pageable = PageRequest.of(1, 2);
Page 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 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 page = repository.findByProductDescMatch("我喜欢三体", pageable);
System.out.println("总条数"+page.getTotalElements());
System.out.println("总页数"+page.getTotalPages());
System.out.println("数据"+page.getContent());
}
通过继承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 hits = template.search(request, Product.class);
//4、处理查询结果
for (SearchHit 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 searchHits = template.search(request, Product.class);
//4、处理查询结果
for (SearchHit 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 hits = template.search(request, Product.class);
//4、处理查询结果
//将结果封装为Page对象
List list = new ArrayList<>();
for (SearchHit hit : hits) {
Product product = hit.getContent();
list.add(product);
}
//参数一:具体数据
//参数二:分页条件
//参数三:总条数
Page 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 hits = template.search(request, Product.class);
//4、处理查询结果
for (SearchHit hit : hits) {
System.out.println(hit.getContent());
}
}
本次案例我们要实现的是高亮字段以及自动补全的功能
创建索引
这一段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
public interface NewsRepository extends ElasticsearchRepository {
}
实现自动补全功能
1、创建补全请求
2、创建补全条件并且添加补全条件
3、查询
4、处理查询结果
@Service public class NewsService { @Autowired private ElasticsearchRestTemplate template; public List
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 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
{ @Highlight(fields = {@HighlightField(name = "title"),@HighlightField(name = "content")}) public List > findByTitleMatchesOrContentMatches(String title, String content); } 实现功能
public List
highLight(String keyword){ //构建新的news集合,该集合是有高亮字段的 List news = new ArrayList<>(); //高亮查询 List > hits = repository.findByTitleMatchesOrContentMatches(keyword, keyword); //处理查询结果 for (SearchHit hit : hits) { News content = hit.getContent();//返回的是没有高亮字段的News //获取高亮字段 Map > 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的时候就会以集合的方式存入
@RequestMapping所写的路径和方法参数名必须与我的匹配,因为后面提供的前端页面需要使用到
@RestController
public class NewsController {
@Autowired
private NewsService newsService;
@RequestMapping("/autoSuggest")
public List suggest(String term){
return newsService.suggestion(term);
}
@RequestMapping("/highLightSearch")
public List highlight(String term){
return newsService.highLight(term);
}
}
通过该链接获取前端资源,获取之后将该资源放到static目录中
百度网盘链接:https://pan.baidu.com/s/1SgcdqzdWC_530nWjJReLFA?pwd=tt5r
提取码:tt5r
启动项目访问localhost:8080/news.html
测试自动补全: