A081_Lucene_ElasticSearch

目录

1.内容介绍

1.认识全文检索
2.Lucene入门(掌握)
3.认识 ElasticSearch
4.ElasticSearch基本操作(掌握)
5.Java操作ElasticSearch(掌握)

2.认识全文检索

2.1.什么是全文检索

对非结构化数据的搜索就叫全文检索,狭义的理解主要针对文本数据的搜索。

结构化数据:

业界指关系模型数据,即以关系数据库表形式管理的数据

半结构数据:

非关系模型的、有基本固定结构模式的数据,例如日志文件、XML文档、JSON文档、Email等。

非结构化数据:

没有固定模式的数据,如WORD、PDF、PPT、EXL,各种格式的图片、视频等。
非结构化数据是数据结构不规则或不完整,没有预定义的数据模型,不方便用数据库二维逻辑表来表现的数据。包括所有格式的办公文档、文本、图片、XML, HTML、各类报表、图像和音频/视频信息等等

理解:可以理解为全文检索就是把没有结构化的数据变成有结构的数据,然后进行搜索,因为有结构化的数据通常情况下可以按照某种算法进行搜索。

2.2.全文检索的特点

相关度最高的排在最前面,官网中相关的网页排在最前面; java
关键词的高亮。
只处理文本,不处理语义。 以单词方式进行搜索
比如在输入框中输入“中国的首都在哪里”,搜索引擎不会以对话的形式告诉你“在北京”,而仅仅是列出包含了搜索关键字的网页。

2.3.常见的全文检索方案

全文搜索工具包-Lucene(核心)
全文搜索服务器 ,Elastic Search(ES) / Solr等封装了lucene并扩展

3.Lucene介绍

3.1.什么是Lucene

Lucene是apache下的一个开源的全文检索引擎工具包(一堆jar包)。它为软件开发人员提供一个简单易用的工具包(类库),以方便的在小型目标系统中实现全文检索的功能。

Lucene适用于中小型项目 ,ES适用于中大型项目(它底层是基于lucene实现的)

3.2.Lucene实现全文检索架构

任何技术都有一些核心,Lucene也有核心,而它的核心分为:索引创建,索引搜索。接下来我们就一一的来看。
在这里插入图片描述

3.2.1.索引创建

将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程。那么索引里面究竟存的什么,以及如何创建索引呢?在这通过下面的例子来解答这个问题。
首先构造三个不同的句子,有长有短:
在这里插入图片描述
在①处分别为3个句子加上编号,然后进行分词,把每一个单词分解出来与编号对应放在②处;在搜索的过程中,对于搜索的过程中大写和小写指的都是同一个单词,在这就没有区分的必要,按规则统一变为小写放在③处;要加快搜索速度,就必须保证这些单词的排列时有一定规则,这里按照字母顺序排列后放在④处;最后再简化索引,合并相同的单词,就得到如下结果:
在这里插入图片描述
通常在数据库中我们都是根据文档找到内容,而这里是通过词,能够快速找到包含他的文档,这就是文档倒排链表。

以上就是lucene索引结构中最核心的部分。我们注意到关键字是按字符顺序排列的(lucene没有使用B树结构),因此lucene可以用二元搜索算法快速定位关键词。

3.2.2.索引搜索

就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程。
在这里插入图片描述
比如我们要搜索java world两个关键词,符合java的有1,2两个文档,符合world的有1,3两个文档,在搜索引擎中直接这样排列两个词他们之间是OR的关系,出现其中一个都可以被找到,所以这里3个都会出来。全文检索中是有相关性排序的,那么结果在是怎么排列的呢?hello java world中包含两个关键字排在第一,另两个都包含一个关键字,得到结果,hello lucene world排在第二,java在最长的句子中占的权重最低排在结果集的第三。从这里可以看出相关度排序还是有一定规则的。

1.Lucene入门

4.Lucene-Helloworld程序

Lucene的索引库和数据库一样,都提供相应的API来便捷操作。

Lucene中的索引维护使用IndexWriter,由这个类提供添删改相关的操作;索引的搜索则是使用IndexSearcher进行索引的搜索。

HelloWorld代码如下,导入三个jar包:lucene-analyzers-common-5.5.0.jar,lucene-core-5.5.0.jar,lucene-queryparser-5.5.0.jar

<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-core</artifactId>
    <version>5.5.0</version>
</dependency>
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers-common</artifactId>
    <version>5.5.0</version>
</dependency>
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-queryparser</artifactId>
    <version>5.5.0</version>
</dependency>
4.1.创建索引

在这里插入图片描述

步骤:
1、把文本内容转换为Document对象
2、准备IndexWriter(索引写入器)
3、通过IndexWriter,把Document添加到缓冲区并提交
//创建索引的数据 现在写死,以后根据实际应用场景
String doc1 = "hello world";
String doc2 = "hello java world";
String doc3 = "hello lucene world";
private String path ="F:/eclipse/workspace/lucene/index/hello";
@Test
public void testCreate() {
	try {
		//2、准备IndexWriter(索引写入器)
		//索引库的位置 FS fileSystem
		Directory d = FSDirectory.open(Paths.get(path ));
		//分词器 -- 把文档进行分词
		Analyzer analyzer = new SimpleAnalyzer();
		//索引写入器的配置对象
		IndexWriterConfig conf = new IndexWriterConfig(analyzer);
		IndexWriter indexWriter = new IndexWriter(d, conf);
		System.out.println(indexWriter);
		
		//1、 把文本内容转换为Document对象
		//把文本转换为document对象
		Document document1 = new Document();
		//标题字段
		document1.add(new TextField("title", "doc1", Store.YES));
		document1.add(new TextField("content", doc1, Store.YES));
		//添加document到缓冲区
		indexWriter.addDocument(document1);
		Document document2 = new Document();
		//标题字段
		document2.add(new TextField("title", "doc2", Store.YES));
		document2.add(new TextField("content", doc2, Store.YES));
		//添加document到缓冲区
		indexWriter.addDocument(document2);
		Document document3 = new Document();
		//标题字段
		document3.add(new TextField("title", "doc3", Store.YES));
		document3.add(new TextField("content", doc3, Store.YES));
			
		//3 、通过IndexWriter,把Document添加到缓冲区并提交
		//添加document到缓冲区
		indexWriter.addDocument(document3);
		indexWriter.commit();
	    indexWriter.close();
			
	} catch (Exception e) {
		e.printStackTrace();
	}
	
}

在这里插入图片描述

4.2.搜索索引

在这里插入图片描述

