1.核心概念

索引(Index)

含有相同属性的文档集合

文档(Document)

文档可以被索引的基本单位, 一般是json格式,包含一些field

列(Field)

es的最小单位,相当于数据的某个列

类比

关系型数据库(如Mysql) 非关系型数据库(Elasticsearch)
数据库 Database 索引 Index
数据行 Row 文档 Type
数据列 Column 字段 Field

2.常规操作

索引操作

添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
语法:PUT /索引名
在没有特殊设置的情况下,默认有1个分片,1个备份,也可以通过请求参数的方式来指定

默认:
PUT my_index

明确指定:
PUT /my_index
{
"settings": {
"number_of_shards": 5, # 设置5个片区
"number_of_replicas": 1 # 设置1个备份
}
}

注意:
1: 索引不能有大写字母
2: 参数格式必须是标准的json格式

查看

1
2
3
4
5
# 看单个
GET /索引名

# 看所有
GET _cat/indices

删除

语法:DELETE /索引名

设置映射(列)

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
类型跟映射一起创建

语法:
PUT /索引名
{
"mappings": {
"properties": {
字段名: {
"type": 字段类型,
"analyzer": 分词器类型,
"search_analyzer": 分词器类型,
...
},
...
}
}
}

字段类型就是:数据类型
配置:analyzer search_analyzer 前提是 type:text 类型
-------------------------------------------------------
创建类型并设置映射(类似于mysql创建表)

eg:
PUT /my_index
{
"mappings": {
"properties":{
"id":{
"type":"long"
},
"name":{
"type":"keyword"
},
"age":{
"type":"integer"
}
}
}
}

查看

1
2
3
4
语法:GET /索引名/_mapping

需求: 查看映射
GET /my_index/_mapping

3.文档操作

数据类型

核心类型:

text 类型:当一个字段是要被全文搜索的,比如Email内容、产品描述,应该使用text类型。设置text类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分析器分成一个一个词项。text类型的字段不用于排序,很少用于聚合。

keyword类型:适用于索引结构化的字段,比如email地址、主机名、状态码和标签。如果字段需要进行过滤(比如查找已发布博客中status属性为published的文章)、排序、聚合。keyword类型的字段只能通过精确值搜索到。

文档基本操作

文档添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
语法:
# 必须明确指定id-推荐
PUT /索引名/_doc/文档ID
{
field1: value1,
field2: value2,
...
}

# 不指定指定id默认使用随机字符串
POST /索引名/_doc
{
field1: value1,
field2: value2,
...
}

注意:
1:当索引/映射不存在时, 会使用默认设置自动添加
2:ES中的数据一般是从别的数据库导入的, 所以文档的ID会沿用原数据库中的ID
3:操作时, 如果指定文档id, 并且索引库中已经存在, 则执行更新操作, 否则执行添加
4:不指定id的添加, es会指定添加一个字符串类型id


结果字段解释:
_index:所属索引
_type:所属类型
_id:文档ID
_version:乐观锁版本号
_source:数据内容
result : 命令操作类型
_shards: 分片相关信息

需求1:新增一个文档
PUT /my_index/_doc/1
{
"id":1,
"name":"xiaowen",
"age":21
}

文档更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
语法:
PUT /索引名/_doc/文档ID
{
field1: value1,
field2: value2,
...
}

注意: 如果不指定id, 操作失败

需求2: 替换一个文档
PUT /my_index/_doc/1
{
"id":1,
"name":"tangtang",
"age":18
}

-----------------------
{
"_index" : "es_index",
"_type" : "users",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 3,
"_primary_term" : 1
}

----------------
部分更新字段
POST /my_index/_update/1/
{
"doc":{
"name":"mimimi"
}
}

文档查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
语法:
根据ID查询 -> GET /索引名/_doc/文档ID
查询所有(基本查询语句) -> GET /索引名/_doc/_search

结果字段解释:

took:耗时
_shards.total:分片总数
hits.total:查询到的数量
hits.max_score:最大匹配度
hits.hits:查询到的结果
hits.hits._score:匹配度


需求1:根据文档ID查询一个文档
GET /my_index/_doc/1
-------
{
"_index" : "es_index",
"_type" : "users",
"_id" : "1",
"_version" : 4,
"_seq_no" : 3,
"_primary_term" : 1,
"found" : true,
"_source" : {
"id" : 1,
"name" : "xiaowen",
"age" : 18
}
}


需求2:查询所有的文档
GET /my_index/_search # 默认查询10

文档删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
语法:DELETE /索引名/类型名/文档ID
注意:这里的删除并且不是真正意义上的删除,仅仅是清空文档内容而已,并且标记该文档的状态为删除

