Elasticsearch 概念
入门学习:
- Index索引=>MySQL 里的表(table)
- 建表、增删改查(查询需要花费的学习时间最多)
- 用客户端去调用 ElasticSearch(3 种)
- 语法:SQL、代码的方法(4 种语法)
ES 相比于 MySQL,能够自动帮我们做分词,能够非常高效、灵活地查询内容。
索引(倒排索引)
正向索引:理解为书籍的目录,可以快速帮你找到对应的内容(怎么根据页码找到文章)
倒排索引:怎么根据内容找到文章
文章 A: 你好,我是 rapper
文章 B: 苏麟暴打小杨科
切词 :
|--------|------|
| 词 | id |
| 你好 | 文章 A |
| 我 | 文章 A |
| rapper | 文章 A |
| 苏麟 | 文章 B |
| 暴打 | 文章 B |
| 杨科 | 文章 B |
用户搜索 : 苏麟杨科
ES切词 : 苏麟 , 杨科
然后去倒排索引表找对应的文章
ES 的几种调用方式
1) restful api 调用(http 请求)
GET请求 : localhost:9200
curl 可以模拟发送请求: curl -XGET"localhost:9200/?pretty
ES 的启动端口
1.9200:给外部用户(给客户端调用)的端口
2.9300:给 ES 集群内部通信的(外部调用不了的)
2) kibana devtools
自由地对 ES 进行操作(本质也是 restful api)
devtools 不建议生产环境使用
3)客户端调用
java 客户端、go 客户端等。
参考文档 : Getting started | Elasticsearch Java API Client [7.17] | Elastic
ES 的语法
DSL
json 格式,好理解;和 http 请求最兼容,应用最广
建表,插入数据 (文档就是MySQL里的表,映射就是MySQL里的字段)
post 就相当于表名 , title 和 content 就相当于字段
java
POST post/_doc
{
"title":"苏麟",
"content":"苏麟暴打小杨科"
}
有 successful就代表存入成功了
查询
DSL (不要背) : Query DSL | Elasticsearch Guide [7.17] | Elastic
查询全部
java
GET post/_search
{
"query": {
"match_all": {}
}
}
根据 id 查询
java
GET post/_doc/UBHryo0B5ErW0HhSBc0q
修改
根据 id 修改
java
POST post/_doc/UBHryo0B5ErW0HhSBc0q
{
"title":"杨科",
"content":"杨科委委屈屈!"
}
删除
根据文档删除
java
DELETE post
EQL
专门查询 ECS 文档(标准指标文档)的数据的语法,更加规范,但只适用于特定场景(比如事件流)
文档 : EQL search | Elasticsearch Guide [7.17] | Elastic
建表,插入
java
POST post_my/_doc
{
"title": "苏麟爱看美女",
"@timestamp": "2099-05-06T16:21:15.000Z",
"event": {
"original": "192.0.2.42 - - [06/May/2099:16:21:15 +0000] \"GET /images/bg.jpg HTTP/1.0\" 200 24736"
}
}
查询
java
GET post_my/_search
{
"query": {
"match_all": { }
},
"sort": [
{
"@timestamp": "desc"
}
]
}
SQL
文档 : Getting Started with SQL | Elasticsearch Guide [7.17] | Elastic
查询
java
POST /_sql?format=txt
{
"query": "SELECT * FROM post "
}
Painless Scripting language
编程式取值,更灵活,但是学习成本高
Mapping
文档 : Explicit mapping | Elasticsearch Guide [7.17] | Elastic
可以理解为数据库的表结构,有哪些字段、字段类型,
ES 支持动态 mapping,表结构可以动态改变,而不像 MySQL 一样必须手动建表,没有的字段就不能插入。
java
GET user/_mapping
PUT /user
{
"mappings": {
"properties": {
"age": { "type": "integer" },
"email": { "type": "keyword" },
"name": { "type": "text" }
}
}
}
分词器
指定了分词的规则。
内置分词器 : Built-in analyzer reference | Elasticsearch Guide [7.17] | Elastic
示例 :
空格分词器: whitespace,结果The、quick、brown、fox.
java
POST _analyze
{
"analyzer": "whitespace",
"text": "The quick brown fox."
}
标准分词规则,结果:is、this、deja、vu
java
POST _analyze
{
"tokenizer": "standard",
"filter": [ "lowercase", "asciifolding" ],
"text": "Is this déja vu?"
}
关键词分词器:就是不分词,整句话当作专业术语
java
GET _analyze
{
"analyzer": "keyword",
"text": "Is this sl?"
}
打分机制
比如有3条内容:
1.苏麟是gay
2.苏麟暴打小杨科
3.小杨科
用户搜索:
1.杨科,第三条分数最高,因为第三条匹配了关键词,而且更短(匹配比例更大)
2.苏麟 => 苏麟是gay => 苏麟暴打小杨科 , 排序结果 1 2
参考文章 : Controlling Relevance | Elasticsearch: The Definitive Guide [master] | Elastic
Java 操作 ES
3 种方式:
- ES 官方的 Java API
文章 : Introduction | Elasticsearch Java API Client [7.17] | Elastic
快速开始 : Connecting | Elasticsearch Java API Client [7.17] | Elastic
-
ES 以前的官方 Java APl,HighLevelRestclient(已废弃,不建议用)
-
Spring Data Elasticsearch(推荐)
spring-data 系列:spring 提供的操作数据的框架
spring-data-redis:操作 redis 的-套方法
spring-data-mongodb:操作 mongodb 的一套方法
spring-data-elasticsearch:操作 elasticsearch 的一套方法
...
建表结构:
aliases 起别名
java
PUT post_v1
{
"aliases": {
"post": {}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"tags": {
"type": "keyword"
},
"thumbNum": {
"type": "long"
},
"favourNum": {
"type": "long"
},
"userId": {
"type": "keyword"
},
"createTime": {
"type": "date"
},
"updateTime": {
"type": "date"
},
"isDelete": {
"type": "keyword"
}
}
}
}
增删改查
第一种方式: ElasticsearchRepository<xxxx, Long>,默认提供了简单的增删改查,多用于可预期的、相对没那么复杂的查询、自定义查询,返回结果相对简单直接。
有一些现成的方法可以使用
java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.data.repository;
import java.util.Optional;
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
Optional<T> findById(ID id);
boolean existsById(ID id);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> ids);
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAllById(Iterable<? extends ID> ids);
void deleteAll(Iterable<? extends T> entities);
void deleteAll();
}
第二种方式: Spring 默认给我们提供的操作 es 的客户端对象 ElasticsearchRestTemplate,也提供了增删改查它的增删改查更灵活,适用于更复杂的操作,返回结果更完整,但需要自己解析。
准备工作
java
package com.yupi.springbootinit.model.dto.post;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.yupi.springbootinit.model.entity.Post;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 帖子 ES 包装类
*
**/
@Document(indexName = "post")
@Data
public class PostEsDTO implements Serializable {
private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
/**
* id
*/
@Id
private Long id;
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 标签列表
*/
private List<String> tags;
/**
* 创建用户 id
*/
private Long userId;
/**
* 创建时间
*/
@Field(index = false, store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN)
private Date createTime;
/**
* 更新时间
*/
@Field(index = false, store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN)
private Date updateTime;
/**
* 是否删除
*/
private Integer isDelete;
private static final long serialVersionUID = 1L;
/**
* 对象转包装类
*
* @param post
* @return
*/
public static PostEsDTO objToDto(Post post) {
if (post == null) {
return null;
}
PostEsDTO postEsDTO = new PostEsDTO();
BeanUtils.copyProperties(post, postEsDTO);
String tagsStr = post.getTags();
if (StringUtils.isNotBlank(tagsStr)) {
postEsDTO.setTags(JSONUtil.toList(tagsStr, String.class));
}
return postEsDTO;
}
/**
* 包装类转对象
*
* @param postEsDTO
* @return
*/
public static Post dtoToObj(PostEsDTO postEsDTO) {
if (postEsDTO == null) {
return null;
}
Post post = new Post();
BeanUtils.copyProperties(postEsDTO, post);
List<String> tagList = postEsDTO.getTags();
if (CollUtil.isNotEmpty(tagList)) {
post.setTags(JSONUtil.toJsonStr(tagList));
}
return post;
}
}
java
public interface PostEsDao extends ElasticsearchRepository<PostEsDTO, Long> {
}
java
@Resource
private PostEsDao postEsDao;
测试
java
@Test
void testAdd() {
PostEsDTO postEsDTO = new PostEsDTO();
postEsDTO.setId(1L);
postEsDTO.setTitle("苏麟");
postEsDTO.setContent("风雨交加的夜晚,苏麟暴打小杨科,小杨科奄奄一息");
postEsDTO.setTags(Arrays.asList("苏麟", "杨科","暴打"));
postEsDTO.setUserId(1L);
postEsDTO.setCreateTime(new Date());
postEsDTO.setUpdateTime(new Date());
postEsDTO.setIsDelete(0);
postEsDao.save(postEsDTO);
System.out.println(postEsDTO.getId());
}
DSL 查询结果
查询结果
java
@Test
void testFindById() {
Optional<PostEsDTO> postEsDTO = postEsDao.findById(1L);
System.out.println(postEsDTO);
}
DSL 查询转换 Java 代码查询
DSL 查询
文档 : Query and filter context | Elasticsearch Guide [7.17] | Elastic
文档 : Boolean query | Elasticsearch Guide [7.17] | Elastic
java
GET post/_search
{
"query": {
"bool": { //组合条件
"must": [ //必须满足
{ "match": { "title": "苏麟" }}, //match 模糊查询
{ "match": { "content": "苏麟" }}
],
"filter": [ //过滤
{ "term": { "status": "published" }}, //term 精确查询
{ "range": { "publish_date": { "gte": "2015-01-01" }}} //范围查询
]
}
}
}
java
GET post/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "苏麟" }},
{ "match": { "content": "苏麟" }}
]
}
}
}
wildcard 模糊查询
regexp 正则匹配查询
查询结果中,score 代表匹配分数
建议先测试 DSL、再翻译成 Java
java
POST _search
{
"query": {
"bool" : {
"must" : {
"term" : { "user.id" : "kimchy" }
},
"filter": {
"term" : { "tags" : "production" }
},
"must_not" : {
"range" : {
"age" : { "gte" : 10, "lte" : 20 }
}
},
"should" : [
{ "term" : { "tags" : "env1" } },
{ "term" : { "tags" : "deployed" } }
],
"minimum_should_match" : 1,
"boost" : 1.0
}
}
}
这期就到这里 , 下期见 !