1 封装查询提交为查询对象
2 准备IndexSearcher 
3 使用IndexSearcher传入查询对象做查询-----查询出来只是文档编号DocID
4 通过IndexSearcher传入DocID获取文档
5 把文档转换为前台需要的对象
@Test
public void testSearch() {
	String keyWord = "lucene";
	try {
		// * 1 封装查询提交为查询对象
	    //通过查询解析器解析一个字符串为查询对象
		String f = "content"; //查询的默认字段名,
		Analyzer a = new SimpleAnalyzer();//查询关键字要分词,所有需要分词器
		QueryParser parser = new QueryParser(f, a);
		Query query = parser.parse("content:"+keyWord); 

		// * 2 准备IndexSearcher
		Directory d = FSDirectory.open(Paths.get(path ));
		IndexReader r = DirectoryReader.open(d);
		IndexSearcher searcher = new IndexSearcher(r);

		// * 3 使用IndexSearcher传入查询对象做查询-----查询出来只是文档编号DocID
		TopDocs topDocs = searcher.search(query, 1000);//查询ton条记录 前多少条记录
		System.out.println("总命中数:"+topDocs.totalHits);
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;//命中的所有的文档的封装(docId)
		


// * 4 通过IndexSearcher传入DocID获取文档
		for (ScoreDoc scoreDoc : scoreDocs) {
			int docId = scoreDoc.doc;
			Document document = searcher.doc(docId);
			// * 5 把文档转换为前台需要的对象 Docment----> Article
			System.out.println("=======================================");
			System.out.println("title:"+document.get("title")
							+",content:"+document.get("content"));
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}

4.3.总结
1.哪些字段需要索引以及分词?

2.哪些字段需要索引但是不需要分词?
	要进行搜索的字段就要创建索引(倒排索引文档)
	不参与搜索的字段不创建索引
	要进行关键字搜索的(模糊)字段就要索引且要分词     -  intro like “%xx%”
	需要进行等值查询的要索引但是不分词              -  name = “oo”

3.哪些数据要放到数据区的?
	1.列表需要展示的数据
	2.查询条件需要用到的数据(要创建索引库,不一定要放到数据去)

5.认识ElasticSearch

5.1.为什么要使用ElasticSearch

虽然全文搜索领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。但是,Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene的配置及使用非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。
实际项目中,我们建立一个网站或应用程序,并要添加搜索功能,令我们受打击的是:搜索工作是很难的。我们希望我们的搜索解决方案要快,我们希望有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON/XML通过HTTP的索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并在需要扩容时方便地扩展到数百,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。

5.2.ElasticSearch(简称ES)

ES即为了解决原生Lucene使用的不足,优化Lucene的调用方式,并实现了高可用的分布式集群的搜索方案,其第一个版本于2010年2月出现在GitHub上并迅速成为最受欢迎的项目之一。
首先,ES的索引库管理支持依然是基于Apache Lucene™的开源搜索引擎。
ES也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
不过,ES的核心不在于Lucene,其特点更多的体现为:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 分布式的实时分析搜索引擎
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据:KB-MB-GB-TB-PB
  • 高度集成化的服务,你的应用可以通过简单的 RESTful API、各种语言的客户端甚至命令行与之交互。
  • 上手Elasticsearch非常容易。它提供了许多合理的缺省值,并对初学者隐藏了复杂的搜索引擎理论。它拥有开瓶即饮的效果(安装即可使用),只需很少的学习既可在生产环境中使用。
5.3.ES的使用者及类似框架

典型使用案例

  • Github(美国)使用Elasticsearch搜索20TB的数据,包括13亿的文件和1300亿行的代码.
  • Foursquare实时搜索5千万地点信息?Foursquare每天都用Elasticsearch做这样的事.
  • 德国SoundCloud使用Elasticsearch来为1.8亿用户提供即时精准的音乐搜索服务.
  • Mozilla公司以火狐著名,它目前使用 WarOnOrange 这个项目来进行单元或功能测试,测试的结果以 json的方式索引到elasticsearch中,开发人员可以非常方便的查找 bug.
  • Sony公司使用elasticsearch 作为信息搜索引擎.

类似框架

  • Solr(重量级对手)
    Apache Lucene项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如Word、PDF)的处理。Solr是高度可扩展的,并提供了分布式搜索和索引复制。Solr是最流行的企业级搜索引擎,Solr4 还增加了NoSQL支持。Solr和ES比较:
    Solr 利用 Zookeeper(注册中心) 进行分布式管理,支持更多格式的数据(HTML/PDF/CSV),官方提供的功能更多在传统的搜索应用中表现好于 ES,但实时搜索效率低。
    ES自身带有分布式协调管理功能,但仅支持json文件格式,本身更注重于核心功能,高级功能多有第三方插件提供,在处理实时搜索应用时效率明显高于 Solr 。
  • Katta
    基于 Lucene 的,支持分布式,可扩展,具有容错功能,准实时的搜索方案。
    优点:开箱即用,可以与 Hadoop (大数据)配合实现分布式。具备扩展和容错机制。
    缺点:只是搜索方案,建索引部分还是需要自己实现。在搜索功能上,只实现了最基本的需求。成功案例较少,项目的成熟度稍微差一些。
  • HadoopContrib --大数据相关的东西 (大数据工程师)
    Map/Reduce 模式(云计算)的,分布式建索引方案,可以跟 Katta 配合使用。
    优点:分布式建索引,具备可扩展性。
    缺点:只是建索引方案,不包括搜索实现。工作在批处理模式,对实时搜索的支持不佳。
5.4.小结

ElasticSearch简化了全文检索lucene的使用,同时增加了分布式的特性,使得构建大规模分布式全文检索变得非常容易。

6.ES安装及使用说明

6.1.包含的内容

ES的安装比较简单,只需要官方下载ES的运行包,然后启动ES服务即可。
ES的使用主要是通过能够发起HTTP请求的终端来接入,比如Poster插件、CURL、kibana5等。

6.2.安装ES

ES服务只依赖于JDK,推荐使用JDK1.7+。
① 下载ES安装包
官方下载地址:https://www.elastic.co/downloads/elasticsearch
本课程以在window环境下,ES 5.2.2版本为例,下载对应的ZIP文件
在这里插入图片描述
② 运行ES
bin/elasticsearch.bat
在这里插入图片描述
③ 验证
访问:http://localhost:9200/
在这里插入图片描述
看到上图信息,恭喜你,你的ES集群已经启动并且正常运行.

6.3.ES交互方式客户端

① 基于RESTful API
ES和所有客户端的交互都是使用JSON格式的数据.
其他所有程序语言都可以使用RESTful API,通过9200端口的与ES进行通信,在开发测试阶段,你可以使用你喜欢的WEB客户端, curl命令以及火狐的POSTER插件方式和ES通信。
Curl命令方式:
默认windows下不支持curl命令,在资料中有curl的工具及简单使用说明。
在这里插入图片描述
火狐的POSTER插件界面:
类似于Firebug,在火狐的“扩展”中搜索“POSTER”,并安装改扩展工具。
在这里插入图片描述
使用POSTER模拟请求的效果
在这里插入图片描述
② Java API
ES为Java用户提供了两种内置客户端:
节点客户端(node client):节点客户端以无数据节点(none data node)身份加入集群,换言之,它自己不存储任何数据,但是它知道数据在集群中的具体位置,并且能够直接转发请求到对应的节点上。
传输客户端(Transport client):这个更轻量的传输客户端能够发送请求到远程集群。它自己不加入集群,只是简单转发请求给集群中的节点。

两个Java客户端都通过9300端口与集群交互,使用ES传输协议(ES Transport Protocol)。集群中的节点
之间也通过9300端口进行通信。如果此端口未开放,你的节点将不能组成集群。

注意:Java客户端所在的ES版本必须与集群中其他节点一致,否则,它们可能互相无法识别。
6.4.扩展:Restful认识

Restful是一种面向资源的架构风格,可以简单理解为:使用URL名词定位资源,用HTTP动词(GET,POST,DELETE,PUT)描述操作。

使用Restful的好处:
透明性,暴露资源存在。充分利用 HTTP 协议本身语义。
无状态,这点非常重要。在调用一个接口(访问、操作资源)的时候,可以不用考虑上下文,不用考虑当前状态,极大的降低了复杂度。

HTTP 本身提供了丰富的内容协商手段,无论是缓存,还是资源修改的乐观并发控制,都可以以业务无关的中间件来实现。

Restful的典型特征:
Server提供的RESTful API中,URL中只使用名词来指定资源。
“资源”是REST架构或者说整个网络处理的核心。比如:
GET http://api.itsource.cn/emp/323: 获取323号员工的基本资料;
GET http://api.itsource.cn/emps: 获取源码时代所有员工资料列表;
REST 是面向资源的,这个概念非常重要,而资源是通过 URI 进行暴露
URI 的设计只要负责把资源通过合理方式暴露出来就可以了。对资源的操作与它无关,所以REST 通过 URI 暴露资源时,会强调不要在 URI 中出现动词。
比如:左边是错误的设计,而右边是正确的
GET /rest/api/getDogs -> GET /rest/api/dogs 获取所有小狗狗
GET /rest/api/getDogById?id=1 -> GET /rest/api/dog/1 获取所有小狗狗
GET /rest/api/addDogs -> POST /rest/api/dogs 添加一个小狗狗
POST /rest/api/editDogs/12 -> PUT /rest/api/dogs/12 修改一个小狗狗
POST /rest/api/deleteDogs/12 ->DELETE /rest/api/dogs/12 删除一个小狗

左边的这种设计,很明显不符合REST风格,URI 只负责准确无误的暴露资源,而 getDogs/addDogs…已经包含了对资源的操作,这是不对的。相反右边却满足了,它的操作是使用标准的HTTP动词来体现。
③ 用HTTP协议里的动词来实现资源的添加,修改,删除等操作。
即通过HTTP动词来实现资源的状态扭转:
GET 用来获取资源,
POST 用来新建资源(也可以用于更新资源),
PUT 用来更新资源,
DELETE 用来删除资源。
比如:
GET http://api.itsource.cn/emp/323
POST http://api.itsource.cn/emp/232: 修改一个员工
PUT http://api.itsource.cn/emp: 添加员工资料
DELETE http://api.itsource.cn/emp/323: 删除323号员工

6.4.辅助管理工具Kibana5

① Kibana5.2.2下载地址:https://www.elastic.co/downloads/kibana
② 解压并编辑config/kibana.yml,设置elasticsearch.url的值为已启动的ES
③ 启动Kibana5 : bin\kibana.bat
④ 默认访问地址:http://localhost:5601
在这里插入图片描述
Discover:可视化查询分析器
Visualize:统计分析图表
Dashboard:自定义主面板(添加图表)
Timelion:Timelion是一个kibana时间序列展示组件(暂时不用)
Dev Tools :Console(同CURL/POSTER,操作ES代码工具,代码提示,很方便)
Management:管理索引库(index)、已保存的搜索和可视化结果(save objects)、设置 kibana 服务器属性。

7.ES相关概念理解

7.1.Index:索引库

包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。

7.2.Type:类型

每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。

7.3.Document&field

文档,es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以去存储多个document。一个document里面有多个field,每个field就是一个数据字段
在这里插入图片描述

8.ES文档操作

8.1.什么是ES中的文档

document --存放数据
在ES里面 通过index索引库 type 类型(表) 行({id:name:}) 列 id /name
ES是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在ES中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。
ES使用Javascript对象符号(JavaScript Object Notation),也就是JSON,作为文档序列化格式。JSON现在已经被大多语言所支持,而且已经成为NoSQL领域的标准格式。
ES存储的一个员工文档的格式示例:

{
	"email": "laozheng@itsource.cn",
	"name": "老郑",
	"info": {
	     "addr": "四川省成都市",
	     "age": 30,
	     "interests": [ "美食", "美女" ]
	},
	"join_date": "2016-06-01"
}

尽管原始的 employee对象很复杂,但它的结构和对象的含义已经被完整的体现在JSON中了,在ES中将对象转化为JSON并做索引要比在表结构中做相同的事情简单的多。
一个文档不只有数据。它还包含元数据(metadata)—关于文档的信息。三个必须的元数据节点是:
在这里插入图片描述
_index:索引库,类似于关系型数据库里的“数据库”—它是我们存储和索引关联数据的地方。
_type:在应用中,我们使用对象表示一些“事物”,例如一个用户、一篇博客、一个评论,或者一封邮件。可以是大写或小写,不能包含下划线或逗号。我们将使用 employee 做为类型名。
_id:与 _index 和 _type 组合时,就可以在ELasticsearch中唯一标识一个文档。当创建一个文档,你可以自定义 _id ,也可以让Elasticsearch帮你自动生成。
另外还包括:_uid文档唯一标识(_type#_id)
_source:文档原始数据
_all:所有字段的连接字符串

8.2.文档的增删改-重点掌握

我们以员工对象为例,我们首先要做的是存储员工数据,每个文档代表一个员工。在ES中存储数据的行为就叫做索引(indexing),文档归属于一种类型(type),而这些类型存在于索引(index)中,我们可以简单的对比传统数据库和ES的对应关系:
关系数据库(MYSQL)-> 数据库DB -> 表TABLE -> 行ROW -> 列Column
Elasticsearch -> 索引库Indices -> 类型Types -> 文档Documents -> 字段Fields
ES集群可以包含多个索引(indices)(数据库),每一个索引库中可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。

创建索引文档
①使用自己的ID创建:
PUT {index}/{type}/{id}
{
	"field": "value",
	...
}
②ES内置ID创建:
POST {index}/{type}/
{
	"field": "value",
	...
}
①②ES响应内容:
{
	"_index": "itsource",
	"_type": "employee",
	"_id": xxxxxx,
	"_version": 1, //文档版本号
	"created": true //是否新增
}

③ 获取指定ID的文档
GET itsource/employee/123?pretty --没有效果 经过格式化
③返回的内容:
{
	"_index" : "itsource",
	"_type" : "employee",
	"_id" : "123",
	"_version" : 1,
	"found" : true,
	"_source" : {
	   "email": "laozheng@itsource.cn",
	   "fullName": "老郑",
	   ...
	   "joine_date": "2016-06-01"
	}
}
返回文档的部分字段:
GET默认返回整个文档,通过GET /itsource/employee/123?_source=fullName,email
只返回文档内容,不要元数据:
GET itsource/employee/123/_source
④ 修改文档
更新整个文档
同post {index}/{type}/{id}
{
   	Id:1,
 	Name:xx
}
在响应中,我们可以看到Elasticsearch把 _version  增加了。
{
	...
	"_version" : 2,
	"created": false
}
created  标识为 false  因为同索引、同类型下已经存在同ID的文档。
在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。旧版本文档不会立即消失,但你也不能去访问它。Elasticsearch会在你继续索引更多数据时清理被删除的文档。
局部更新文档
接受一个局部文档参数 doc,它会合并到现有文档中,对象合并在一起,存在的标量字段被覆盖,新字段被添加。
POST itsource/employee/123/_update
{
	“doc”:{
		"email" : "nixianhua@itsource.cn", 
		"salary": 1000
	}
}
email会被更新覆盖,salary会新增。
这个API 似乎 允许你修改文档的局部,但事实上Elasticsearch
遵循与之前所说完全相同的过程,这个过程如下:
1. 从旧文档中检索JSON
2. 修改它
3. 删除旧文档
4. 索引新文档

脚本更新文档 --了解
也可以通过使用简单的脚本来进行。这个例子使用一个脚本将age加5:
POST itsource/emploee/123/_update
{
	"script" : "ctx._source.age += 5"
}
在上面的例子中, ctx._source指向当前被更新的文档。
注意,目前的更新操作只能一次应用在一个文档上。

删除文档
DELETE {index}/{type}/{id}
存在文档的返回:
{
	"found" : true,
	"_index" : "website",
	"_type" : "blog",
	"_id" : "123",
	"_version" : 3
}
不存在的返回:
{
	"found" : false,
	"_index" : "website",
	"_type" : "blog",
	"_id" : "123",
	"_version" : 4
}
注意:尽管文档不存在,但_version依旧增加了。这是内部记录的一部分,它确保在多节点间不同操作可以有正确的顺序。

批量操作bulk  API -- 后台java程序api
使用单一请求来实现多个文档的create、index、update 或 delete。
Bulk请求体格式:
{ PUT: { metadata }}\n
{ request body }\n
{ action: { metadata }}\n
{ request body }\n
每行必须以 "\n"  符号结尾,包括最后一行。这些都是作为每行有效的分离而做的标记。
create当文档不存在时创建之。
index创建新文档或替换已有文档。
update局部更新文档。
delete删除一个文档。
例如:
POST _bulk
{ "delete": { "_index": "itsource", "_type": "employee", "_id": "123" }}
{ "create": { "_index": "itsource", "_type": "blog", "_id": "123" }}
{ "title": "我发布的博客" }
{ "index": { "_index": "itsource", "_type": "blog" }}
{ "title": "我的第二博客" }

注意:delete后不需要请求体,最后一行要有回车
8.3.文档的简单查询
8.3.1.通过文档ID获取


GET crm/employee/1

8.3.2.批量获取

mget API参数是一个 docs数组,数组的每个节点定义一个文档的 _index 、 _type 、 _id 元数据。如果你只想检索一个或几个确定的字段,也可以定义一个 _source 参数:
方式1:GET _mget (了解)

{
	"docs" : [
		{
			"_index" : "itsource",
			"_type" : "blog",
			"_id" : 2
		},
		{
			"_index" : "itsource",
			"_type" : "employee",
			"_id" : 1,
			"_source": "email,age"
		}
	]
}

方式2:同一个索引库的同一个类型下 (掌握)

GET itsource/blog/_mget
{
	"ids" : [ "2", "1" ]
}
8.3.3.空搜索

没有指定任何的查询条件,只返回集群索引中的所有文档: GET _search

8.3.4.分页搜索

和SQL使用 LIMIT 关键字返回只有一页的结果一样,Elasticsearch接受 from 和 size 参数:
size : 每页条数,默认 10
from : 跳过开始的结果数,默认 0
如果你想每页显示5个结果,页码从1到3,那请求如下:

GET _search?size=5
GET _search?size=5&from=5
GET _search?size=5&from=10
8.3.5 查询字符串搜索

一个搜索可以用纯粹的uri来执行查询。在这种模式下使用搜索,并不是所有的选项都是暴露的。它可以方便快速进行 curl 测试。

查询年龄为25岁的员工

GET itsource/employee/_search?q=age:25&size=5&from=10
age[20 TO 30]

在这里插入图片描述
在这里插入图片描述
现在我们条件比较少,直接在上面写条件,很舒服,如果我们添加比较多,上面的方式就是不适合

8.4.小结

本节主要是介绍了ES文档的表现形式以及文档的增删改查(简单)的基本操作。

9.ES查询

9.1.DSL查询
1.什么是DSL

由ES提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。
DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。我们可以这样表示之前关于“老郑”的查询:
查询字符串模式(查询条件很少):
GET itsource/employee/_search?q=fullName:老郑

DSL模式(查询条件很多):

GET itsource/employee/_search
{
		"query" : {
		   "match" : { //标准查询(分词查询 、模糊查询)
		   		"fullName" : "赵郑"
		}
	}
}

通配符匹配

GET itsource/employee/_search
{
	 "query": {
	        "wildcard" : { //通配符查询
				 "name" : "*zs*" }
    }
}

对于简单查询,使用查询字符串比较好,但是对于复杂查询,由于条件多,逻辑嵌套复杂,查询字符串不易组织与表达,且容易出错,因此推荐复杂查询通过DSL使用JSON内容格式的请求体代替。

DSL有两部分组成:DSL查询和DSL过滤

2.DSL查询

使用DSL查询,必须要传递query参数给ES。
GET _search
{“query”: YOUR_QUERY_HERE}
一个常用的相对完整的DSL查询:

GET itsource/employee/_search
{
	"query": {
	   "match_all": {}
	},
	"from": 20, 
	"size": 10,
	"_source": ["fullName", "age", "email"],
	"sort": [{"join_date": "desc"},{"age": "asc"}]
}

上面查询 表示 查询所有数据,查询fullName,age和email,按照加入日期和年龄进行排序

3.DSL过滤

DSL过滤语句和DSL查询语句非常相似,但是它们的使用目的却不同 :
DSL过滤 查询文档的方式更像是对于我的条件“有”或者“没有”,–精确查询
而DSL查询语句则像是“有多像”。–类似于模糊查询

DSL过滤和DSL查询在性能上的区别 :

过滤结果可以缓存并应用到后续请求。
查询语句同时 匹配文档,计算相关性,所以更耗时,且不缓存。
过滤语句 可有效地配合查询语句完成文档过滤。

有点像如下关系:
where username like ‘%zs%’ and age = 11

where age = 11 and username like ‘%zs%’

原则上,使用DSL查询 做模糊查询 或其他需要进行相关性评分的场景,其它全用DSL过滤。

where (description = “hello world” ) and ( description like “%hello%” or description like “%java%” )

2.0以上的用法
Get index/type/_search
{
	"query": {
	   "bool": {  //booleanQuery 组合查询
	          "must": [   //查询 与(must) 或(should) 非(must not)
					{ 
							"match": {//标准查询(分词匹配)  term:单词查询(部分词)
								"description": "hello java" 
							}
					}
				],
	         "filter": {  //过滤
	               "term": {"description": "hello world"}
	           }
	    }
	},
	"from": 20, 
	"size": 10,
	"_source": ["fullName", "age", "email"],
	"sort": [{"join_date": "desc"},{"age": "asc"}]
}
~~2.0以前的用法~~
{
	"query": {
	   "filtered": {
	          "query": {
	                 "match": {"description": "search" }
	            },
	           "filter": {
	               "term": {"tags": "lucene"}
	           }
	    }
	}
}
4.使用DSL查询与过滤

① 全匹配(match_all)
普通搜索(匹配所有文档):

{
	"query" : {
		"match_all" : {}
	}
}

如果需要使用过滤条件(在所有文档中过滤,红色部分默认可不写):

GET _search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ],
      "filter": {
        "term": {
          "name": "zs1"
        }
      }
    }
  }
}

