lucene和Elasticsearch

lucene和Elasticsearch

一.全文检索

1.什么是全文检索

1.1 数据分类

结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。
非结构化数据:指不定长或无固定格式的数据,如邮件,word 文档等磁盘上的文件。

1.2 什么是全文检索

这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search) 。

二.lucene

2.1 如何使用lucene

如何创建索引
第一步:获取原始数据
不同的场景原始数据是不一样的,获取的方式肯定也不一样
搜索引擎:原始数据就是互联网上网页 获取方式就是爬虫程序 java本质 httpClient python
京东商城:原始数据就是mysql的数据 获取方式就是sql语句 重点
百度文库:原始数据就是word pdf excel TXT文件 获取方式就是使用io流
第二步:构建Lucene中的Document对象
把上面获取到原始数据都需要转成Lucene能识别的Document对象
第三步:分词 Lucene中自带一个分词器
举例:北京东进航空科技股份有限公司
companyName:北京 是索引库存储的最小单位 term
第四步:保存索引和原始数据的关系
使用的是倒排索引结构
就是能根据一个关键字查询出对应的文档 ,举例:新华字典

如果在索引上查询
第一步:创建一个搜索接口 就是指输入关键字的位置
第二步:创建查询
第三步:执行查询
第四步:渲染查询结果 (高亮显示)

2.2 索引和搜索流程图

在这里插入图片描述

2.3 使用

2.3.1 LuceneDemo

package com.leyou.lucene;

import com.leyou.pojo.JobInfo;
import com.leyou.service.JobInfoServiceImpl;
import com.sun.rowset.JoinRowSetImpl;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cjk.CJKAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
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.test.context.junit4.SpringRunner;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.File;
import java.util.List;

/**
 * @author cyy
 * @date 2020/4/7 14:47
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class LuceneDemo {


    @Autowired
    private JobInfoServiceImpl jobInfoService;

    @Test
    public void test() throws Exception{

//        Directory d 指定索引库的位置, IndexWriterConfig conf 使用哪种分词器
        Directory directory = FSDirectory.open(new File("D:\\index"));
//        Version matchVersion lucene 的版本, Analyzer analyzer 使用的分词器
//        StandardAnalyzer标准分词器
//        Analyzer analyzer = new StandardAnalyzer();
//        Analyzer analyzer = new CJKAnalyzer();//中日韩分词器
        Analyzer analyzer = new IKAnalyzer();//ik分词器 第三方的 需要导入相关的jar
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST,analyzer);
        IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);

        indexWriter.deleteAll();

        List<JobInfo> list = jobInfoService.findAll();
        for (JobInfo jobInfo : list) {
            Document document = new Document();
           /* jobInfo.getCompanyName();*/
//            name是域的名字 随便写 第二个参数是获取的路径 第三个参数是否存储
            document.add(new TextField("companyName",jobInfo.getCompanyName(), Field.Store.YES));
            document.add(new TextField("jobName",jobInfo.getJobName(), Field.Store.YES));
            document.add(new IntField("salary",jobInfo.getSalary(), Field.Store.YES));
            document.add(new StringField("url",jobInfo.getUrl(), Field.Store.YES));
            //TextField和StringField 都是放字符串的
//            TextField和StringField默认分词 string不会分词
            indexWriter.addDocument(document);

        }

            indexWriter.close();
    }

     @Test
     public void indexTest() throws Exception{
         Directory directory = FSDirectory.open(new File("D:\\index"));
         IndexReader indexReader = DirectoryReader.open(directory);

         IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//         Query query 查询方式  int n 最多查询多少条数据


         TopDocs topDocs = indexSearcher.search(new TermQuery(new Term("companyName", "中认环宇")), 10);
//        符合条件的总数据量
         System.out.println("topDocs.totalHits = " + topDocs.totalHits);

         ScoreDoc[] scoreDocs = topDocs.scoreDocs;
         for (ScoreDoc scoreDoc : scoreDocs) {
             int docId = scoreDoc.doc;
             Document document = indexSearcher.doc(docId);
             System.out.println(document.get("companyName"));
             System.out.println(document.get("jobName"));
             System.out.println(document.get("url"));
             System.out.println(document.get("salary"));
             System.out.println(" = ========================== =");
         }

     }
}

