一、Elasticsearch使用-linux
Es常用操作
索引操作
Elasticsearch是使用RESTful风格的http请求访问操作的,请求参数和返回值都是Json格式的,我们可以使用kibana发送http请求操作ES。
域的属性
属性 | 作用 |
---|---|
index | 该域是否创建索引。只有值设置为true,才能根据该域的关键词查询文档。 |
type | 域的类型 |
store | 是否单独存储。如果设置为true,则该域能够单独查询。 |
analyzer | standard、(ik_smart、ik_max_word)、pinyin、自定义分词器 |
- type
核心类型 | 具体类型 |
---|---|
字符串类型 | text |
整数类型 | long, integer, short, byte |
浮点类型 | double, float |
日期类型 | date |
布尔类型 | boolean |
数组类型 | array |
对象类型 | object |
不分词的字符串 | keyword |
根据关键词查询文档
- index
# 根据关键词查询文档
# 创建索引:"index": true
PUT /student1
{
"mappings": {
"properties": {
"name":{
"type": "text",
"index": true
}
}
}
}
# 不创建索引:"index": false
PUT /student2
{
"mappings": {
"properties": {
"name":{
"type": "text",
"index": false
}
}
}
}
# 创建文档
POST /student1/_doc/1
{
"name":"i love java"
}
POST /student2/_doc/1
{
"name":"i love java"
}
# 搜索文档
GET /student1/_search
{
"query": {
"term": {
"name": "love"
}
}
}
GET /student2/_search
{
"query": {
"term": {
"name": "love"
}
}
}
单独查询某个域
// 单独查询某个域:
GET /索引名/_search
{
"stored_fields": ["域名"]
}
分词器
ES文档的数据拆分成一个个有完整含义的关键词,并将关键词与文档对应,这样就可以通过关键词查询文档。要想正确的分词,需要选择合适的分词器。
⑴默认分词器:standard
- standard analyzer:Elasticsearch默认分词器,根据空格和标点符号对英文进行分词,会进行单词的大小写转换。
- 默认分词器是英文分词器,对中文的分词是一字一词。
GET /_analyze
{
"text": ["i love spring"],
"analyzer": "standard"
}
⑵IK分词器
- IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。提供了两种分词算法:
- ik_smart:最少切分
- ik_max_word:最细粒度划分
GET /_analyze
{
"text": ["我爱百战程序员"],
"analyzer": "ik_smart"
}
----------------------------------------
GET /_analyze
{
"text": ["我爱百战程序员"],
"analyzer": "ik_max_word"
}
IK分词器词典
- IK分词器根据词典进行分词,词典文件在IK分词器的config目录中。
- main.dic:IK中内置的词典。记录了IK统计的所有中文单词。
- IKAnalyzer.cfg.xml:用于配置自定义词库。
- 主配置文件
:配置文件名 :配置文件名cd /usr/local/elasticsearch1/plugins/analysis-ik/config/ vim 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_dict.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">ext_stopwords.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
- 自定义词
cd /usr/local/elasticsearch1/plugins/analysis-ik/config/ # 新增词 vim ext_dict.dic # 禁止词 vim ext_stopwords.dic
⑶拼音分词器
GET /_analyze
{
"text": ["我爱英雄联盟"],
"analyzer": "pinyin"
}
⑷自定义分词器
- 真实开发中我们往往需要对一段内容既进行文字分词,又进行拼音分词,此时我们需要自定义ik+pinyin分词器。
- 在创建索引时自定义分词器
PUT /索引名
{
"settings": {
"analysis": {
"analyzer": {
"ik_pinyin": {//自定义分词器名
"tokenizer": "ik_max_word",// 基本分词器
"filter": "pinyin_filter"// 配置分词器过滤
}
},
"filter": {// 分词器过滤时配置另一个分词器,相当于同时使用两个分词器
"pinyin_filter": {// 另一个分词器
"type": "pinyin",// 拼音分词器的配置
"keep_separate_first_letter": false,// 是否分词每个字的首字母
"keep_full_pinyin": true,// 是否分词全拼
"keep_original": true,// 是否保留原始输入
"remove_duplicated_term": true// 是否删除重复项
}
}
}
},
"mappings": {
"properties": {
"域名1": {
"type": 域的类型,
"store": 是否单独存储,
"index": 是否创建索引,
"analyzer": 分词器
},
"域名2": {
...
}
}
}
}
实例
PUT /student3
{
"settings": {
"analysis": {
"analyzer": {
"ik_pinyin":{
"tokenizer": "ik_max_word",
"filter": "pinyin_filter"
}
},
"filter": {
"pinyin_filter":{
"type": "pinyin",
"keep_separate_first_letter": false,
"keep_full_pinyin": true,
"keep_original": true,
"remove_duplicated_term": true
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"store": true,
"index": true,
"analyzer": "ik_pinyin"
},
"age": {
"type": "integer"
}
}
}
}
①、创建索引(PUT)
创建没有结构的索引
索引添加结构
POST /索引名/_mapping
{
"properties":{
"域名1":{
"type":域的类型,
"store":是否存储,
"index":是否创建索引,
"analyzer":分词器
},
"域名2":{
...
}
}
}
实例
# 创建索引
put /student
# 创建结构
POST /student/_mapping
{
"properties":{
"id":{
"type":"integer"
},
"name":{
"type":"text"
},
"age":{
"type":"integer"
}
}
}
创建有结构的索引
PUT /索引名
{
"mappings":{
"properties":{
"域名1":{
"type":域的类型,
"store":是否单独存储,
"index":是否创建索引,
"analyzer":分词器
},
"域名2":{
...
}
}
}
}
实例
# 创建索引&结构
PUT /student1
{
"mappings": {
"properties": {
"id":{
"type": "integer"
},
"name":{
"type": "text"
},
"age":{
"type": "integer"
}
}
}
}
②、删除索引(DELETE)
DELETE /索引名
实例
DELETE /student1
文档操作
①、新增/修改文档
id值不写时自动生成文档id,id和已有id重复时修改文档
POST /索引/_doc/[id值]
{
"field名":field值
}
实例
# 新增/修改文档
POST /student/_doc/1
{
"id":1,
"name":"bz",
"age":20
}
②、查询文档
实例 | 作用 |
---|---|
GET /索引/_doc/id值 | 根据id查询文档 |
GET /索引/_mget | 根据id批量查询文档 |
GET /索引/_search | 查询所有文档 |
POST /索引/_doc/id值/_update | 修改文档部分字段 |
根据id查询文档
# 根据id查询文档
GET /student/_doc/1
根据id批量查询文档
# 根据id批量查询文档
GET /student/_mget
{
"docs":[
{"_id":1},
{"_id":2}
]
}
查询所有文档
# 查询所有文档
GET /student/_search
{
"query": {
"match_all": {}
}
}
修改文档部分字段
- Elasticsearch执行删除操作时,ES先标记文档为deleted状态,而不是直接物理删除。当ES存储空间不足或工作空闲时,才会执行物理删除操作。
- Elasticsearch执行修改操作时,ES不会真的修改Document中的数据,而是标记ES中原有的文档为deleted状态,再创建一个新的文档来存储数据。
# 修改文档部分内容
POST /student/_doc/2/_update
{
"doc":{
"name":"tttttttttttttt"
}
}
③、删除文档
DELETE /索引/_doc/id值
实例
# 删除文档
DELETE /student/_doc/1
Elasticsearch搜索文档
GET /索引/_search
{
"query":{
搜索方式:搜索参数
}
}
搜索方式 | 搜索参数 | 含义 |
---|---|---|
match_all | {} | 查询所有文档 |
match | {搜索字段:搜索条件} | 将查询条件分词后再进行搜索。 |
range | {搜索字段:{“gte”:最小值,“lte”:最大值 }} | 对数字类型的字段进行范围搜索 |
match_phrase | {搜索字段:搜索条件} | 搜索条件不做任何分词解析 |
创建搜索数据
# 搜索文档
PUT /students
{
"mappings": {
"properties": {
"id": {
"type": "integer",
"index": true
},
"name": {
"type": "text",
"store": true,
"index": true,
"analyzer": "ik_smart"
},
"info": {
"type": "text",
"store": true,
"index": true,
"analyzer": "ik_smart"
}
}
}
}
# 添加数据
POST /students/_doc/
{
"id":1,
"name":"IT小熊",
"info":"I love java"
}
POST /students/_doc/
{
"id":2,
"name":"美羊羊",
"info":"美羊羊是羊村最漂亮的羊"
}
POST /students/_doc/
{
"id":3,
"name":"懒洋洋",
"info":"懒洋洋的成绩不是很好"
}
POST /students/_doc/
{
"id":4,
"name":"小灰灰",
"info":"小灰灰的成绩比较小"
}
POST /students/_doc/
{
"id":5,
"name":"沸羊羊",
"info":"沸羊羊喜欢美羊羊"
}
POST /students/_doc/
{
"id":6,
"name":"灰太狼",
"info":"灰太狼是小灰灰的父亲,口头禅是我一定会回来的"
}
①、简单搜索
⑴查询所有文档
格式:
{
"query":{
"match_all":{}
}
}
实例
# 查询所有文档
GET /students/_search
{
"query": {
"match_all": {}
}
}
⑵全文检索(match)
将查询条件分词后再进行搜索。
在搜索时关键词有可能会输入错误,ES搜索提供了自动纠错功能,即ES的模糊查询。使用match方式可以实现模糊查询。模糊查询对中文的支持效果一般,我们使用英文数据测试模糊查询。
格式:
{
"query":{
"match":{
搜索字段:搜索条件
}
}
}
实例一
# 全文检索(包含其中的词,就会被检索到)
GET /students/_search
{
"query": {
"match": {
"info": "我喜欢成绩好的"
}
}
}
实例二
----------------------------------
纠错
GET /students/_search
{
"query": {
"match": {
"info": {
"query":"love",
"fuzziness":1
}
}
}
}
⑶范围搜索(range)
对数字类型的字段进行范围搜索
格式:
{
"query":{
"range":{
搜索字段:{
"gte":最小值,
"lte":最大值
}
}
}
}
gt/lt:大于/小于
gte/lte:大于等于/小于等于
实例
# 范围搜索
GET /students/_search
{
"query": {
"range": {
"id": {
"gte": 1,
"lte": 3
}
}
}
}
⑷短语检索
搜索条件不做任何分词解析,在搜索字段对应的倒排索引中精确匹配。
格式:
{
"query":{
"match_phrase":{
搜索字段:搜索条件
}
}
}
实例
# 短语检索
GET /students/_search
{
"query": {
"match_phrase": {
"info": "喜欢"
}
}
}
⑸单词/词组搜索
搜索条件不做任何分词解析,在搜索字段对应的倒排索引中精确匹配
格式:
{
"query":{
"term/terms":{
搜索字段: 搜索条件
}
}
}
实例
# 单词检索
GET /students/_search
{
"query": {
"terms": {
"info": [
"成绩",
"喜欢"
]
}
}
}
②、复合搜索
方式 | 含义 |
---|---|
must | 必须满足的条件 |
should | 多个条件有任意一个满足即可 |
must_not | 必须不满足的条件 |
格式:
GET /索引/_search
{
"query": {
"bool": {
// 必须满足的条件
"must": [
搜索方式:搜索参数,
搜索方式:搜索参数
],
// 多个条件有任意一个满足即可
"should": [
搜索方式:搜索参数,
搜索方式:搜索参数
],
// 必须不满足的条件
"must_not": [
搜索方式:搜索参数,
搜索方式:搜索参数
]
}
}
}
实例
# 复合搜索
GET /students/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"info": "美羊羊喜欢成绩好的同学"
}
}
],
"must_not": [
{
"range": {
"FIELD": {
"gte": 1,
"lte": 3
}
}
}
]
}
}
}
③、结果排序
ES中默认使用相关度分数实现排序,可以通过搜索语法定制化排序。
由于ES对text类型字段数据会做分词处理,使用哪一个单词做排序都是不合理的,所以 ES中默认不允许对text类型的字段做排序。如果需要使用字符串做结果排序,可以使用 keyword类型的字段作为排序依据,因为keyword字段不做分词处理。
格式:
GET /索引/_search
{
"query": 搜索条件,
"sort": [
{
"字段1":{
"order":"asc"
}
},
{
"字段2":{
"order":"desc"
}
}
]
}
实例
# 结果排序
GET /students/_search
{
"query": {
"match": {
"info": "我喜欢成绩好的同学"
}
},
"sort": [
{
"id": {
"order": "desc"
}
}
]
}
④、分页查询
格式:
GET /索引/_search
{
"query": 搜索条件,
"from": 起始下标,
"size": 查询记录数
}
实例
# 分页查询
GET /students/_search
{
"query": {
"match_all": {}
},
"from": 4,
"size": 4
}
⑤、高亮查询
在进行关键字搜索时,搜索出的内容中的关键字会显示不同的颜色,称之为高亮。
我们可以在关键字左右加入标签字符串,数据传入前端即可完成高亮显示,ES可以对查询出的内容中关键字部分进行标签和样式的设置。
格式:
GET /索引/_search
{
"query":搜索条件,
"highlight":{
"fields": {
"高亮显示的字段名": {
// 返回高亮数据的最大长度
"fragment_size":100,
// 返回结果最多可以包含几段不连续的文字
"number_of_fragments":5
}
},
"pre_tags":["前缀"],
"post_tags":["后缀"]
}
}
实例
# 高亮查询
GET /students/_search
{
"query": {
"match": {
"info": "我喜欢成绩好的同学"
}
},
"highlight": {
"fields": {
"info": {
"fragment_size": 100,
"number_of_fragments": 5
}
},
"pre_tags": ["<em>"],
"post_tags": ["</em>"]
}
}
⑥、SQL查询
开源版本的ES并不支持通过Java操作SQL进行查询,如果需要操作 SQL查询,则需要氪金(购买白金版)
格式:
GET /_sql?format=txt
{
"query": SQL语句
}
实例
# SQL查询
GET /_sql?format=txt
{
"query": "select * from students"
}
ES自动补全
自动补全对性能要求极高,ES不是通过倒排索引来实现的,所以需要将对应的查询字段类型设置为completion。
GET /索引/_search { "suggest": { "prefix_suggestion": {// 自定义推荐名 "prefix": "elastic", // 被补全的关键字 "completion": { "field": "productName", // 查询的域 "skip_duplicates": true, //忽略重复结果 "size": 10 //最多查询到的结果数 } } } }
# 创建索引
PUT /product2
{
"mappings": {
"properties": {
"id":{
"type": "integer",
"store": true,
"index": true
},
"productName":{
"type": "completion"
},
"productDesc":{
"type": "text",
"store": true,
"index": true
}
}
}
}
# 准备数据
POST /product2/_doc
{
"id":1,
"productName":"elasticsearch1",
"productDesc":"elasticsearch1 is a good engine"
}
POST /product2/_doc
{
"id":2,
"productName":"elasticsearch2",
"productDesc":"elasticsearch2 is a good engine"
}
POST /product2/_doc
{
"id":3,
"productName":"elasticsearch3",
"productDesc":"elasticsearch3 is a good engine"
}
# 搜索
GET /product2/_search
{
"suggest": {
"prefix_SUGGESTION": {
"prefix": "elastic",
"completion": {
"field": "productName",
"skip_duplicates": true,
"size": 10
}
}
}
}
二、Elasticsearch使用-Maven
导入依赖
<!-- es 依赖 版本和使用的软件相同 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.17.0</version>
</dependency>
<!-- es 客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.0</version>
</dependency>
Elasticsearch使用
索引操作
①、创建索引
创建空索引
// 创建空索引
@Test
public void createIndex() throws IOException {
// 1.创建客户端对象,连接ES
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.66.66", 9200, "http")));
// 2.创建请求对象
CreateIndexRequest request = new CreateIndexRequest("student");
// 3.发送请求
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
// 4.操作响应结果
System.out.println(response.index());
// 5.关闭客户端
client.close();
}
给索引添加结构
// 给索引添加结构
@Test
public void mappingIndex() throws IOException {
// 1.创建客户端对象,连接ES
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.66.66", 9200, "http")));
// 2.创建请求对象
PutMappingRequest request = new PutMappingRequest("student");
request.source("{\n" +
" \"properties\":{\n" +
" \"id\":{\n" +
" \"type\":\"integer\"\n" +
" },\n" +
" \"name\":{\n" +
" \"type\":\"text\"\n" +
" },\n" +
" \"age\":{\n" +
" \"type\":\"integer\"\n" +
" }\n" +
" }\n" +
"}", XContentType.JSON);
// 3.发送请求
AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT);
// 4.操作响应结果
System.out.println(response.isAcknowledged());
// 5.关闭客户端
client.close();
}
②、删除索引
// 删除索引
@Test
public void deleteIndex() throws IOException {
// 1.创建客户端对象,连接ES
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new
HttpHost("192.168.66.66", 9200, "http")));
// 2.创建请求对象
DeleteIndexRequest request = new DeleteIndexRequest("student");
// 3.发送请求
AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
// 4.操作响应结果
System.out.println(response.isAcknowledged());
// 5.关闭客户端
client.close();
}
文档操作
①、新增&修改文档
//新增&修改文档
@Test
public void addDocument() throws IOException {
// 1.创建客户端对象,连接ES
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.66.66", 9200, "http")));
// 2.创建请求对象
IndexRequest request = new IndexRequest("student").id("1");
request.source(XContentFactory.jsonBuilder()
.startObject()
.field("id", 1)
.field("name", "i love baizhan")
.field("age", 20)
.endObject());
// 3.发送请求
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
// 4.操作响应结果
System.out.println(response.status());
// 5.关闭客户端
client.close();
}
②、查询文档
根据id查询文档
//根据id查询文档
@Test
public void findByIdDocument() throws IOException {
// 1.创建客户端对象,连接ES
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.66.66", 9200, "http")));
// 2.创建请求对象
GetRequest request = new GetRequest("student", "1");
// 3.发送请求
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 4.操作响应结果
System.out.println(response.getSourceAsString());
// 5.关闭客户端
client.close();
}
③、删除文档
@Test
public void DeleteDocument() throws IOException {
// 1.创建客户端对象,连接ES
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.66.66", 9200, "http")));
// 2.创建请求对象
DeleteRequest request = new DeleteRequest("student", "1");
// 3.发送请求
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
// 4.操作响应结果
System.out.println(response.status());
// 5.关闭客户端
client.close();
}
搜索操作
①、搜索所有文档
//搜索所有文档
@Test
public void queryAllDocument() throws IOException {
// 创建客户端对象,链接ES
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.66.66", 9200, "http")));
// 创建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
// 创建请求对象
SearchRequest request = new SearchRequest("student").source(searchSourceBuilder);
// 发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 输出返回结果
for (SearchHit hit : response.getHits()) {
System.out.println(hit.getSourceAsString());
}
// 关闭客户端
client.close();
}
②、根据关键词搜索文档
// 根据关键词搜索文档
@Test
public void queryTermDocument() throws
IOException {
// 创建客户端对象,链接ES
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.66.66", 9200, "http")));
// 创建请求条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("info", "hunaggang"));
// 创建请求对象
SearchRequest request = new SearchRequest("student").source(searchSourceBuilder);
// 发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 输出返回结果
for (SearchHit hit : response.getHits()) {
System.out.println(hit.getSourceAsString());
}
// 关闭客户端
client.close();
}
三、Elasticsearch使用-Springboot
导入依赖
<!-- es 起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
报错:版本控制
<properties>
<java.version>11</java.version>
<!-- 版本控制:和es版本一致 -->
<elasticsearch.version>7.17.0</elasticsearch.version>
</properties>
配置文件
# elasticsearch配置
spring:
elasticsearch:
uris: http://192.168.66.66:9200
使用Elasticsearch
创建索引
注解 | 位置 |
---|---|
@Document | 标记在类上,标记实体类为文档对象 |
@Id | 标记在成员变量上,标记一个字段为主键,该字段的值会同步到ES该文档的id值。 |
@Field | 标记在成员变量上,标记为文档中的域。 |
一个实体类的所有对象都会存入ES的一个索引中,所以我们在创建实体类时关联ES索引
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "product", createIndex = true)
public class Product {
@Id
@Field(type = FieldType.Integer, store = true, index = true)
private Integer id;
@Field(type = FieldType.Text, store = true,
index = true, analyzer = "ik_max_word",
searchAnalyzer = "ik_max_word")
private String productName;
@Field(type = FieldType.Text, store = true,
index = true, analyzer = "ik_max_word",
searchAnalyzer = "ik_max_word")
private String productDesc;
}
@Document
标记在类上,标记实体类为文档对象,一般有如下属性:
属性 | 意义 |
---|---|
indexName | 对应索引的名称 |
createIndex | 是否自动创建索引 |
@Id
标记在成员变量上,标记一个字段为主键,该字段的值会同步到ES该文档的id值。
@Field
标记在成员变量上,标记为文档中的域,一般有如下属性:
属性 | 意义 |
---|---|
type | 域的类型 |
index | 是否创建索引,默认是 true |
store | 是否单独存储,默认是 false |
analyzer | 分词器 |
searchAnalyzer | 搜索时的分词器 |
操作文档-继承接口
创建Repository接口继承ElasticsearchRepository,该接口提供了文档的增删改查方法
public interface ProductRepository extends ElasticsearchRepository<Product,Integer> {
}
①、自带方法
新增/修改文档
@Test
public void addDocument(){
Product product = new Product(1, "iphone30", "iphone30是苹果最新手机");
Product save = productRepository.save(product);
System.out.println(save);
}
查询所有
@Test
public void findAllDocument() {
Iterable<Product> all = repository.findAll();
all.forEach(product -> {
System.out.println(product);
});
}
通过id查询
@Test
public void findDocumentById(){
Optional<Product> product = repository.findById(1);
System.out.println(product.get());
}
②、自定义
- 创建数据-使用DSL语句查询文档
@Test
public void addDocument() {
// 添加一些数据
repository.save(new Product(2, "三体1", "三体1是优秀的科幻小说"));
repository.save(new Product(3, "三体2", "三体2是优秀的科幻小说"));
repository.save(new Product(4, "三体3", "三体3是优秀的科幻小说"));
repository.save(new Product(5, "elasticsearch", "elasticsearch是基于lucene开发的优秀的搜索引擎"));
}
⑴@Query
query后的json对象称为DSL语句,我们可以在接口方法上使用@Query注解自定义DSL语句查询。
占位符:?0
public interface ProductRepository extends ElasticsearchRepository<Product,Integer> {
@Query("{\n" +
" \"match\": {\n" +
" \"productDesc\": \"?0\"\n" +
" }\n" +
" }")
List<Product> findByProductDescMatch(String keyword);
}
⑵命名方法命名规则
- 只需在Repository接口中按照SpringDataES的规则命名方法,该方法就能完成相应的查询。
- 规则:查询方法以findBy开头,涉及查询条件时,条件的属性用条件关键字连接。
关键字 | 命名规则 | 解释 | 示例 |
---|---|---|---|
and | findByField1AndField2 | 根据Field1和Field2 获得数据 | findByTitleAndContent |
or | findByField1OrField2 | 根据Field1或Field2 获得数据 | findByTitleOrContent |
is | findByField | 根据Field获得数据 | findByTitle |
not | findByFieldNot | 根据Field获得补集数据 | findByTitleNot |
between | findByFieldBetween | 获得指定范围的数据 | findByPriceBetween |
List<Product> findByProductName(String productName);
List<Product> findByProductNameOrProductDesc(String productName,String productDesc);
List<Product> findByIdBetween(Integer startId,Integer endId);
⑶分页查询
使用继承或自定义的方法时,在方法中添加Pageable类型的参数,返回值为Page类型即可进行分页查询。
- 继承方法实现分页
@Test
public void test(){
Pageable pageable = PageRequest.of(1,3);
Page<Product> page = repository.findAll(pageable);
System.out.println("总条数"+page.getTotalElements());
System.out.println("总页数"+page.getTotalPages());
System.out.println("数据"+page.getContent());
}
------------------------------------------------
@Test
public void testFindPage3() {
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(0, 2, sort);
Page<Product> page = repository.findByProductDesc("我喜欢三体", pageable);
System.out.println("总条数" + page.getTotalElements());
System.out.println("总页 数" + page.getTotalPages());
System.out.println("数据" + page.getContent());
}
- 自定义方法实现分页
@Query("{\n" +
" \"match\": {\n" +
" \"productDesc\": \"?0\"\n" +
" }\n" +
" }")
Page<Product> findByProductDesc(String keyword, Pageable pageable);
----------------------------------------------------------
@Test
public void testFindPage2() {
Pageable pageable = PageRequest.of(1, 2);
Page<Product> page = repository.findByProductDesc("我喜欢三体", pageable);
System.out.println("总条数" + page.getTotalElements());
System.out.println("总页数" + page.getTotalPages());
System.out.println("数据" + page.getContent());
}
⑷结果排序
使用继承或自定义的方法时,在方法中添加Sort类型的参数即可进行结果排序。
@Test
public void testFindSort() {
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Iterable<Product> all = repository.findAll(sort);
for (Product product : all) {
System.out.println(product);
}
}
操作文档-template工具类
SpringDataElasticsearch提供了一个工具类ElasticsearchRestTemplate,我们使用该类对象也能对ES进行操作。
①、索引操作
新增
(推荐注解自动创建索引)
@Test
public void addIndex() {
// 获得索引操作对象
IndexOperations indexOperations = template.indexOps(Product.class);
// 创建索引,注:该方法无法设置索引结构,不推荐使用
indexOperations.create();
}
删除
@Test
public void delIndex() {
// 获得索引操作对象
IndexOperations indexOperations = template.indexOps(Product.class);
// 删除索引
indexOperations.delete();
}
②、文档操作
新增/修改文档:save()
@Test
public void addDocument() {
Product product = new Product(7, "es1", "es是一款优秀的搜索引擎");
template.save(product);
}
删除文档:delete()
@Test
public void delDocument() {
template.delete("7", Product.class);
}
③、查询操作
查询文档:SearchHits search(Query query, Classclazz):查询文档,query是查询条件对象,clazz是结果类型。
普通查询
@Test
public void searchDocument() {
// 1.确定查询方式
// MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
// TermQueryBuilder builder = QueryBuilders.termQuery("productDesc", "手机");
MatchQueryBuilder builder = QueryBuilders.matchQuery("productDesc", "我喜欢看科幻小说");
// 2.构建查询条件
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(builder).build();
// 3.查询
SearchHits<Product> result = template.search(query, Product.class);
// 4.处理查询结果
result.forEach(productSearchHit -> {
System.out.println(productSearchHit.getContent());
});
}
复杂查询
//复杂条件查询
@Test
public void searchDocument2() {
// String productName ="elasticsearch";
// String productDesc = "优秀";
String productName = null;
String productDesc = null;
// 1.确定查询方式
BoolQueryBuilder builder = QueryBuilders.boolQuery();
// 如果没有传入参数,查询所有
if (productName == null && productDesc == null) {
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
builder.must(matchAllQueryBuilder);
} else {
if (productName != null && productName.length() > 0) {
MatchQueryBuilder queryBuilder1 = QueryBuilders.matchQuery("productName", productName);
builder.must(queryBuilder1);
}
if (productDesc != null && productDesc.length() > 0) {
MatchQueryBuilder queryBuilder2 = QueryBuilders.matchQuery("productDesc", productDesc);
builder.must(queryBuilder2);
}
}
// 2.构建查询条件
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(builder).build();
// 3.查询
SearchHits<Product> result = template.search(query, Product.class);
// 4.处理查询结果
for (SearchHit<Product> productSearchHit : result) {
Product product = productSearchHit.getContent();
System.out.println(product);
}
}
分页查询
@Test
public void searchDocumentPage() {
// 1.确定查询方式
MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
// 2.构建查询条件
// 分页条件
Pageable pageable = PageRequest.of(0, 3);
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(builder)
.withPageable(pageable)
.build();
// 3.查询
SearchHits<Product> result =
template.search(query, Product.class);
// 4.将查询结果封装为Page对象
List<Product> content = new ArrayList();
for (SearchHit<Product> productSearchHit : result) {
Product product = productSearchHit.getContent();
content.add(product);
}
/**
* 封装Page对象,参数1:具体数据,参数2:分页条件对象,参数3:总条数
*/
Page<Product> page = new PageImpl(content, pageable, result.getTotalHits());
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
System.out.println(page.getContent());
}
结果排序
@Test
public void searchDocumentSort() {
// 1.确定查询方式
MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
// 2.构建查询条件
// 排序条件
SortBuilder sortBuilder = SortBuilders.fieldSort("id").order(SortOrder.DESC);
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(builder)
.withSorts(sortBuilder)
.build();
// 3.查询
SearchHits<Product> result = template.search(query, Product.class);
// 4.处理查询结果
for (SearchHit<Product> productSearchHit : result) {
Product product = productSearchHit.getContent();
System.out.println(product);
}
}
四、Elasticsearch优化
磁盘选择
ES的优化即通过调整参数使得读写性能更快磁盘通常是服务器的瓶颈。Elasticsearch重度使用磁盘,磁盘的效率越高,Elasticsearch的执行效率就越高。这里有一些优化磁盘的技巧:
- 使用SSD(固态硬盘),它比机械磁盘优秀多了。
- 使用RAID0模式(将连续的数据分散到多个硬盘存储,这样可以并行进行IO操作),代价是一块硬盘发生故障就会引发系统故障。
- 不要使用远程挂载的存储。
分片策略
分片和副本数并不是越多越好。每个分片的底层都是一个Lucene索引,会消耗一定的系统资源。且搜索请求需要命中索引中的所有分片,分片数过多会降低搜索性能。索引的分片数需要架构师和技术人员对业务的增长有预先的判断,一般来说我们遵循以下原则:
- 每个分片占用的硬盘容量不超过ES的最大JVM的堆空间设置(一般设置不超过32G)。比如:如果索引的总容量在500G左右,那分片数量在16个左右即可。
- 分片数一般不超过节点数的3倍。比如:如果集群内有10个节点,则分片数不超过30个。
- 减少副本数量:进行写入操作时,需要把写入的数据都同步到副本,副本越多写入的效率就越慢。我们进行大批量进行写入操作时可以先设置副本数为0,写入完成后再修改回正常的状态。
- 推迟分片分配:节点中断后集群会重新分配分片。但默认集群会等待一分钟来查看节点是否重新加入。我们可以设置等待的时长,减少重新分配的次数:
PUT /索引/_settings
{
"settings":{
"index.unassianed.node_left.delayed_timeout":"5m"
}
}
内存设置
ES默认占用内存是4GB,我们可以修改config/jvm.option设置ES的堆内存大小,Xms表示堆内存的初始大小,Xmx表示可分配的最大内存。
- Xmx和Xms的大小设置为相同的,可以减轻伸缩堆大小带来的压力。
- Xmx和Xms不要超过物理内存的50%,因为ES内部的Lucene也要占据一部分物理内存。
- Xmx和Xms不要超过32GB,由于Java语言的特性,堆内存超过32G会浪费大量系统资源,所以在内存足够的情况下,最终我们都会采用设置为31G:
cd /usr/local/elasticsearch1/config/
vim jvm.option
-Xms 31g
-Xmx 31g
五、Elasticsearch案例
- 创建索引
PUT /news
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"ik_pinyin": {
"tokenizer": "ik_smart",
"filter": "pinyin_filter"
},
"tag_pinyin": {
"tokenizer": "keyword",
"filter": "pinyin_filter"
}
},
"filter": {
"pinyin_filter": {
"type": "pinyin",
"keep_joined_full_pinyin": true,
"keep_original": true,
"remove_duplicated_term": true
}
}
}
},
"mappings": {
"properties": {
"id": {
"type": "integer",
"index": true
},
"title": {
"type": "text",
"index": true,
"analyzer": "ik_pinyin",
"search_analyzer": "ik_smart"
},
"content": {
"type": "text",
"index": true,
"analyzer": "ik_pinyin",
"search_analyzer": "ik_smart"
},
"url": {
"type": "keyword",
"index": true
},
"tags": {
"type": "completion",
"analyzer": "tag_pinyin",
"search_analyzer": "tag_pinyin"
}
}
}
}
mysql数据同步到es中
①、解压logstash-7.17.0-windows-x86_64.zip
windows:logstash要和elastisearch版本一致
②、在解压路径下的/config中创建mysql.conf文件,文件写入以下脚本内容:
input {
jdbc {
jdbc_driver_library => "C:\Users\Administrator\Desktop\logstash-7.17.0-windows-x86_64\mysql-connector-java-8.0.29.jar"
jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
jdbc_connection_string => "jdbc:mysql:///news"
jdbc_user => "root"
jdbc_password => "root"
schedule => "* * * * *"
jdbc_default_timezone => "Asia/Shanghai"
statement => "SELECT * FROM news"
}
}
filter {
mutate {
split => {"tags" => ","}
}
}
output {
elasticsearch {
hosts => ["192.168.66.66:9200"]
index => "news"
document_id => "%{id}"
}
}
③、在解压路径下打开cmd黑窗口,运行命令:cmd
bin\logstash -f config\mysql.conf
④、测试
GET /news/_search
{
"query": {
"match_all": {}
}
}