需求1:根据文档ID删除一个文档

DELETE /my_index/_doc/1

需求2:又添加刚刚删除的文档
PUT /my_index/_doc/1
{
"id":1,
"name":"xioawen",
"age":18
}

注意: es中的删除并不会将数据真实删除,仅仅是修改数据中状态

全文搜索

数据准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
PUT /product
{
"mappings": {
"properties": {
"id": {
"type": "long"
},
"title": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"price": {"type": "integer"},
"intro": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"brand": {
"type": "keyword"
}
}
}
}

POST /product/_bulk
{"create":{"_id": 1}}
{"id":1,"title":"Apple iPhone XR (A2108) 128GB 白色 移动联通电信4G手机 双卡双待","price":5299,"intro":"【iPhoneXR限时特惠!】6.1英寸视网膜显示屏,A12仿生芯片,面容识别,无线充电,支持双卡!选【换修无忧版】获 AppleCare 原厂服务,享只换不修!更有快速换机、保值换新、轻松月付!","brand":"Apple"}
{"create":{"_id": 2}}
{"id":2,"title":"Apple 2019款 Macbook Pro 13.3【带触控栏】八代i7 18G 256G RP645显卡 深空灰 苹果笔记本电脑 轻薄本 MUHN2CH/A","price":15299,"intro":"【八月精选】Pro2019年新品上市送三重好礼,现在购买领满8000减400元优惠神劵,劵后更优惠!","brand":"Apple"}
{"create":{"_id": 3}}
{"id":3,"title":"Apple iPad Air 3 2019年新款平板电脑 10.5英寸(64G WLAN版/A12芯片/Retina显示屏/MUUL2CH/A)金色","price":3788,"intro":"8月尊享好礼!买iPad即送蓝牙耳机!领券立减!多款产品支持手写笔!【新一代iPad,总有一款适合你】选【换修无忧版】获 AppleCare 原厂服务,享只换不修!更有快速换机、保值换新、轻松月付!","brand":"Apple"}
{"create":{"_id": 4}}
{"id":4,"title":"华为HUAWEI MateBook X Pro 2019款 英特尔酷睿i5 13.9英寸全面屏轻薄笔记本电脑(i5 8G 512G 3K 触控) 灰","price":7999,"intro":"3K全面屏开启无界视野;轻薄设计灵动有型,HuaweiShare一碰传","brand":"华为"}
{"create":{"_id": 5}}
{"id":5,"title":"华为 HUAWEI Mate20 X (5G) 7nm工艺5G旗舰芯片全面屏超大广角徕卡三摄8GB+256GB翡冷翠5G双模全网通手机","price":6199,"intro":"【5G双模,支持SA/NSA网络,7.2英寸全景巨屏,石墨烯液冷散热】5G先驱,极速体验。","brand":"华为"}
{"create":{"_id": 6}}
{"id":6,"title":"华为平板 M6 10.8英寸麒麟980影音娱乐平板电脑4GB+64GB WiFi(香槟金)","price":2299,"intro":"【华为暑期购】8月2日-4日,M5青春版指定爆款型号优惠100元,AI语音控制","brand":"华为"}
{"create":{"_id": 7}}
{"id":7,"title":"荣耀20 PRO DXOMARK全球第二高分 4800万四摄 双光学防抖 麒麟980 全网通4G 8GB+128GB 蓝水翡翠 拍照手机","price":3199,"intro":"白条6期免息!麒麟980,4800万全焦段AI四摄!荣耀20系列2699起,4800万超广角AI四摄!","brand":"荣耀"}
{"create":{"_id": 8}}
{"id":8,"title":"荣耀MagicBook Pro 16.1英寸全面屏轻薄性能笔记本电脑(酷睿i7 8G 512G MX250 IPS FHD 指纹解锁)冰河银","price":6199,"intro":"16.1英寸无界全面屏金属轻薄本,100%sRGB色域,全高清IPS防眩光护眼屏,14小时长续航,指纹一健开机登录,魔法一碰传高速传输。","brand":"荣耀"}
{"create":{"_id": 9}}
{"id":9,"title":"荣耀平板5 麒麟8核芯片 GT游戏加速 4G+128G 10.1英寸全高清屏影音平板电脑 WiFi版 冰川蓝","price":1549,"intro":"【爆款平板推荐】哈曼卡顿专业调音,10.1英寸全高清大屏,双喇叭立体环绕音,配置多重护眼,值得拥有!","brand":"荣耀"}
{"create":{"_id": 10}}
{"id":10,"title":"小米9 4800万超广角三摄 6GB+128GB全息幻彩蓝 骁龙855 全网通4G 双卡双待 水滴全面屏拍照智能游戏手机","price":2799,"intro":"限时优惠200,成交价2799!索尼4800万广角微距三摄,屏下指纹解锁!","brand":"小米"}
{"create":{"_id": 11}}
{"id":11,"title":"小米(MI)Pro 2019款 15.6英寸金属轻薄笔记本(第八代英特尔酷睿i7-8550U 16G 512GSSD MX250 2G独显) 深空灰","price":6899,"intro":"【PCIE固态硬盘、72%NTSC高色域全高清屏】B面康宁玻璃覆盖、16G双通道大内存、第八代酷睿I7处理器、专业级调校MX150","brand":"小米"}
{"create":{"_id": 12}}
{"id":12,"title":"联想(Lenovo)拯救者Y7000P 2019英特尔酷睿i7 15.6英寸游戏笔记本电脑(i7 9750H 16G 1T SSD GTX1660Ti 144Hz)","price":9299,"intro":"超大1T固态,升级双通道16G内存一步到位,GTX1660Ti电竞级独显,英特尔9代i7H高性能处理器,144Hz电竞屏窄边框!","brand":"联想"}