2.3.2 分词器

  • 自带的分词器不符合中国人的说话方式,使用ik分词器
  1. 导入jar
 <dependency>
            <groupId>com.janeluo</groupId>
            <artifactId>ikanalyzer</artifactId>
            <version>2012_u6</version>
        </dependency>
  1. ik分词器支持自定义 可以自己定义拓展词典和禁用的词典
    ext.dic和stopword.dic 使用这两个需要ik分词器的配置文件IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
<properties>  
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict">ext.dic;</entry> 

	<!--用户可以在这里配置自己的停止词字典-->
	<entry key="ext_stopwords">stopword.dic;</entry> 
	
</properties>

三.Elasticsearch

Elastic有一条完整的产品线:Elasticsearch、Kibana、Logstash等,前面说的三个就是大家常说的ELK技术栈。

3.1 Elasticsearch

Elasticsearch官网:官网

Elasticsearch具备以下特点:

  • 分布式,无需人工搭建集群(solr就需要人为配置,使用Zookeeper作为注册中心)
  • Restful风格,一切API都遵循Rest原则,容易上手
  • 近实时搜索,数据更新在Elasticsearch中几乎是完全同步的。

3.1.1 安装

下一步

3.1.2 修改配置文件

1、修改索引数据和日志数据存储的路径
在这里插入图片描述
第33行和37行,修改完记得把注释打开

path.data: d:\class\es\data
#
# Path to log files:
#
path.logs: d:\class\es\log

3.1.3 访问

在这里插入图片描述
可以看到绑定了两个端口:
9300:集群节点间通讯接口,接收tcp协议
9200:客户端访问接口,接收Http协议

3.2 Kibana

3.2.1 什么是Kibana

Kibana是一个基于Node.js的Elasticsearch索引库数据统计工具,可以利用Elasticsearch的聚合功能,生成各种图表,如柱形图,线状图,饼图等。

而且还提供了操作Elasticsearch索引数据的控制台,并且提供了一定的API提示,非常有利于我们学习Elasticsearch的语法。

3.2.2 安装

因为Kibana依赖于node,需要在windows下先安装Node.js。
然后安装kibana,最新版本与elasticsearch保持一致。

3.2.3 配置运行

配置
进入安装目录下的config目录,修改kibana.yml文件:
修改elasticsearch服务器的地址:

elasticsearch.url: "http://127.0.0.1:9200"

运行
在这里插入图片描述
访问:http://127.0.0.1:5601

3.2 使用kibana对索引库操作

索引库(indices)---------------------------------Database 数据库

    类型(type)----------------------------------Table 数据表

 文档(Document)---------------------------------Row 行

     字段(Field)--------------------------------Columns 列 
     
映射配置(mappings)-------------------------------每个列的约束(类型、长度)   

3.3 操作索引库

PUT/索引库名 --创建
GET /索引库名 --获取
DELETE/索引库名 --删除

3.4 使用kibana对类型及映射操作

3.4.1 创建字段映射

语法

PUT /索引库名/_mapping/类型名称
{
  "properties": {
    "字段名": {
      "type": "类型",
      "index": true,
      "store": true,
      "analyzer": "分词器"
    }
  }
}
  • 类型名称:就是前面将的type的概念,类似于数据库中的表
    字段名:任意填写,下面指定许多属性,例如:
  • type:类型,可以是text、long、short、date、integer、object等
  • index:是否索引,默认为true
  • store:是否存储,默认为false
  • analyzer:分词器,这里的ik_max_word即使用ik分词器
    keyword相当于string 默认是不分词

测试:

PUT heima/_mapping/goods
{
  "properties": {
    "goodName":{
      "type": "text",
      "analyzer": "ik_max_word",
      "store": true,
      "index": true
    },
    "price":{
      "type": "double",
      "store": true,
      "index": true
    },
      "image":{
      "type": "keyword",
      "store": true
    }
  }
}

在这里插入图片描述
一次性创建索引库和映射

PUT /heima2
{
  
    "mappings": {
      "goods":{
        "properties": {
      "goodName":{
        "type": "text",
        "analyzer": "ik_max_word",
        "index": true,
        "store": true
      },
      "price":{
        "type": "keyword",
        "index": true,
        "store": true
      }
    }
      }
    }
}

3.4.2 映射属性详解