② 标准查询(match和multi_match)
match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。
如果你使用match查询一个全文本字段,它会在真正查询之前用分析器先分析查询字符:

{
	"query": {
		"match": {
			"fullName": "Steven King"
		}
	}
}

上面的搜索会对Steven King分词,并找到包含Steven或King的文档,然后给出排序分值。
如果用 match 下指定了一个确切值,在遇到数字,日期,布尔值或者 not_analyzed的字符串时,它将为你搜索你给定的值,如:

{ "match": { "age": 26 }}
{ "match": { "date": "2014-09-01" }}
{ "match": { "public": true }}
{ "match": { "tag": "full_text" }}
multi_match  查询允许你做 match查询的基础上同时搜索多个字段:
{
	"query":{
		"multi_match": {
			"query": "Steven King",
			"fields": [ "fullName", "title" ]
		}
	}
}

where fullName = “Steven ”or fullName = “King”or title= “Steven ”or title= “King”

上面的搜索同时在fullName和title字段中匹配。
提示:match一般只用于全文字段的匹配与查询,一般不用于过滤。

③单词搜索与过滤(Term和Terms)

{
	"query": {
		"bool": {
			"must": { 
					"match_all": {} 
				}, 
				"filter": { 
					"term": { 
						"tags": "elasticsearch" 
				} 
			} 
		} 
	}
}

