1.ES概述:
a.为什么需要ES:
因为如果我们基于数据库做搜索,那么Like的效率非常低,而且各种条件排序及其不方便,所以我们需要使用专业的搜索工具ES来完成搜索
b.什么是全文检索引擎:
就是把没有结构的数据通过检索工具转换为有结构的数据然后存储起来,在查找数据的时候根据结构的数据进行查找
2.Lucene原理
-
索引:索引提供指向数据值的指针,然后根据您指定的排序顺序对这些指针排序,我们可以根据索引快速定位到想要查找的数据
-
ElasticSearch基于Lucene实现了高效的全文检索功能、那么Lucene又是如何实现高效搜索的?核心就是以下两步
-
索引创建:使用一定的规则,将文本进行分词,创建一个倒排索引文档,倒排索引文档做搜索的速度非常快,这也是为什么Lucene全文检索快的原因
-
索引搜索:把要搜索的关键字去匹配倒排索引文档,拿到关键字对应的原始数据并进行排序,匹配度高的在前面
-
-
Lucene的数据存储分为两个部分
-
索引区:存储倒排索引文档
-
数据区:存储原始数据
-
a.索引创建
1.给原始数据加索引
2.将原始数据进行分词,分词之后的数据对应了之前的索引
3.将所有词进行小写以及词态转换
4.对所有的此进行字母顺序排序
5.将所有的词进行去重并且合并索引,最终形成倒排索引
b.索引搜索
1.将要搜索的数据进行分词,如果你要分的话
2.根据分词后的数据去倒排索引中找到对应的索引值
3.根据索引值匹配原始数据
4.计算出匹配的值的相关度进行排序响应
c.ES要存储的数据
1.倒排索引库
2.原始数据库
d.ES的数据来源
1.在新增数据的时候,我们除了要落库到MySql还要添加到ES,你要添加到ES的时候就把倒排索引创建好了
3.索引&文档操作
a.创建索引
-
ES在创建索引时,需要指定number_of_shards和number_of_replicas,因为默认ES就是基于集群的,所以创建索引时就需要指定此索引的集群策略
-
number_of_shards:分片数,意思是将数据分布在几个集群节点中
-
number_of_replicas:副本,也就是同一个分片有多少个备份,对于查询压力较大的Index,可以考虑提高副本数,通过多个副本均摊查询压力
-
PUT shopping
{
"settings":{
"number_of_shards":5,
"number_of_replicas":1
}
}
b.查询索引
-
查询所有索引库:GET _cat/indices?v
-
查询指定索引库:GET _cat/indices/aigou
c.删除索引
-
删除索引:DELETE 索引名称
d.修改索引
-
删除索引再添加索引,就是修改索引
e.文档基本操作
method | url地址 | 描述 |
---|---|---|
PUT | localhost:9200/索引名称/类型名称/文档Id | 创建文档-指定文档Id |
POST | localhost:9200/索引名称/类型名称 | 创建文档-随机文档Id |
POST | localhost:9200/索引名称/类型名称/文档Id/_update | 修改文档 |
DELETE | localhost:9200/索引名称/类型名称/文档Id | 删除文档 |
GET | localhost:9200/索引名称/类型名称/文档Id | 通过文档Id查询文档 |
POST | localhost:9200/索引名称/类型名称/_search | 查询所有数据 |
1.创建文档-指定文档Id
# 创建文档,指定文档Id
PUT /pethome/pet/2
{
"id":2,
"name":"金毛",
"sex":1
}
2.创建文档-随机文档Id
# 创建文档,随机文档Id
POST /pethome/pet
{
"id":3,
"name":"吉娃娃",
"sex":0
}
3.通过指定Id查询文档
# 通过指定Id查询文档
GET /pethome/pet/2
4.查询所有数据
# 查询所有数据
GET /pethome/pet/_search
5.修改文档
-
全部修改,原有数据会消失,以修改的为准,就是删除再新增
# 全部修改
PUT /pethome/pet/2
{
"id":2,
"name":"金毛-01"
}
-
局部修改,只会修改填写的字段值
# 局部修改
POST /pethome/pet/2/_update
{
"doc": {
"name":"金毛-02"
}
}
6.删除文档
# 删除文档
DELETE /pethome
7、QueryStringSearch语法
-
query string search:称为查询字符串语法,其实就是在基础语法后面拼接条件查询,我们在进行ES搜索时,除了基本的查询肯定要带条件查询、排序、分页等需求,这时候我们就可以使用查询字符串,查询字符串就是拼接条件进行查询
-
查询字符串/query string其实就是在url后面以字符串的方式拼接各种查询条件,这种方式不推荐,因为条件过多,拼接起来比较麻烦
-
-
例如分页查询:&size=2&from=2
-
size:每页展示条数
-
from:分页数据起始位置,从0开始计算
-
-
条件查询+分页+排序
GET /pethome/pet/_search?q=age:17&size=2&from=2&sort=id:desc&_source=id,username
4.DSL
两种查询语句
1.DSL查询:就是去看关键字在原始数据中匹不匹配,有可能匹配度非常低也会查询出来,使用DSL查询会计算结果的相关度评分,所以效率不如DSL过滤高
2.DSL过滤:就是去看关键字对应数据等不等与,没有第三种可能性,等于就查询出来,不等于就不要,DSL过滤不会计算相关度评分,并且使用DSL过滤查询出来的数据ES会做缓存,下次再查询直接从缓存中获取,所以使用DSL过滤效率对比DSL查询高
DSL常用关键字
关键字 | 含义 | 在SQL中 |
---|---|---|
query | 查询 | select |
bool | 多个组合条件 | selext xxx from xxx where age=20 and gender=male |
filter | DSL过滤 | = |
term | 全值匹配,不会分词 | |
match | 分词后进行匹配 | |
must | DSL查询 | and |
from | 从哪一条开始取,以数值的下一条数据开始 | currentPage |
size | 每页展示条数 | pageSize |
_source | 只选择某些字段 | select 字段 |
must_not | 不包含 | ! |
range | 匹配某个范围 | between and |
示例代码:
GET /pethome/pet/_search where sex = 1 and username like "%zs%"
{
"query": { // 编写DSL查询条件关键字
"bool": { // 开启组合查询
"must": [{ // 必须匹配
"match": { // 分词后再进行查询
"username": "zs"
},
}],
"filter": { // DSL过滤
"term": { // 等值查询
"sex": 1
}
}
}
},
"from": 20,
"size": 10,
"_source": ["name", "age", "username"],
"sort": [{
"age": "desc"
}, {
"id": "asc"
}]
}
5.IK分词器
a.为什么需要IK分词器:因为ES默认不支持中文分词,所以我们需要自己使用中文分词器的插件,在国内比较好的插件有IK
b.安装IK分词器
-
需要使用与ES对应版本
-
将IK分词器包解压到ES插件包下,改名为IK即可
6.文档类型映射
a.什么是文档类型映射:就是字段的类型
b.需要特别注意的字段
1.text:支持分词
2.keyword:不支持分词
c.默认ES在第一次添加索引库数据库就会给我们字段设置默认映射类型,在实际开发中我们肯定是先创建索引库再创建字段映射类型,在代码中添加数据到ES
字段类型
-
基本字段类型
类型 | 类型1 | 类型2 | 类型3 | 类型4 | |
---|---|---|---|---|---|
字符串 | text-分词 | keyword-不分词 | StringField-不分词文本 | TextFiled分词文本 | |
数字 | long | integer | short | double | float |
日期 | date | ||||
逻辑 | boolean |
-
复杂字段类型
对象类型 | object |
---|---|
数组类型 | array |
地理位置 | geo_point,geo_shape |
-
默认映射:ES在没有配置Mapping的情况下新增文档,ES会尝试对字段类型进行猜测,并动态生成字段和类型的映射关系
内容 | 默认映射类型 |
---|---|
JSON type | Field 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" | "text" |
映射规则
-
字段映射的常用属性配置列表,即给某个字段执行类的时候可以指定以下属性
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== | 多字段索引:当对该字段需要使用多种索引模式时使用 |
7.Java整合ElasticSearch(SpringBoot )
-
第一步:导入Jar包
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>6.8.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
-
第二步:添加Java连接ES工具类
public class ESClientUtil {
public static TransportClient getClient(){
TransportClient client = null;
Settings settings = Settings.builder()
.put("cluster.name", "elasticsearch").build();
try {
client = new PreBuiltTransportClient(settings)
.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
} catch (UnknownHostException e) {
e.printStackTrace();
}
return client;
}
}
-
第三步:代码测试
添加文档
@Test
public void testAdd() throws Exception{
// 1.获取ES连接
TransportClient client = ESClientUtil.getClient();
// 2.准备要放入到ES中的数据
HashMap<String, Object> map = new HashMap<>();
map.put("id", "1");
map.put("name", "我在学习Java!");
map.put("age", "8");
map.put("sex", "1");
// 3.使用链式编程,将数据存入到ES中
IndexRequestBuilder builder = client.prepareIndex("pethome", "pet", "1").setSource(map);
// 4.得到添加文档结果
System.out.println(builder.get());
// 5.关闭连接
client.close();
}
查询文档
@Test
public void testGet() throws Exception {
TransportClient client = ESClientUtil.getClient();
GetRequestBuilder getRequestBuilder = client.prepareGet("pethome", "pet", "1");
GetResponse documentFields = getRequestBuilder.get();
// 4.得到添加文档结果
System.out.println(documentFields.getSource());
// 5.关闭连接
client.close();
}
修改文档
@Test
public void testUpdate() throws Exception {
TransportClient client = ESClientUtil.getClient();
UpdateRequestBuilder updateRequestBuilder = client.prepareUpdate("pethome", "pet", "1");
// 2.准备要放入到ES中的数据
HashMap<String, Object> map = new HashMap<>();
map.put("name", "川川使用猪突猛进");
updateRequestBuilder.setDoc(map);
UpdateResponse updateResponse = updateRequestBuilder.get();
// 4.得到添加文档结果
System.out.println(updateResponse);
// 5.关闭连接
client.close();
}
删除文档
@Test
public void testDel() throws Exception {
// 1.获取ES连接
TransportClient client = ESClientUtil.getClient();
// 3.使用链式编程,删除指定文档
DeleteResponse response = client.prepareDelete("pethome", "pet", "1").get();
// 4.得到删除结果
System.out.println(response);
// 5.关闭连接
client.close();
}
复杂查询
@Test
public void testComplex() throws Exception {
// 1.获取ES连接
TransportClient client = ESClientUtil.getClient();
// 查询条件:- 查询用户表,name包含:我在源码,age在1~12之间,每页大小2,从二页开始查,按照age倒序
// 2.得到搜索对象
SearchRequestBuilder builder = client.prepareSearch("pethome");
// 3.指定要搜索的类型
builder.setTypes("pet");
// 4.指定Query搜索对象,当参数是接口时我们可以传递:接口的工具类、接口实现、匿名内部类,此处传递接口工具类
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 4.1.指定DSL查询,name包含我在源码
boolQuery.must(QueryBuilders.matchQuery("name", "雷川川"));
// 4.2.指定DSL过滤,age在1~12之间
boolQuery.filter(QueryBuilders.rangeQuery("age").gte(1).lte(12)).filter(QueryBuilders.termQuery("sex", 1));
// 4.3.将搜索条件放入到搜索对象中
builder.setQuery(boolQuery);
// 5.设置分页起始位置
builder.setFrom(2);
// 6.设置分页每页展示条数
builder.setSize(2);
// 7.设置排序,age倒序
builder.addSort("age", SortOrder.DESC);
// 8.得到结果进行打印
SearchHits hits = builder.get().getHits();
System.out.println("命中结果:" + hits.getTotalHits());
for (SearchHit document : hits) {
System.out.println(document.getSourceAsMap());
}
}
8.ES实际开发的添加流程
a.创建索引库
b.按照字段类型创建文档类型映射
c.把数据添加到ES中
d.编写搜索条件进行搜索