1)type
  • String类型,又分两种:

    • text:可分词,不可参与聚合
    • keyword:不可分词,数据会作为完整字段进行匹配,可以参与聚合
  • Numerical:数值类型,分两类

    • 基本数据类型:long、interger、short、byte、double、float、half_float
    • 浮点数的高精度类型:scaled_float
      • 需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
  • Date:日期类型

    elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。

  • Array:数组类型

    • 进行匹配时,任意一个元素满足,都认为满足
    • 排序时,如果升序则用数组中的最小值来排序,如果降序则用数组中的最大值来排序
  • Object:对象

2)index

index影响字段的索引情况。

  • true:字段会被索引,则可以用来进行搜索过滤。默认值就是true
  • false:字段不会被索引,不能用来搜索

index的默认值就是true,也就是说你不进行任何配置,所有字段都会被索引。

但是有些字段是我们不希望被索引的,比如商品的图片信息,就需要手动设置index为false。

3)store

是否将数据进行额外存储。

在学习lucene时,我们知道如果一个字段的store设置为false,那么在文档列表中就不会有这个字段的值,用户的搜索结果中不会显示出来。

但是在Elasticsearch中,即便store设置为false,也可以搜索到结果。

原因是Elasticsearch在创建文档索引时,会将文档中的原始数据备份,保存到一个叫做_source的属性中。而且我们可以通过过滤_source来选择哪些要显示,哪些不显示。

而如果设置store为true,就会在_source以外额外存储一份数据,多余,因此一般我们都会将store设置为false,事实上,store的默认值就是false。

4)boost

权重,新增数据时,可以指定该数据的权重,权重越高,得分越高,排名越靠前。

3.5 使用kibana对文档操作

3.5.1 新增文档

不指定id新增

post heima/goods
{
  "goodName":"huawei",
  "pricce":1111,
  "image":"http://sdsa.jpg"
}

指定id新增
在索引库后面写id的值就可以了

POST heima/goods/2
{
  "goodsName":"ipone",
  "price":1111,
  "img":"sada.jpg"
}

3.5.2 常用命令

GET  获取
POST 新增
PUT  修改
DELETE 删除

3.5.3 自定义模板

动态模板的语法:
在这里插入图片描述
1)模板名称,随便起

2)匹配条件,凡是符合条件的未定义字段,都会按照这个规则来映射

3)映射规则,匹配成功后的映射规则

PUT heima3
{
  "mappings": {
    "goods": {
      "properties": {
        "title": {
          "type": "text",
          "analyzer": "ik_max_word"
        }
      },
      "dynamic_templates": [
        {
          "strings": {
            "match_mapping_type": "string",
            "mapping": {
              "type": "keyword"
            }
          }
        }
      ]
    }
  }
}

3.6 查询

3.6.1 词条匹配(term)

注意_search

GET /heima/_search
{
    "query":{
        "term":{
            "price":2699.00
        }
    }
}

3.6.2 匹配查询(match)

会把关键字分词 or关系

GET /heima/_search
{
    "query":{
        "match":{
            "title":"小米电视"
        }
    }
}

更改为and关系

GET /goods/_search
{
    "query":{
        "match":{
            "title":{"query":"小米电视","operator":"and"}
        }
    }
}

3.6.3 查询所有(match_all)

GET /heima/_search
{
    "query":{
        "match_all": {}
    }
}
  • query:代表查询对象
  • match_all:代表查询所有
    结果:
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 3,
    "successful": 3,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "heima",
        "_type": "goods",
        "_id": "2",
        "_score": 1,
        "_source": {
          "title": "大米手机",
          "images": "http://image.leyou.com/12479122.jpg",
          "price": 2899
        }
      },
      {
        "_index": "heima",
        "_type": "goods",
        "_id": "r9c1KGMBIhaxtY5rlRKv",
        "_score": 1,
        "_source": {
          "title": "小米手机",
          "images": "http://image.leyou.com/12479122.jpg",
          "price": 2699
        }
      }
    ]
  }
}
  • took:查询花费时间,单位是毫秒
  • time_out:是否超时
  • _shards:分片信息
  • hits:搜索结果总览对象
    • total:搜索到的总条数
    • max_score:所有结果中文档得分的最高分
    • hits:搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
      • _index:索引库
      • _type:文档类型
      • _id:文档id
      • _score:文档得分
      • _source:文档的源数据

3.6.4 模糊查询(fuzzy)

fuzzy 查询是 term 查询的模糊等价。它允许用户搜索词条与实际词条的拼写出现偏差,但是偏差的编辑距离不得超过2:

fuzziness偏离度 不得超过2

搜索ipona的时候可以查出ipone