Terms搜索与过滤

{
	"query": {
		"terms": {
			"tags": ["jvm", "hadoop", "lucene"],
			"minimum_match": 1
		}
	}
}

minimum_match:至少匹配个数,默认为1

④ 组合条件搜索与过滤(Bool)
组合搜索bool可以组合多个查询条件为一个查询对象,查询条件包括must、should和must_not。
例如:查询爱好有美女,同时也有喜欢游戏或运动,且出生于1990-06-30及之后的人。

{
	"query": {
		"bool": {
			"must": [{"term": {"hobby": "美女"}}],
			"should": [{"term": {"hobby": "游戏"}}, 
						 {"term": {"hobby": "运动"}} 
						],
			"must_not": [
				{"range" :{"birth_date":{"lt": "1990-06-30"}}} 
			],
			"filter": [...],
			"minimum_should_match": 1
		}
	}
}

Hobby=美女 and (hobby=游戏 or hobby=运动) and birth_date >= 1990-06-30
提示: 如果 bool 查询下没有must子句,那至少应该有一个should子句。但是 如果有 must子句,那么没有 should子句也可以进行查询。
⑤ 范围查询与过滤(range)
range过滤允许我们按照指定范围查找一批数据:

{
	"query":{
		"range": {  
			"age": {
				"gte": 20,
				"lt": 30
			}
		}
	}
}