搜索语句

  • match
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
表示全文检索,也可也做精确查询,value值会被分词器拆分,然后去倒排索引中匹配
参数格式:

GET /索引/_search
{
"query": {
"match": {field: value}
}
}

需求:查询商品标题中符合"游戏 手机"的字样的商品 【暂时理解为模糊查询】
GET /product/_search
{
"query": {
"match":{
"title": "游戏 手机"
}
}
}
  • multi_match
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
参数格式:
GET /索引/_search
{
"query": {
"multi_match": {
"query": value,
"fields": [field1, field2, ...]
}
}
}

multi_match:表示在多个字段间做检索,只要其中一个字段满足条件就能查询出来,多用在字段上

需求:查询商品标题或简介中符合"蓝牙 指纹 双卡"的字样的商品
GET /product/_search
{
"query": {
"multi_match":{
"query":"蓝牙 指纹 双卡",
"fields": ["title", "intro"]
}
}
}

中文分词器

把文本内容按照标准进行切分,默认的是standard,该分词器按照单词切分,内容转变为小写,去掉标点,遇到每个中文字符都当成1个单词处理

开源的中文分词器插件(ik)

默认的分词器只能对英文正常分词,不能对中文正常分词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
IK分词器:
1、ik_max_word 【细粒度分词】
会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。

2、ik_smart 【粗粒度分词】
会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。


GET /product/_analyze
{
"text":"I am Groot",
"analyzer":"ik_smart"
}

GET /product/_analyze
{
"text":"英特尔酷睿i7处理器",
"analyzer":"ik_smart"
}

GET /product/_analyze
{
"text":"英特尔酷睿i7处理器",
"analyzer":"ik_max_word"
}

倒排索引

倒排索引es

高亮显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
参数格式:

GET /索引/_search
{
"query": { ... },
"highlight": {
"fields": {
field1: {},
field2: {},
...
},
"pre_tags": 开始标签,
"post_tags" 结束标签
}
}

highlight:表示高亮显示,需要在fields中配置哪些字段中检索到该内容需要高亮显示必须配合检索(term / match)一起使用

需求:查询商品标题或简介中符合"蓝牙 指纹 双卡"的字样的商品,并且高亮显示
GET /product/_search
{
"query": {
"multi_match":{
"query":"蓝牙 指纹 双卡",
"fields": ["title", "intro"]
}
},
"highlight": {
"fields": {
"title": {"type": "plain"},
"intro": {"type": "plain"}
},
"pre_tags": "<span style='color:red;'>",
"post_tags": "</span>"
}
}

4.springboot集成

依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

配置

1
2
3
4
spring:
elasticsearch:
rest:
uris: http://localhost:9200

domain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Getter
@Setter
@Document(indexName="es_shop", type="shop_product")
public class Product {
@Id
private String id;

@Field(analyzer="ik_smart",searchAnalyzer="ik_smart",type = FieldType.Text)
private String title;

private Integer price;

@Field(analyzer="ik_smart",searchAnalyzer="ik_smart",type = FieldType.Text)
private String intro;

@Field(type=FieldType.Keyword)
private String brand;
}

CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public interface ProductRepository  extends ElasticsearchRepository<Product, String>{
}

---------------------------------------------------

public interface IProductService {
void save(Product product);
void update(Product product);
void delete(String id);
Product get(String id);
List<Product> list();
}

---------------------------------------------------