GET heima/goods/_search
{
"query":{
  "fuzzy": {
    "goodsName": {
      "value": "手环",
      "fuzziness": 1
    }
  }
}
}

结果:

{
  "took": 19,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.23014566,
    "hits": [
      {
        "_index": "heima",
        "_type": "goods",
        "_id": "3",
        "_score": 0.23014566,
        "_source": {
          "goodsName": "ipone 2",
          "price": 11111,
          "image": "sad.jpg"
        }
      }
    ]
  }
}

3.6.5 范围查询(range)

GET heima/goods/_search
{
"query":{
  "range": {
    "price": {
      "gte": 10000,
      "lte": 12000
    }
  }
}
}

range查询允许以下字符:

操作符说明
gt大于
gte大于等于
lt小于
lte小于等于

3.6.6 布尔组合(bool)

bool把各种其它查询通过must(与)、must_not(非)、should(或)的方式进行组合

商品名称带有手机并且价格在10000-20000之间的
GET heima/goods/_search
{
"query":{
  "bool": {
    "must": [
      {"term": {
        "goodsName": {
          "value": "手机"
        }
      }},{
        "range": {
          "price": {
            "gte": 10000,
            "lte": 20000
          }
        }
      }
    ]
  }
}
}

3.7 分页,过滤,排序,高亮

3.7.1 分页

elasticsearch的分页与mysql数据库非常相似,都是指定两个值:

  • from:开始位置
  • size:每页大小
GET /heima/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "price": {
        "order": "asc"
      }
    }
  ],
  "from": 3,
  "size": 3
}

3.7.2 排序

GET heima/goods/_search
{
"query":{
  "match_all": {}
},"sort": [
  {
    "price": {
      "order": "desc"
    }
  }
], 
"from": 0,
"size": 2
}

3.7.3 过滤

结果过滤 - 指定includes和excludes
  • includes:来指定想要显示的字段
  • excludes:来指定不想要显示的字段
GET heima/goods/_search
{
  "query": {
    "match": {
      "goodsName": "手机"
    }
  },
  "_source": {
    "includes": ["goodsName","price"]
  
  }
}

结果:

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 4,
    "max_score": 0.6099695,
    "hits": [
      {
        "_index": "heima",
        "_type": "goods",
        "_id": "7",
        "_score": 0.6099695,
        "_source": {
          "price": 12345,
          "goodsName": "手机2"
        }
      },
      {
        "_index": "heima",
        "_type": "goods",
        "_id": "4",
        "_score": 0.5619608,
        "_source": {
          "price": 12345,
          "goodsName": "手机"
        }
      },
      {
        "_index": "heima",
        "_type": "goods",
        "_id": "6",
        "_score": 0.43445712,
        "_source": {
          "price": 12345,
          "goodsName": "手机1"
        }
      },
      {
        "_index": "heima",
        "_type": "goods",
        "_id": "8",
        "_score": 0.2876821,
        "_source": {
          "price": 12345,
          "goodsName": "手机3"
        }
      }
    ]
  }
}
过滤(filter)

条件查询中进行过滤

所有的查询都会影响到文档的评分及排名。如果我们需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用filter方式:

GET heima/goods/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "goodsName": "手机"
        }}
      ],
      "filter": {"range": {
        "price": {
          "gte": 10000,
          "lte": 20000
        }
      }}
    }
  }
}

3.7.4 高亮

在使用match查询的同时,加上一个highlight属性:

  • pre_tags:前置标签
  • post_tags:后置标签
  • fields:需要高亮的字段
    • title:这里声明title字段需要高亮,后面可以为这个字段设置特有配置,也可以空
GET heima/goods/_search
{
  "query": {
    "term": {
      "goodsName": {
        "value": "手机"
      }
    }
  },
  "highlight": {
    "fields": {"goodsName": {}},
    "pre_tags": "<span style='color:red'>",
    "post_tags": "/<span>"
  }
}

3.8 聚合aggregations

3.8.1 桶(bucket ) 类似于 group by

根据车辆的颜色分类

GET car/orders/_search
{
  "size": 0, 
  "aggs": {
    "color": {
      "terms": {
        "field": "color",
        "size": 10
      }
    }
  }
}
  • size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率
  • aggs:声明这是一个聚合查询,是aggregations的缩写
    • popular_colors:给这次聚合起一个名字,可任意指定。
      • terms:聚合的类型,这里选择terms,是根据词条内容(这里是颜色)划分
        • field:划分桶时依赖的字段