上例中查询年龄大于等于20并且小于30。
gt:>   gte:>=   lt:<   lte:<=
⑥ 存在和缺失过滤器(exists和missing)

{
	"query": {
		"bool": {
			"must": [{
				"match_all": {}
			}],
			"filter": {
				"exists": { "field": "gps" }
			}
		}
	}
}

提示:exists和missing只能用于过滤结果。
⑦ 前匹配搜索与过滤(prefix)
和term查询相似,前匹配搜索不是精确匹配,而是类似于SQL中的like ‘key%’

{
	"query": {
		"prefix": {
			"fullName": "倪"
		}
	}
}

上例即查询姓倪的所有人。
⑧ 通配符搜索(wildcard)
使用*代表0~N个,使用?代表1个。

{
	"query": {
		"wildcard": {
			"fullName": "倪*华"
		}
	}
}
9.2.高亮查询

高亮查询指的是可以给查询的结果中将查询的关键字进行高亮处理,比如加粗,上色等

GET hrm/course/_search
{
  "query": {
    "match": {
      "name": "java"
    }
  },
  "highlight": {
    "fields": {
      "name": {}
    },
    "pre_tags": "<b style='color:red'>",
    "post_tags": "<b/>"
  }
}

在这里插入图片描述

9.3.聚合查询
1.Metrics Aggregations,指标聚合。

它是对文档进行一些权值计算(比如求所有文档某个字段求最大、最小、和、平均值),输出结果往往是文档的权值,相当于为文档添加了一些统计信息。

Max Aggregation:求最大值。基于文档的某个值计算该值在聚合文档中的均值。
Min Aggregation:求最小值。同上
Sum Aggregation:求和。同上
Avg Aggregation:求平均数。同上

Max Aggregation : 查询价格最大值

GET hrm/course/_search
{
  "aggs": {
    "price_max": {
      "max": {
        "field": "price"
      }
    }
  }
}

在这里插入图片描述
其他三个函数用法一样

2.Value Count Aggregation,数量统计

Value Count Aggregation,值计数聚合。计算聚合文档中某个值(可以是特定的数值型字段,也可以通过脚本计算而来)的个数。该
聚合一般域其它 single-value 聚合联合使用,比如在计算一个字段的平均值的时候,可能还会关注这个平均值是由多少个值计算而来。

GET hrm/course/_search
{
  "aggs": {
    "id_count": {
      "value_count": {
        "field": "id"
      }
    }
  }
}

在这里插入图片描述

3.distinct 去重聚合

Cardinality Aggregation,基数聚合。它属于multi-value,基于文档的某个值(可以是特定的字段,也可以通过脚本计算而来),计算文档非重复的个数(去重计数),相当于sql中的distinct。

GET hrm/course/_search
{
  "aggs": {
    "price_count": {
      "cardinality": {
        "field": "price"
      }
    },
    "id_count": {
      "cardinality": {
        "field": "id"
      }
    }
  }
}

在这里插入图片描述

4.统计聚合

Stats Aggregation,统计聚合。它属于multi-value,基于文档的某个值(可以是特定的数值型字段,也可以通过脚本计算而来),计算出一些统计信息(min、max、sum、count、avg5个值)。

GET hrm/course/_search
{
  "aggs": {
    "price_stats": {
      "stats": {
        "field": "price"
      }
    }
  }
}

在这里插入图片描述

5.拓展的统计聚合

Extended Stats Aggregation,扩展统计聚合。它属于multi-value,比stats多4个统计结果: 平方和、方差、标准差、平均值加/减两个标准差的区间

GET hrm/course/_search
{
  "aggs": {
    "price_stats": {
      "extended_stats": {
        "field": "price"
      }
    }
  }
}

在这里插入图片描述

6.Terms Aggregation 分组并统计数量

Terms Aggregation,词聚合。基于某个field,该 field 内的每一个【唯一词元】为一个桶,并计算每个桶内文档个数。默认返回顺序是按照文档个数多少排序。它属于multi-bucket。当不返回所有 buckets 的情况(它size控制),文档个数可能不准确。
统计在文档中某个字段的值出现的次数

GET hrm/course/_search
{
  "aggs": {
    "price_terms": {
      "terms": {
        "field": "price",
        "size": 10
      }
    }
  }
}

在这里插入图片描述
terms统计的是每种情况的条数是多少,SQL里面的 group by 后再 count

统计每个等级的数量以及价格平均值
在这里插入图片描述
统计每个等级的数量,且计算出每个等级的平均薪资
在这里插入图片描述

子聚合:根据机构ID聚合,然后再根据机构名字子聚合 , 比如:要聚合出所有结果集中的机构ID,然后用子聚合的方式聚合出机构的名字,可以用来实现查询条件的联动,如京东的品牌联动查询条件

GET hrm/course/_search
{
  "aggs": {
    "tenantIdAgg": {
      "terms": {
        "field": "tenantId",
        "size": 10
      },
      "aggs": {
        "tenantNameAgg": {
          "terms": {
            "field": "tenantName",
            "size": 10
          }
        }
      }
    }
  }
}

在这里插入图片描述

7.Top Hits 最高匹配

Top Hits Aggregation,最高匹配权值聚合。获取到每组前n条数据,相当于sql 中Top(group by 后取出前n条)。它跟踪聚合中相关性最高的文档,该聚合一般用做 sub-aggregation,以此来聚合每个桶中的最高匹配的文档,较为常用的统计。

案例:统计每个价格的前10条数据

#分组统计数量 
GET hrm/course/_search
{
  "aggs": {
    "price_terms": {
      "terms": {
        "field": "price",
        "size": 100
      },
      "aggs": {
        "tops": {
          "top_hits": {
            "size": 10
          }
        }
      }
    }
  }
}

在这里插入图片描述

8.百分比排名聚合

Percentile Ranks Aggregation,统计price小于500和年龄小于600的文档的占比

GET hrm/course/_search
{
  "aggs": {
    "price_percentiles": {
      "percentile_ranks": {
        "field": "price",
        "values": [
          500,
          600
        ]
      }
    }
  }
}

在这里插入图片描述

9.4.小结

DSL查询是ES提供的通用查询方式,这种方式最大的特点是开发语言的无关性,即任意的客户端只要支持HTTP请求,就可以通过JSON格式的查询数据完成复杂的搜索。
查询与过滤在实际的项目开发中是经常遇到的主题。

10.分词与映射

10.1.为什么要使用分词与映射

在全文检索理论中,文档的查询是通过关键字查询文档索引来进行匹配,因此将文本拆分为有意义的单词,对于搜索结果的准确性至关重要,因此,在建立索引的过程中和分析搜索语句的过程中都需要对文本串分词。
ES中分词需要对具体字段指定分词器等细节,因此需要在文档的映射中明确指出。