@Service
public class ProductServiceImpl implements IProductService {
@Autowired
private ProductRepository repository;
@Autowired
private ElasticsearchTemplate template;
@Override
public void save(Product product) {
repository.save(product);
}
@Override
public void update(Product product) {
repository.save(product);
}
@Override
public void delete(String id) {
repository.deleteById(id);
}
@Override
public Product get(String id) {
return repository.findById(id).get();
}
@Override
public List<Product> list() {
Iterable<Product> all = repository.findAll();
List<Product> p = new ArrayList<>();
all.forEach(a->p.add(a));
return p;
}
}

---------------------------------------------------

@SpringBootTest
public class ElasticsearchDemoApplicationTests {
@Autowired
private IProductService productService;
@Test
public void testSave() {
Product p = new Product();
p.setId("123")
p.setBrand("dafei");
p.setIntro("dafei手机");
p.setPrice(1000);
p.setTitle("全球最帅的手机");
productService.save(p);
}
@Test
public void testUpate() {
Product p = new Product();
p.setId("ue5r1m4BXlaPW5P0TegF");
p.setBrand("dafei");
p.setIntro("dafei手机");
p.setPrice(1000);
p.setTitle("全球最sb的手机");

productService.update(p);
}
@Test
public void testDelete() {
productService.delete("123");
}
@Test
public void testGet() {
System.out.println(productService.get("123"));
}
@Test
public void testList() {
System.err.println(productService.list());
}
}

各类查询

  • match
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 查询商品标题中符合"游戏 手机"的字样的商品
@Test
public void testQuery4(){
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
/**
* {
* query:{
* match:{title:"游戏 手机"}
* }
* }
*/
builder.withQuery(
QueryBuilders.matchQuery("title", "游戏 手机")
);
builder.withPageable(PageRequest.of(0, 100));
Page<Product> search = repository.search(builder.build());
search.getContent().forEach(System.err::println);

}
  • multi_match
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 查询商品标题或简介中符合"蓝牙 指纹 双卡"的字样的商品
@Test
public void testQuery7(){
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();

/**
* {
* query:{
* multi_match:{
* "query":"蓝牙 指纹 双卡",
* "fields":["title", "intro"]
* }
* }
* }
*/
builder.withQuery(
QueryBuilders.multiMatchQuery("蓝牙 指纹 双卡", "title", "intro")
);
builder.withPageable(PageRequest.of(0, 100, Sort.Direction.DESC, "price"));
Page<Product> search = repository.search(builder.build());
search.getContent().forEach(System.err::println);
}

高亮显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Test
public void testHighlight() throws Exception {
//定义索引库
SearchRequest searchRequest = new SearchRequest("product");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

//定义query查询
MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery("蓝牙 指纹 双卡","title", "intro");

HighlightBuilder highlightBuilder = new HighlightBuilder(); // 生成高亮查询器
highlightBuilder.field("title"); // 高亮查询字段
highlightBuilder.field("intro"); // 高亮查询字段
highlightBuilder.requireFieldMatch(false); // 如果要多个字段高亮,这项要为false
highlightBuilder.preTags("<span style='color:red'>"); // 高亮设置
highlightBuilder.postTags("</span>");
highlightBuilder.fragmentSize(800000); // 最大高亮分片数
highlightBuilder.numOfFragments(0); // 从第一个分片获取高亮片段

Pageable pageable = PageRequest.of(1, 2); // 设置分页参数
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryBuilder) // match查询
.withPageable(pageable).withHighlightBuilder(highlightBuilder) // 设置高亮
.build();
SearchHits<Product> searchHits = template.search(searchQuery, Product.class);

List<Product> list = new ArrayList();
for (SearchHit<Product> searchHit : searchHits) { // 获取搜索到的数据
Product content = searchHit.getContent();
// 处理高亮
Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
for (Map.Entry<String, List<String>> stringHighlightFieldEntry : highlightFields.entrySet()) {
String key = stringHighlightFieldEntry.getKey();
if (StringUtils.equals(key, "title")) {
List<String> fragments = stringHighlightFieldEntry.getValue();
StringBuilder sb = new StringBuilder();
for (String fragment : fragments) {
sb.append(fragment.toString());
}
content.setTitle(sb.toString());
}
if (StringUtils.equals(key, "intro")) {
List<String> fragments = stringHighlightFieldEntry.getValue();
StringBuilder sb = new StringBuilder();
for (String fragment : fragments) {
sb.append(fragment.toString());
}
content.setIntro(sb.toString());
}
}
list.add(content);
}

Page page = new PageImpl(list, pageable, searchHits.getTotalHits());
list.forEach(System.out::println);
}