结果

{
  "took": 19,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 8,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "color": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "红",
          "doc_count": 4
        },
        {
          "key": "绿",
          "doc_count": 2
        },
        {
          "key": "蓝",
          "doc_count": 2
        }
      ]
    }
  }
}
  • hits:查询结果为空,因为我们设置了size为0
  • aggregations:聚合的结果
  • popular_colors:我们定义的聚合名称
  • buckets:查找到的桶,每个不同的color字段值都会形成一个桶
    • key:这个桶对应的color字段的值
    • doc_count:这个桶中的文档数量

3.8.2 度量(metrics)相当于聚合的结果

分类后每个颜色的平均价格

求平均值写在aggs里面的 和terms 并列

GET car/orders/_search
{
  "size": 0, 
  "aggs": {
    "color": {
      "terms": {
        "field": "color",
        "size": 10
      },
    "aggs": {
    "avg_price": {
      "avg": {
        "field": "price"
      }
    }
  }
    }
  }
}
  • aggs:我们在上一个aggs(popular_colors)中添加新的aggs。可见度量也是一个聚合
  • avg_price:聚合的名称
  • avg:度量的类型,这里是求平均值
  • field:度量运算的字段

四.Elasticsearch集群

4.1 数据分片和数据备份

在这里插入图片描述
分布式 把一个项目分成几部分,集群是和它本身相同的。

4.2 搭建集群

第一步:把昨天安装的ES软件中的data文件夹的数据删除
在这里插入图片描述
第二步:修改每一个节点的配置文件,下面已第一份配置文件为例

#允许跨域名访问
http.cors.enabled: true
http.cors.allow-origin: "*"
network.host: 127.0.0.1
# 集群的名称
cluster.name: leyou-elastic
#当前节点名称 每个节点不一样
node.name: node-01
#数据的存放路径 每个节点不一样
path.data: d:\class\sorfware\elasticsearch-9201\data
#日志的存放路径 每个节点不一样
path.logs: d:\class\sorfware\elasticsearch-9201\logs
# http协议的对外端口 每个节点不一样
http.port: 9201
# TCP协议对外端口 每个节点不一样
transport.tcp.port: 9301
#三个节点相互发现
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
#声明大于几个的投票主节点有效,请设置为(nodes / 2) + 1
discovery.zen.minimum_master_nodes: 2
# 是否为主节点
node.master: true

第三步:启动集群
把三个节点分别启动,启动时不要着急,要一个一个地启动
使用head插件查看
在这里插入图片描述

4.3 测试集群中创建索引库

PUT heima
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  }
}
  • settings:就是索引库设置,其中可以定义索引库的各种属性
  • number_of_shards:分片数量,这里设置为3
  • number_of_replicas:副本数量,这里设置为1,每个分片一个备份,一个原始数据,共2份。
    在这里插入图片描述

五.Elasticsearch客户端

5.1索引数据操作

5.1.1 初始化客户端

public class ElasticSearchTest {

    private RestHighLevelClient client;
	// Json工具
    private Gson gson = new Gson();

    @Before
    public void init(){
        // 初始化HighLevel客户端
        client = new RestHighLevelClient(
                RestClient.builder(
                        HttpHost.create("http://127.0.0.1:9201"),
                        HttpHost.create("http://127.0.0.1:9202"),
                        HttpHost.create("http://127.0.0.1:9203")
                )
        );
    }

    @After
    public void close() throws IOException {
        // 关闭客户端
        client.close();
    }
}

5.1.2 新增文档

gson 将string 转为json字符串 直接new一个gson使用就可以

package com.leyou.rest;

import com.google.gson.Gson;
import com.leyou.pojo.Item;
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.autoconfigure.h2.H2ConsoleProperties;

import java.io.IOException;

/**
 * @author cyy
 * @date 2020/4/9 14:01
 */
public class RestDemo {
    RestHighLevelClient client =null;
    @Before
    public void init(){
        // 初始化HighLevel客户端
        client = new RestHighLevelClient(
                RestClient.builder(
                        HttpHost.create("http://127.0.0.1:9201"),
                        HttpHost.create("http://127.0.0.1:9202"),
                        HttpHost.create("http://127.0.0.1:9203")
                )
        );
    }

    @After
    public void colse() throws IOException {
        client.close();
    }
     @Test
//     创建索引库
    public void addIndex() throws Exception{

    }