10.2.IK分词器

ES默认对英文文本的分词器支持较好,但和lucene一样,如果需要对中文进行全文检索,那么需要使用中文分词器,同lucene一样,在使用中文全文检索前,需要集成IK分词器。
ES的IK分词器插件源码地址:
https://github.com/medcl/elasticsearch-analysis-ik
① Maven打包IK插件
② 解压target/releases/elasticsearch-analysis-ik-5.2.2.zip文件
并将其内容放置于ES根目录/plugins/ik
在这里插入图片描述
③ 配置插件:(不需要)
插件配置:plugin-descriptor.properties

④ 分词器(可默认)
 词典配置:config/IKAnalyzer.cfg.xml
⑤ 重启ES
⑥ 测试分词器

POST _analyze
{
  "analyzer":"ik_smart",
  "text":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
}

注意:IK分词器有两种类型,分别是ik_smart分词器和ik_max_word分词器。
ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。
ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;

10.3.文档映射Mapper

ES的文档映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型。

1.ES字段类型

① 基本字段类型

字符串:text(分词),keyword(不分词)   StringField(不分词文本),TextFiled(要分词文本)
        text默认为全文文本,keyword默认为非全文文本
数字:long,integer,short,double,float
日期:date
逻辑:boolean
{user:{“key”:value}}
{hobbys:[xxx,xx]}

② 复杂数据类型

对象类型:object
数组类型:array
地理位置:geo_point,geo_shape
2.默认映射–掌握

查看索引类型的映射配置:GET {indexName}/_mapping/{typeName}
ES在没有配置Mapping的情况下新增文档,ES会尝试对字段类型进行猜测,并动态生成字段和类型的映射关系。

JSON typeField type
Boolean: true or false“boolean”
Whole number: 123“long”
Floating point: 123.45“double”
String, valid date:“2014-09-15”“date”
String: “foo bar”“string”
3.简单类型映射

字段映射的常用属性配置列表

type类型:基本数据类型,integer,long,date,boolean,keyword,text…
enable是否启用:默认为true。 false:不能索引、不能搜索过滤,仅在_source中存储
boost权重提升倍数:用于查询时加权计算最终的得分。
format格式:一般用于指定日期格式,如 yyyy-MM-dd HH:mm:ss.SSS
ignore_above长度限制:长度大于该值的字符串将不会被索引和存储。
ignore_malformed转换错误忽略:true代表当格式转换错误时,忽略该值,被忽略后不会被存储和索引。
include_in_all是否将该字段值组合到_all中。
null_value默认控制替换值。如空字符串替换为”NULL”,空数字替换为-1
store是否存储:默认为false。true意义不大,因为_source中已有数据
index索引模式:analyzed (索引并分词,text默认模式), not_analyzed (索引不分词,keyword默认模式),no(不索引)
analyzer索引分词器:索引创建时使用的分词器,如ik_smart,ik_max_word,standard
search_analyzer搜索分词器:搜索该字段的值时,传入的查询内容的分词器。
fields多字段索引:当对该字段需要使用多种索引模式时使用。如:城市搜索New York 下面字段city既可以分词有可以不分词"city": { “type”: “text”,“analyzer”: “ik_smart”,“fields”: {“raw”: { “type”: “keyword” } }} 有些类型 有时候需要分词 有时候不需要分词 city分词 city.raw不分词 那么以后搜索过滤和排序就可以使用city.raw字段名 match:{“city”:“北京”} match:{ “city.raw”:“北京”}

① 针对单个类型的映射配置方式–掌握
查询映射类型:
GET shop/goods/_mapping
修改映射类型

1)Delete shop;2PUT shop;3POST shop/goods/_mapping
{
"goods": {
	"properties": {
		"price": {
				"type": "integer"
		 },
		"name": {
				"type": "text",
				"analyzer": "ik_smart",
				"search_analyzer": "ik_smart"
            }
        }
    }
}

(4)加入数据

put shop/goods/1
{
  "price":88,
  "name": "iphone8"
}

注意:你可以在第一次创建索引的时候指定映射的类型。此外,你也可以晚些时候为新类型添加映射(或者为已有的类型更新映射)。

你可以向已有映射中增加字段,但你不能修改它。如果一个字段在映射中已经存在,这可能意味着那个字段的数据已经被索引。如果你改变了字段映射,那已经被索引的数据将错误并且不能被正确的搜索到。

我们可以更新一个映射来增加一个新字段,但是不能把已有字段的类型那个从 analyzed 改到 not_analyzed。
② 同时对多个类型的映射配置方式–了解

PUT {indexName}
{
  "mappings": {
    "user": {
      "properties": {
        "id": {
          "type": "integer"
        },
        "info": {
          "type": "text",
          "analyzer": "ik_smart",
          "search_analyzer"
        }
      }
    },
    "dept": {
      "properties": {
        "id": {
          "type": "integer"
        },
        ....更多字段映射配置
      }
    }
  }
}
4.对象及数组类型映射–了解

① 对象的映射与索引

{
	“id” : 1,
	“girl” : {
	    “name” : “王小花”,
	    “age”  : 22
	}
}

对应的mapping配置:

{ 
	"properties": {
	            "id": {"type": "long"},
	            "girl": {
					"properties":{
						"name": {"type": "keyword"},
						"age": {"type": "integer"}
				}
			}
     }
}

② 数组与对象数组
注意:数组中元素的类型必须一致。

{
	“id” : 1,
	“hobby” : [“王小花”,“林志玲”]
}
对应的mapping配置是:
{ 
	"properties": {
            "id": {"type": "long"},
            "hobby": {"type": "keyword"}
     }
}

对象数组的映射–用不上

{
	"id" : 1,
	"girl":[{"name":"林志玲","age":32},{"name":"赵丽颖","age":22}]
}

对应的映射配置为:

"properties": {
	"id": {
            "type": "long"
        },
        "girl": {
            "properties": {
              "age": { "type": "long" },
              "name": { "type": "text" }
            }
        }
}

注意:同内联对象一样,对象数组也会被扁平化索引

{
    "user1.girl.age":    [32, 22],
    "user2.girl.name":   ["林志玲", "赵丽颖"]
}
6.全局映射

全局映射可以通过动态模板和默认设置两种方式实现。
默认方式:default(了解)
索引下所有的类型映射配置会继承_default_的配置,如:

PUT {indexName}
{
  "mappings": {
    "_default_": { 
      "_all": {
        "enabled": false 关闭默认映射配置
      }
    },
	"user": {
		//指定自己的自定义配置
	}, 
	    "dept": { 
	      "_all": {
	        "enabled": true //启动默认配置
	      }
		},
	....
  }
}

上例中:默认的enabled=false 表示关闭模式的配置,如果你想用,在自己的配置里面开启配置
动态模板:dynamic_templates
注意:ES会默认把string类型的字段映射为text类型(默认使用标准分词器)和对应的keyword类型,如:

"name": {
     "type": "text",
     "fields": {
         "keyword": {
             "type": "keyword",
             "ignore_above": 256
          }
      }
}

在实际应用场景中,一个对象的属性中,需要全文检索的字段较少,大部分字符串不需要分词,因此,需要利用全局模板覆盖自带的默认模板:

