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分词器
- 导入jar
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>
- 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:划分桶时依赖的字段
- terms:聚合的类型,这里选择terms,是根据词条内容(这里是颜色)划分
- popular_colors:给这次聚合起一个名字,可任意指定。
结果
{
"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());
});
}
高亮