    Gson gson = new Gson();
    @Test
//     添加文档
    public void addDocument() throws Exception{
       Item item = new Item(1L,"小米6","手机","小米",2000d,"ada.jpg");
       String json = gson.toJson(item);
       IndexRequest request = new IndexRequest("item","docs",item.getId().toString());
       request.source(json,XContentType.JSON);

       //执行
        IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
        System.out.println("indexResponse.toString() = " + indexResponse.toString());
    }

}

5.1.3 修改文档

@Test
     public void updateDocument() throws Exception{

         Item item = new Item(1L,"小米7","手机","小米",2000d,"ada.jpg");
         String json = gson.toJson(item);
         UpdateRequest request = new UpdateRequest("item","docs","1");
         request.doc(json,XContentType.JSON);

         UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);

     }

5.14 删除文档

根据id删除

 @Test
     public void deleteByID() throws Exception{
         DeleteRequest request = new DeleteRequest("item","docs","1");
         DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);
     }

5.1.5 查看文档

   @Test
    public void getdecument() throws Exception{
        GetRequest request = new GetRequest("item","docs","1");
        GetResponse getResponse = client.get(request, RequestOptions.DEFAULT);
        System.out.println(getResponse);
    }

5.1.6 批量新增

  @Test
     public void testBulkIndex() throws Exception{
         BulkRequest request = new BulkRequest();
         List<Item> list = new ArrayList<>();

         list.add(new Item(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.leyou.com/13123.jpg"));
         list.add(new Item(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.leyou.com/13123.jpg"));
         list.add(new Item(3L, "华为META10", "手机", "华为", 4499.00, "http://image.leyou.com/13123.jpg"));
         list.add(new Item(4L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.leyou.com/13123.jpg"));
         list.add(new Item(5L, "荣耀V10", "手机", "华为", 2799.00, "http://image.leyou.com/13123.jpg"));

         for (Item item : list) {
             String json =gson.toJson(item);
             request.add(new IndexRequest("item","docs",item.getId().toString()).source(json,XContentType.JSON));
         }
         BulkResponse itemResponses = client.bulk(request, RequestOptions.DEFAULT);
         System.out.println(itemResponses);

     }

5.2 操作

5.2.1 查询

 @Test
     public void search() throws Exception{

         //指定索引库和类型
         SearchRequest searchRequest = new SearchRequest("item");
         searchRequest.types("docs");
//         构建查询
         SearchSourceBuilder searchSourceBuilder  = new SearchSourceBuilder();
//         matchall
         searchSourceBuilder.query(QueryBuilders.matchAllQuery());
         // 模糊查询
         //searchSourceBuilder.query(QueryBuilders.fuzzyQuery("title","大米").fuzziness(Fuzziness.ONE));
         //组合查询
         //searchSourceBuilder.query(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("title", "手机")).must(QueryBuilders.rangeQuery("price").gte(2000d).lte(4000d)));

         //价格区间查询
         // searchSourceBuilder.query(QueryBuilders.rangeQuery("price").gte(2000d).lte(4000));
         searchRequest.source(searchSourceBuilder);

//         执行
         SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

         SearchHits hits = searchResponse.getHits();
         long totalHits = hits.getTotalHits();
         System.out.println("总条数:"+totalHits);

         SearchHit[] searchHits = hits.getHits();
         for (SearchHit searchHit : searchHits) {
             String jsonStr = searchHit.getSourceAsString();
             Item item = gson.fromJson(jsonStr, Item.class);
             System.out.println("item = " + item);
         }
     }

5.2.1 分页,排序

//分页
		searchSourceBuilder.from(0);
        searchSourceBuilder.size(2);
//排序
         searchSourceBuilder.sort("price", SortOrder.DESC);
    @Test
    public void searchPageAndSort() throws Exception {

        //指定索引库和类型
        SearchRequest searchRequest = new SearchRequest("item");
        searchRequest.types("docs");
//         构建查询
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//         matchall
         searchSourceBuilder.query(QueryBuilders.matchAllQuery());
         searchSourceBuilder.from(0);
         searchSourceBuilder.size(2);

         searchSourceBuilder.sort("price", SortOrder.DESC);
         searchRequest.source(searchSourceBuilder);
//         执行
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hits = searchResponse.getHits();
        long totalHits = hits.getTotalHits();
        System.out.println("总条数:" + totalHits);

        SearchHit[] searchHits = hits.getHits();
        for (SearchHit searchHit : searchHits) {
            String jsonStr = searchHit.getSourceAsString();
            Item item = gson.fromJson(jsonStr, Item.class);
            System.out.println("item = " + item);
        }
    }

5.2.2 高亮

//设置高亮
		HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style ='color:red'>" );
        highlightBuilder.postTags("/<span>");
        searchSourceBuilder.highlighter(highlightBuilder);
//在item set标题
			Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            HighlightField highlightField = highlightFields.get("title");
            Text[] fragments = highlightField.getFragments();
            String title = fragments[0].toString();
            String jsonStr = searchHit.getSourceAsString();
            Item item = gson.fromJson(jsonStr, Item.class);
            item.setTitle(title);
            System.out.println("item = " + item);
 @Test
    public void testHighlight() throws Exception {

        //指定索引库和类型
        SearchRequest searchRequest = new SearchRequest("item");
        searchRequest.types("docs");
//         构建查询
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//         matchall
        searchSourceBuilder.query(QueryBuilders.termQuery("title","手机"));
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style ='color:red'>" );
        highlightBuilder.postTags("/<span>");
        searchSourceBuilder.highlighter(highlightBuilder);
        searchRequest.source(searchSourceBuilder);
//         执行
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hits = searchResponse.getHits();
        long totalHits = hits.getTotalHits();
        System.out.println("总条数:" + totalHits);

        SearchHit[] searchHits = hits.getHits();
        for (SearchHit searchHit : searchHits) {
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            HighlightField highlightField = highlightFields.get("title");
            Text[] fragments = highlightField.getFragments();
            String title = fragments[0].toString();



            String jsonStr = searchHit.getSourceAsString();
            Item item = gson.fromJson(jsonStr, Item.class);
            item.setTitle(title);
            System.out.println("item = " + item);
        }
    }

5.2.3 聚合

 @Test
    public void testAggregation() throws Exception {

        //指定索引库和类型
        SearchRequest searchRequest = new SearchRequest("item");
        searchRequest.types("docs");
//         构建查询
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        TermsAggregationBuilder agg = AggregationBuilders.terms("brand_agg").field("brand");

        searchSourceBuilder.aggregation(agg);

        searchRequest.source(searchSourceBuilder);

//         执行
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        Aggregations aggregations = searchResponse.getAggregations();
        Terms terms =aggregations.get("brand_agg");
        List<? extends Terms.Bucket> buckets = terms.getBuckets();

        for (Terms.Bucket bucket : buckets) {
            System.out.println("bucket.getKeyAsString()+bucket.getDocCount() = " + bucket.getKeyAsString() + bucket.getDocCount());
            
        }

5.2.4 过滤

  searchSourceBuilder.fetchSource(new String[]{"id","title"},null);
    searchSourceBuilder.query(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("title","手机")).filter(QueryBuilders.rangeQuery("price").gte(2000).lte(4000)));

六.SpringDataElasticsearch

6.1 什么是SpringDataElasticsearch

SpringDataElasticsearch(以后简称SDE)是Spring Data项目下的一个子模块。
Spring Data 的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提高开发效率。

6.2 配置SpringDataElasticsearch

6.2.1 pom

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

6.2.2 yml

spring:
  data:
    elasticsearch:
      cluster-name: leyou-elastic
      cluster-nodes: 127.0.0.1:9301,127.0.0.1:9302,127.0.0.1:9303

6.2.3 引导类


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class EsApplication {
    public static void main(String[] args) {
        SpringApplication.run(EsApplication.class,args);
    }
}

6.3 索引库操作

6.3.1 在实体类上映射

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "leyou",type = "goods",shards = 3)
public class Goods {
    @Id
    private Long id;
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String title; //标题
    @Field(type = FieldType.Keyword)
    private String category;// 分类
    @Field(type = FieldType.Keyword)
    private String brand; // 品牌
    @Field(type = FieldType.Double)
    private Double price; // 价格
    @Field(type = FieldType.Keyword,index = false)
    private String images; // 图片地址
}

几个用到的注解:

  • @Document:声明索引库配置
    • indexName:索引库名称
    • type:类型名称,默认是“docs”
    • shards:分片数量,默认5
    • replicas:副本数量,默认1
  • @Id:声明实体类的id
  • @Field:声明字段属性
    • type:字段的数据类型
    • analyzer:指定分词器类型
    • index:是否创建索引

6.3.2 创建索引库

@SpringBootTest
@RunWith(SpringRunner.class)
public class SDEManageDemo {
	@Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

     @Test
     public void test() throws Exception{

         elasticsearchTemplate.createIndex(Goods.class);
     }
}

6.3.2 创建mapping

 @Test
      public void creatMapping() throws Exception{

         elasticsearchTemplate.putMapping(Goods.class);
     }

在这里插入图片描述

6.4 索引数据CRUD

6.4.1 自定义接口

我们需要自定义接口,继承ElasticsearchRepository:

package com.leyou;

import com.leyou.pojo.Goods;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface GoodsRepostroty extends ElasticsearchRepository<Goods,Long> {



}

6.4.2 数据的增删改

update和save 是一样的

  @Autowired
     private GoodsRepostroty goodsRepostroty;
     @Test
     public void addDocument(){
         Goods goods = new Goods(1L, "小米手机9", " 手机",
                 "小米", 3499.00, "http://image.leyou.com/13123.jpg");

         goodsRepostroty.save(goods);
     }

批量新增

@Test
    public void bulkAddDocument(){
        List<Goods> list = new ArrayList<>();
        list.add(new Goods(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.leyou.com/13123.jpg"));
        list.add(new Goods(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.leyou.com/13123.jpg"));
        list.add(new Goods(3L, "华为META10", "手机", "华为", 4499.00, "http://image.leyou.com/13123.jpg"));
        list.add(new Goods(4L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.leyou.com/13123.jpg"));
        list.add(new Goods(5L, "荣耀V10", "手机", "华为", 2799.00, "http://image.leyou.com/13123.jpg"));

        goodsRepostroty.saveAll(list);
    }

6.4.2 查询数据

根据id查询

 @Test
     public void search() throws Exception{
         Optional<Goods> goods = goodsRepostroty.findById(1L);
         Goods goods1 = goods.get();
         System.out.println(goods1);

     }

查询所有

 @Test
     public void search() throws Exception{
        /* Optional<Goods> goods = goodsRepostroty.findById(1L);
         Goods goods1 = goods.get();
         System.out.println(goods1);*/

         Iterable<Goods> all = goodsRepostroty.findAll();
         for (Goods goods : all) {
             System.out.println(goods);
         }

     }

根据条件查询

标题中带手机的

 @Test
      public void byTitleTest() throws Exception{
        List<Goods> goodslist =goodsRepostroty.findByTitle("手机");
          for (Goods goods : goodslist) {
              System.out.println(goods);
          }
     }

自定义方法 无需写实现,SDE会自动帮我们实现该方法

package com.leyou;

import com.leyou.pojo.Goods;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface GoodsRepostroty extends ElasticsearchRepository<Goods,Long> {


    List<Goods> findByTitle(String title);

}

自定义方法关键字
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.4.3 原生查询

查询条件的构建是通过一个名为NativeSearchQueryBuilder的类来完成的

  @Test
      public void nativeQuery() throws Exception{
          NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
          nativeSearchQueryBuilder.withQuery(QueryBuilders.termQuery("title","手机"));
          AggregatedPage<Goods> goods = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
          List<Goods> goodsList = goods.getContent();
          for (Goods goods1 : goodsList) {
              System.out.println(goods1);
          }
      }

分页和排序

 @Test
      public void nativeQuery() throws Exception{
          NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

          nativeSearchQueryBuilder.withQuery(QueryBuilders.termQuery("title","手机"));
//         当前页码是从0开始的
//          分页和排序
          nativeSearchQueryBuilder.withPageable(PageRequest.of(0,2, Sort.by(Sort.Direction.DESC,"price")));
          AggregatedPage<Goods> goods = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
          List<Goods> goodsList = goods.getContent();
          for (Goods goods1 : goodsList) {
              System.out.println(goods1);
          }
      }

聚合

 @Test
    public void nativeAggQuery() throws Exception{

       NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
       TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("brand_agg").field("brand");

       nativeSearchQueryBuilder.addAggregation(aggregationBuilder);
       AggregatedPage<Goods> agg = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
       Aggregations aggregations = agg.getAggregations();

       Aggregation brand_agg = aggregations.get("brand_agg");
       System.out.println(brand_agg);
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
        buckets.forEach(bucket->{
            System.out.println(bucket.getKeyAsString());
            System.out.println(bucket.getDocCount());
        });

    }

高亮

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值