PUT _template/global_template  //创建名为global_template的模板
{
  "template":   "*",  //匹配所有索引库
  "settings": { "number_of_shards": 1 }, //匹配到的索引库只创建1个主分片
  "mappings": {
    "_default_": {
      "_all": { 
        "enabled": false //关闭所有类型的_all字段
      },
      "dynamic_templates": [    username:“zs”
        {
          "string_as_text": {   
            "match_mapping_type": "string",//匹配类型string
            "match":   "*_text", //匹配字段名字以_text结尾 a_text
            "mapping": {
              "type": "text",//将类型为string的字段映射为text类型
              "analyzer": "ik_max_word",
              "search_analyzer": "ik_max_word",
              "fields": {
                "raw": {
                  "type":  "keyword",
                  "ignore_above": 256
                }
              }
            }
          }
        },
        {
          "string_as_keyword": { 
            "match_mapping_type": "string",//匹配类型string
            "mapping": {
              "type": "keyword"//将类型为string的字段映射为keyword类型
             }
          }
        }
      ]
    }
  }}

上面的意思:就是如果索引库里面字段 以_text结尾 就需要进行分词,如果不是,就不分词
测试:
(1)拷贝上面代码执行
(2)删除库 delete shop
(3)创建库 put shop
(4)加入数据测试

POST shop/goods/5
{
	“id”:12,
	“name_text”:”iphone  x”,
	“local“:”cnsssss”
}

说明:上例中定义了两种动态映射模板string_as_text和string_as_keyword.
在实际的类型字段映射时,会依次匹配:
①字段自定义配置
②全局配置
③默认配置

7.最佳实践

(1)有数据不做映射
(2)根据优先级来
步骤(1)如果有库 先删除库
(2)建库
(3)做全局映射
(4)做自定义映射
(5)操作crud

11.Java操作ES

11.1.集成ES
1.导入依赖

ES对Java提供一套操作索引库的工具包,即Java API。所有的ES操作都使用Client对象执行。
ES的Maven引入

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>transport</artifactId>
    <version>5.2.2</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.7</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.7</version>
</dependency>
2.连接ES获取Client对象

方式一: 把每台服务的ip 端口配上
TransportClient 利用transport模块远程连接一个ES集群。它并不加入到集群中,只是简单的获得一个或者多个初始化的transport地址,并以轮询的方式与这些地址进行通信。

// on startup
TransportClient client = new PreBuiltTransportClient(Settings.EMPTY)
        .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host1"), 9300))
        .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host2"), 9300));

// on shutdown
client.close();

方式二:通过集群名称来查找
注意,如果你有一个与 ES集群不同的集群,你可以设置机器的名字。

Settings settings = Settings.builder()
        .put("cluster.name", "myClusterName").build();
TransportClient client = new PreBuiltTransportClient(settings);
//添加地址到client中

方式三: 推荐使用方式
你可以设置client.transport.sniff为true来使客户端去嗅探整个集群的状态,把集群中其它机器的ip地址加到客户端中,这样做的好处是一般你不用手动设置集群里所有集群的ip到连接客户端,它会自动帮你添加,并且自动发现新加入集群的机器。代码实例如下:

Settings settings = Settings.builder()
        .put("client.transport.sniff", true).build();
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300))

在这里插入图片描述

11.2.crud实现
1.创建文档索引

ES索引文档非常方便,只需要构建好需要索引的JSON格式数据,然后调用API:

import static org.elasticsearch.common.xcontent.XContentFactory.*;
IndexResponse response = client.prepareIndex("crm", "vip", "1")
.setSource(jsonDataText).get();
或使用ES自动ID,不提供ID值1也OK。
返回的response对象可以获取到_index,_type,_version,_id等元数据。

在这里插入图片描述

2.获取文档

GetResponse response = client.prepareGet(“crm”, “vip”, “1”).get();
在这里插入图片描述

3.更新文档
client.prepareUpdate("crm", "vip", "1").setDoc("{\"sex\":0}").get();

或client.prepareUpdate("crm", "vip", "1")
.setScript(new Script("ctx._source.sex = 1"  , ScriptService.ScriptType.INLINE,null, null))
            .get();
java API也支持使用 upsert。如果文档还不存在,会根据 upsert内容创建一个新的索引。
--不用上面的代码 ,使用下面的代码

IndexRequest indexRequest = new IndexRequest("crm", "vip", "1")
.source(originalJsonData);
UpdateRequest updateRequest = new UpdateRequest("crm", "vip", "1")
.doc(updateJsonData).upsert(indexRequest);
client.update(updateRequest).get();

在这里插入图片描述

4.删除文档
DeleteResponse response = client.prepareDelete("crm", "vip", "1").get();
5.批量操作
BulkRequestBuilder bulkRequest = client.prepareBulk();
bulkRequest.add(client.prepareIndex("crm", "vip", "1")
.setSource(vip1JsonData));
bulkRequest.add(client.prepareIndex("crm", "vip", "2")
.setSource(vip2JsonData));
BulkResponse bulkResponse = bulkRequest.get();
if (bulkResponse.hasFailures()) {
//处理错误
}

在这里插入图片描述

6.搜索

ES的查询是通过执行json格式的查询条件,在java中就是构造QueryBuilder对象,ES完全支持queryDSL风格的查询方式,QueryBuilder的构建类是QueryBuilders,filter的构建类是FilterBuilders。
查询示例:

 @Test
    public void testDsl()throws Exception{

        TransportClient client = getClient();
        //prepareSearch
        SearchRequestBuilder builder = client.prepareSearch(“crm”)       //库
                .setTypes(“blog”);        //DSL过滤bool       表
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

           //must
        List<QueryBuilder> must = boolQuery.must(); //query bool must
        must.add(QueryBuilders.matchAllQuery()); //可以添加多个
        //filter
        boolQuery.filter(QueryBuilders.rangeQuery("age").gte(10).lte(50));//query bool filter
        //query
        builder.setQuery(boolQuery); //query bool
        //排序
        builder.addSort("age", SortOrder.ASC);
        //分页
        builder.setFrom(10).setSize(10);
        //截取字段 
        builder.setFetchSource(new String[]{"id","age"},null);
        //展示内容
        SearchResponse response = builder.get();

        //获取总数
        SearchHits hits = response.getHits(); //搜索出来东西都放到这个对象里面,里面包含总数+数据
        System.out.println("总数:"+hits.totalHits());
        //获取当前数据
        SearchHit[] searchHits = hits.getHits();
        for (SearchHit searchHit : searchHits) {
            System.out.println(searchHit.getSource());
        }
    }

12.SpringBoot操作ES

12.1.SpringBoot集成ES
1.导入依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
2.配置ES
...
spring:
  application:
    name: hrm-es-server
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 127.0.0.1:9300    #9200是图形界面端,9300代码端
      ...
3.创建ES的文档对象

以前做文档映射如下:
POST hrm;

Post /hrm/employee/_mapping
{
  “employee”:{
     “properties”:{
        “id”:{
           “type”:”long”
			},
			“userName”:{
			  “type”:”keyword”
			},
			“Intro”:{
			   “type”:”text”,
			   “analyzer ”: "ik_max_word"
			}
		}
	}
}
Pub /hrm/employee/1
{
    Id:1,
    userName:“zs”,
	Age: 18,
	Intro:“”
}

该文档对象用来如下几个事情
索引库的创建
文档的映射
存储到ES的数据封装

/**
 * 针对于 Employee 表的文档映射
 * indexName:索引库
 * type:类型(表类型)
 */
@Document(indexName = "hrm" , type = "employee")
public class EmployeeDoc {

//对应文档的id  PUT  /index/type/id
    @Id
    private Long id;

    @Field(type = FieldType.Keyword)    //指定为 不分词
    private String userName;

    private int age;

    @Field(type =FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
    private String intro;
    ...
4.初始化索引库和文档映射
@RunWith(SpringRunner.class)
@SpringBootTest(classes = EsServiceApplication2050.class)
public class ESTest {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Test
    public void testCreateIndex() {
        //创建索引
        elasticsearchTemplate.createIndex(EmployeeDoc.class);
        //做文档映射
        elasticsearchTemplate.putMapping(EmployeeDoc.class);
    }
}
5.定义Repository
@Repository
public interface EmployeeElasticsearchRepository  extends ElasticsearchRepository<EmployeeDoc,Long> {}
12.2.CRUD实现
1.添加数据
//文档的添加
@Test
public void testDocumentAdd(){
    for(long  i = 1 ; i < 30 ; i++){
        CourseDoc doc = new CourseDoc();
        doc.setId(i);
        if(i % 2 == 0){
            doc.setName("php小神班");
            doc.setPrice(1000.00F);
        }else{
            doc.setName("java小神班");
            doc.setPrice(1000.00F+i*100);
        }
        courseElasticsearchRepository.save(doc);
        System.out.println("保存成功....");
    }

}
2.修改数据
修改和添加一样,如果该id已经存在,就是修改
3.删除数据
//文档的删除
@Test
public void testDocumentDelete(){
    courseElasticsearchRepository.deleteById(1L);
    System.out.println("删除成功....");
    testDocumentGet();
}

4.获取数据
//文档的获取
@Test
public void testDocumentGet(){
    Optional<CourseDoc> optional = courseElasticsearchRepository.findById(1L);
    CourseDoc courseDoc = optional.get();
    System.out.println(courseDoc);
}

5.高级查询
//需求:查询课程名 name 中包含 java : DSL查询 - must - match
// 价格 price 在 1000 - 3000 : DSL过滤 - filter - range
// 每页 10 条,取第一页 ,按照价格倒排
@Test
public void testSearch(){

    //创建一个本机查询builder
    NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
    //=================================================
    //1.查询条件
    //组合查询
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

    //查询课程名 name 中包含 java : DSL查询 - must - match
    boolQueryBuilder.must(QueryBuilders.matchQuery("name","php"));

    //价格 price 在 1000 - 3000 : DSL过滤 - filter - range
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(1000).lte(3000));

    builder.withQuery(boolQueryBuilder);

    //2.排序 按照价格倒排
    builder.withSort(new FieldSortBuilder("price").order(SortOrder.DESC));
    //3.分页
    builder.withPageable(PageRequest.of(0,10));
    //=================================================
    //构建一个查询对象
    NativeSearchQuery searchQuery  = builder.build();

    //执行查询,得到结果
    Page<CourseDoc> page = courseElasticsearchRepository.search(searchQuery);
    //page  -> PageList

    System.out.println("总条数:"+page.getTotalElements());
    System.out.println("总页数:"+page.getTotalPages());

    //结果列表
    List<CourseDoc> content = page.getContent();

    content.forEach(courseDoc -> {
        System.out.println(courseDoc);
    });
}

13.高亮

13.1.创建结果处理器

在SpringBootDataES包中SearchResultMapper
默认没有对高亮进行结果处理,我们需要自己进行高亮转换,如下:

@Component
public class HighlightResultMapper implements SearchResultMapper {

    @Override
    public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
        // 记录总条数
        long totalHits = response.getHits().getTotalHits();
        // 记录列表(泛型) - 构建Aggregate使用
        List<T> list = Lists.newArrayList();
        // 获取搜索结果(真正的的记录)
        SearchHits hits = response.getHits();
        for (SearchHit hit : hits) {
            if(hits.getHits().length <= 0){
                return null;
            }
            // 将原本的JSON对象转换成Map对象
            Map<String, Object> map = hit.getSourceAsMap();
            // 获取高亮的字段Map
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            for (Map.Entry<String, HighlightField> highlightField : highlightFields.entrySet()) {
                // 获取高亮的Key
                String key = highlightField.getKey();
                // 获取高亮的Value
                HighlightField value = highlightField.getValue();
                // 实际fragments[0]就是高亮的结果,无需遍历拼接
                Text[] fragments = value.getFragments();
                StringBuilder sb = new StringBuilder();
                for (Text text : fragments) {
                    sb.append(text);
                }
                // 因为高亮的字段必然存在于Map中,就是key值
                // 可能有一种情况,就是高亮的字段是嵌套Map,也就是说在Map里面还有Map的这种情况,这里没有考虑
                map.put(key, sb.toString());
            }
            // 把Map转换成对象
            T item = JSON.parseObject(JSONObject.toJSONString(map),aClass);
            list.add(item);
        }
        // 返回的是带分页的结果
        return new AggregatedPageImpl<>(list, pageable, totalHits);
    }

}
13.2.高亮查询
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SearchApplication1080.class)
public class ESTest {

    @Autowired
    private ElasticsearchTemplate template;
     @Autowired
private HighlightResultMapper highlightResultMapper ;
   @Autowired
private CourseElasticsearchRepository repository;


@Test
    public void testSearchDoc(){
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        ...省略...
        //设置高亮显示===========================================
        HighlightBuilder.Field field = new HighlightBuilder.Field("name").preTags("<font style='color:red'>").postTags("</font>");
        builder.withHighlightFields(field);  // 名字高亮
        NativeSearchQuery build = builder.build();
        //执行查询 ,使用ElasticSearchTemplate,查询, highlightResultMapper指定结果处理映射器
        Page<CourseDoc> page = 
template.queryForPage(builder.build(),CourseDoc.class,highlightResultMapper);

        System.out.println("总条数:"+page.getTotalElements());
        System.out.println("总页数:"+page.getTotalPages());
        page.getContent().forEach(doc->{
            System.out.println(doc);
        });
    }
}

14.聚合查询

14.1.修改结果映射器

添加聚合结果

@Component
public class HighlightResultMapper implements SearchResultMapper {

    @Override
    public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
         ...省略...
        // 返回的是带分页的结果
        return new AggregatedPageImpl<>(list, pageable, totalHits,response.getAggregations());
    }
}
14.2.聚合查询
@Test
public void testSearchDoc(){
    NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
    // 每页 10 条,取第 1 页 ,
    builder.withPageable(PageRequest.of(0,10));
    // 按照价格 price 倒排
    builder.withSort(new FieldSortBuilder("price").order(SortOrder.DESC));
    //查询条件
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery() ;//new BoolQueryBuilder();
    //must  :    name 中包含 "java"   : match
    //boolQueryBuilder.must(QueryBuilders.termQuery("name","java"));
    //filter : 价格 price 在 1000 - 3000 :
    //boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(1000).lte(3000));
    builder.withQuery(boolQueryBuilder);

    //设置高亮显示
    HighlightBuilder.Field field = new HighlightBuilder.Field("name").preTags("<font style='color:red'>").postTags("</font>");
    builder.withHighlightFields(field);  // 名字高亮
    NativeSearchQuery build = builder.build();

    //聚合查询
    TermsAggregationBuilder tenantAgg = AggregationBuilders.terms("tenantAgg").field("tenantName")
            .order(Terms.Order.count(false)).size(50);  //按照count倒排

    builder.addAggregation(tenantAgg);

    //执行查询
    AggregatedPage<CourseDoc> page = template.queryForPage(builder.build(),CourseDoc.class,esResultMapper);

    System.out.println("总条数:"+page.getTotalElements());
    System.out.println("总页数:"+page.getTotalPages());
    page.getContent().forEach(doc->{
        System.out.println(doc);
    });
    //获取聚合结果
    StringTerms terms = (StringTerms) page.getAggregation("tenantAgg");
    List<StringTerms.Bucket> buckets = terms.getBuckets();
    //把机构名字聚合,搜集成字符串里集合
    List<String> tenantNames = buckets.stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
    System.out.println(tenantNames);
}

15.总结

1.重点

索引创建原理(分词)
Lucene的入门
ES文档的CRUD
ES查询与过滤
ES分词与映射
Java操作ES

2.作业

全天代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值