ElasticSearch快速入门和实战
1、什么是全文搜索 lucene
全文搜索是计算机通过扫描文章的每一个词,对每一个词建立一个索引,指明改词咋子文章中出现的次数和位置,当用户查询时根据索引查找,类似字典通过检索字表查询的字过程
检索:检(建立索引) 索(搜索索引)
全文检索以文本为检索对象,找出含有指定词汇的文本。全面、准确和快速是衡量全文检索系统的关键指标
关于全文检索我们要知道
1. 只处理文本
2. 不处理语义
3. 搜索时英文不区分大小写
4. 结果列表有相关度排序
2、什么是Elastic Search
ElasticSearch简称ES,是基于lucene构建的一个准实时的高扩展的分布式搜索引擎。目前在企业级的搜索引擎中是非常流行的。Elasticsearch使用Java开发,提供了简单的RESTful API从而让全文搜索变得简单,避免了lucene的复杂性
3、Elasticsearch的特点
(1)可以作为一个大型分布式集群(数百台服务器)技术
,处理PB级数据,服务大公司;也可以运行在单机上,服务小公司
(2)Elasticsearch不是什么新技术,主要是将全文检索、数据分析以及分布式技术,合并在了一起
,才形成了独一无二的ES;lucene(全文检索),商用的数据分析软件(也是有的),分布式数据库(mycat)
(3)对用户而言,是开箱即用的,非常简单
,作为中小型的应用,直接3分钟部署一下ES,就可以作为生产环**境的系统来使用了,数据量不大,操作不是太复杂
4、Elasticsearch的应用场景
国外
维基百科、Stack Overflow(国外的程序异常讨论论坛)、GitHub…
国内
百度、阿里巴巴、腾讯、携程、滴滴、今日头条、饿了么、360安全、小米、vivo均有对ES的使用
5、Elasticsearch的相关概念关系图
6、kibana的基本操作
6.1、索引的基本操作
# 1.创建索引(不能有大写)
PUT /ems
PUT /wadwa
# 2.查看索引
GET /_cat/indices?v
# 3.删除索引
DELETE /ems
DELETE /*
6.2、类型的操作
创建类型
# 类型操作 index/type(mapping) 一个索引只能存在一个类型 ems/emp(id name age bir)
PUT /ems
{
"mappings": {
"emp" : {
"properties": {
"id" : {"type":"keyword"},
"name" : {"type":"text"},
"age" : {"type":"integer"},
"bir" : {"type":"date"}
}
}
}
}
Mapping Type : text,keyword,date,integer,long,double ,boolean ,ip
查看类型
# 查看创建索引以及索引中的映射类型
# 语法: GET/索引名/mapping/类型名
GET /ems/_mapping
6.3、文档的基本操作
添加文档
PUT /ems/emp/1
{
"id":1,
"name":"zhangsan",
"age" : 23,
"bir" : "2012-12-12"
}
#注意:如果需要自动生成id要以post方式,即POST /ems/emp
# 结果
{
"_index" : "ems",
"_type" : "emp",
"_id" : "1",
"_version" : 1,
"result" : "created", #创建
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}
查询文档
GET /ems/emp/1
# 结果
{
"_index" : "ems",
"_type" : "emp",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : { #查询结果
"id" : 1,
"name" : "zhangsan",
"age" : 23,
"bir" : "2012-12-12"
}
}
删除文档
DELETE /ems/emp/1
# 结果
{
"_index" : "ems",
"_type" : "emp",
"_id" : "1",
"_version" : 2,
"result" : "deleted", #删除成功
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
更新文档
不保留原始数据更新
GET /ems/emp/1
{
"name":"lisi"
}
# 插入之后的结果
{
"_index" : "ems",
"_type" : "emp",
"_id" : "1",
"_version" : 2,
"_seq_no" : 3,
"_primary_term" : 2,
"found" : true,
"_source" : {
"name" : "lisi"
}
}
保留原始数据更新(更新时添加新字段)
GET /ems/emp/1/_update
{
"doc":{
"name":"wangwu",
"age":18
}
}
#结果
{
"_index" : "ems",
"_type" : "emp",
"_id" : "1",
"_version" : 5,
"_seq_no" : 6,
"_primary_term" : 2,
"found" : true,
"_source" : {
"id" : 1,
"name" : "wangwu",
"age" : 18,
"bir" : "2012-12-12"
}
}
文档的批量操作
# 文档的批量操作: _bulk
PUT /ems/emp/_bulk
{"index":{"_id":5}}
{"name":"小明","age":23,"bir":"2013-10-10","intr":"小妹妹"}
{"index":{"_id":4}}
{"name":"小黑","age":24,"bir":"2013-10-10","intr":"小弟弟"}
{"delete":{"_id":4}}
{"update":{"id":1}}
{"doc":{"name":"小霸王"}}
7、Elasticsearch的高级搜索(Query)
7.1、检索方式
-
使用语法:
URL查询: GET/索引/类型/_search?参数
DSL查询: GET/索引/类型/_search{}
官方推荐使用DSL方式
7.2、URL检索
GET /ems/emp/_search?q=*&sort=age:desc&size=5&form=0&source=name,age,bir
_search 搜索的api
q=* 匹配所有文档
sort 以结果中指定字段排序,默认是asc
size 每页显示条数
from 从第几条开始
source : 指定显示的字段
7.3、DSL检索
普通查询
GET /ems/emp/_search
{
"query": {"match_all": {}},
"sort": [
{
"age": {
"order": "desc"
}
},
{
"bir": {
"order": "desc"
}
}
],
"size": 3,
"from": 1,
"_source": "{name,age,bir}"
}
"query": {"match_all": {}}
: 查询所有
"sort": [ { "age": {"order": "desc"}}, {"bir": { "order": "desc"} }]
: 排序,可多个字段联合排序,注意: 不能以text类型排序
"size": 3, "from": 1
: 显示下标1开始的3条记录
"_source": "{name,age,bir}"
: 指定要显示的字段
关键字查询(term)
GET /ems/emp/_search
{
"query": {
"term": {
"FIELD": {
"value": "VALUE"
}
}
}
}
FIELD
: 字段名
VALUE
: 要查找的关键字
重点:
索引库原理
范围查询(range)
GET /ems/emp/_search
{
"query": {"range": {
"age": {
"gte": 1,
"lte": 10
}
}}
}
gte :大于等于
lte : 小于等于
前缀查询(prefix)
检索含有指定前缀的关键词的相关文档
# 前缀查询基于关键词前缀查询 prefix
GET /ems/emp/_search
{
"query": {
"prefix": {
"name": {
"value": "李"
}
}
}
}
通配符查询 (wildcard)
GET /ems/emp/_search
{
"query": {
"wildcard": {
"name": {
"value": "李*"
}
}
}
}
?
: 匹配单个字符
*
: 匹配任意个字符
多个id查询 (ids)
GET /ems/emp/_search
{
"query": {
"ids": {
"name":["id1","id2","idn"]
}
}
}
模糊查询(fuzzy)
1、最大模糊错误在0~2之间
2、搜索关键词为2不允许存在模糊
3、搜索长度在3~5允许一次模糊查询
4、搜索长度大于5,允许两次模糊
布尔查询(bool)
bool
: 组合多个条件实现复杂查询 must: 相当于&& 同时成立
should: 相当于||成立一个就行
must_not:相当于!不能满足任何一个
高亮查询(highlight)
GET /ems/emp/_search
{
"query": {
"term": {
"content": {
"value": "redis"
}
}
},
"highlight": {
"pre_tags": ["<span style='color:red'"],
"post_tags": ["</span>"],
"fields": {
"content" : {}
}
}
}
将查询结果带redis的内容进行高亮,默认是em标签为斜体
pre_tags : 进行高亮的前缀
post_tags : 后缀
多字段查询 (multi_match)
GET /ems/emp/_search
{
"query": {
"multi_match": {
"query": "Redis小",
"fields": ["content","name"]
}
}
}
如果搜索的字段分词,先分词在搜索
如果不分词,则整体搜索
多字段分词查询
GET /ems/emp/_search
{
"query": {
"query_string": {
"default_field": "content",
"query": "开源框架"
}
}
}
先对query这句话分词,在去content搜索
8、ik分词器
8.1 、ik分词器的在线安装
使用该命令在线安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip
注意:需要和es版本一致
8.2、测试ik分词器安装成功
GET /_analyze
{
"text" : "中华人民共和国国歌",
"analyzer": "ik_max_word" #ik_smart
}
# 结果
{
"tokens" : [
{
"token" : "中华人民共和国",
"start_offset" : 0,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "中华人民",
"start_offset" : 0,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "中华",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "华人",
"start_offset" : 1,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 3
},....}
ik_max_word
: 表示最细分词方式
ik_smart
: 表示最粗分词方式,即中华人民共和国国歌拆分为 “中华人民共和国” 和 “国歌”
8.3、创建索引时指定分词类型
PUT /ems
{
"mappings": {
"emp" : {
"properties": {
"id" : {"type":"keyword"},
"name" : {
"type":"text",
"analyzer" : "ik_max_word"
},
"content" : {
"type":"text",
"analyzer" : "ik_max_word"
},
"bir" : {"type":"date"}
}
}
}
}
8.4、配置扩展字典
添加一条数据文档
# 添加文档
PUT /ems/emp/2
{
"id":2,
"name":"张三啊",
"content":"小明开车到大街上,被碰瓷了!",
"bir":"2018-11-11"
}
# 查询
GET /ems/emp/_search
{
"query": {
"term": {
"content": {
"value": "碰瓷"
}
}
}
}
发现根据关键字碰瓷搜索查不到,因为碰瓷是比较新的词,es并没有
这时就需要使用扩展词了
# 新建一个文件exts.dic,在里面添加"碰瓷"
vim /usr/local/elasticsearch-6.8.0/config/analysis-ik/exts.dic
打开配置文件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">exts.dic </entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
根据需要进行配置,这里我把exts.dic文件添加到扩展字典
重启elasticsearch和kibana,再次搜索
GET /ems/emp/_search
{
"query": {
"term": {
"content": {
"value": "碰瓷"
}
}
}
}
发现可以搜到结果了
8.5、配置远程扩展字典
1、新建一个springboot项目,在mian下面添加webapp/ext.txt
2、在ext.txt文件添加内容"杠精"
3、将该springboot项目配置修改如下:
4、修改配置文件IKAnalyzer.cfg.xml
<entry key="remote_ext_dict">http://localhost/es/ext.txt</entry>
localhost改成你的主机的ip,如果是虚拟机那就是192.168…
如果是云服务器要和自己电脑连接可使用端口映射
5、重新启动ES
看到这个表示扩展成功
6、我们再在ext.txt随便添加点东西,重新部署springboot,发现es可以实时刷新
9、过滤查询(Filter Query)
9.1、过滤查询
过滤查询(Filter queries)只是**简单的检查包含或者排除**,筛选出符合的文档,**并不计算、得分、排序、并且可以缓存文档**,因此性能比单查询快,**过滤适用于大数据,因此一般先进行过滤,再在过滤的基础上查询**
9.2、过滤查询举例
GET /ems1/emp/2
{
"query": {
"bool": {
"filter": {
"exists": {
"field": "name"
}
}
}
}
}
一般使用过滤都会加上bool查询
exists
:表示过滤存在属性name的文档,即如果文档没有name,就不查询filter后面也可以根据term…查询
10、java操作ElasticSearch
10.1、服务说明
将需要进行搜索的内容存放到es中,也就是说es存放的是大量可以进行搜索的索引库
某些内容的详细信息还是需要从数据库查
为什么不都存放在es中呢?
因为es的事务做的不好
10.2、java操作ES
1、导入依赖
es的相关依赖
注意:版本需和安装的es版本一致
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.8.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>6.8.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>transport-netty4-client</artifactId>
<version>6.8.0</version>
</dependency>
2、索引的操作
package com.huidu;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequestBuilder;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutionException;
public class TestIndexAndTypeMapping {
private TransportClient transportClient;
@Before
public void before() throws UnknownHostException {
//创建客户端
this.transportClient=new PreBuiltTransportClient(Settings.EMPTY);
//设置es服务地址
transportClient.addTransportAddress(new TransportAddress(InetAddress.getByName("123.56.226.106"),9300));
}
@After
public void after(){
transportClient.close();
}
//创建索引
@Test
public void testCreateIndex(){
//创建索引 需保证索引不存在
CreateIndexResponse dangdang = transportClient.admin().indices().prepareCreate("dangdang2").get();
//获取信息
System.out.println(dangdang.isAcknowledged());
}
//删除索引
@Test
public void testDeleteIndex(){
//删除索引
AcknowledgedResponse dangdang = transportClient.admin().indices().prepareDelete("dangdang2").get();
//获取信息
System.out.println(dangdang.isAcknowledged());
}
//创建索引,创建类型,创建mapping
@Test
public void testCreateIndexAndTypeMapping() throws ExecutionException, InterruptedException {
//创建索引对象
CreateIndexRequest dangdang = new CreateIndexRequest("dangdang");
//索引类型映射
//参数1:类型名 参数2:映射的json格式 参数3:映射格式类型
dangdang.mapping("book","{\"properties\":{\"id\":{\"type\":\"keyword\"},\"name\":{\"type\":\"text\",\"analyzer\":\"ik_max_word\"},\"content\":{\"type\":\"text\",\"analyzer\":\"ik_max_word\"},\"bir\":{\"type\":\"date\"}}}", XContentType.JSON);
//创建索引
CreateIndexResponse createIndexResponse = transportClient.admin().indices().create(dangdang).get();
System.out.println(createIndexResponse.isAcknowledged());
}
}
3、文档的操作
3.1、添加文档
- 创建一个实体类Book,属性类型和映射类型相同
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Book {
private String id;
private String content;
private Date bir;
private String name;
}
- 添加一条指定id的文档
@Test
public void createDoc(){
Book book=new Book(null,"我觉得java是世界上最好的语言",new Date(),"java入坟");
String bookJson = JSONObject.toJSONStringWithDateFormat(book,"yyyy-MM-dd");
IndexResponse indexResponse = transportClient.prepareIndex("dangdang", "book", "1").setSource(bookJson, XContentType.JSON).get();
System.out.println("插入是否成功"+indexResponse.status());
}
- 添加一条文档 自动添加id
@Test
public void createDocAuto(){
Book book=new Book(null,"我觉得java是世界上最好的语言-1",new Date(),"java入坟-1");
String bookJson = JSONObject.toJSONStringWithDateFormat(book,"yyyy-MM-dd");
IndexResponse indexResponse = transportClient.prepareIndex("dangdang", "book").setSource(bookJson, XContentType.JSON).get();
System.out.println("插入是否成功"+indexResponse.status());
3.2、更新一条文档
//更新一条文档
@Test
public void updateDoc(){
Book book=new Book();
book.setName("java入门").setContent("java从入门到入坟aaaa");
String bookJson = JSONObject.toJSONString(book);
UpdateResponse updateResponse = transportClient.prepareUpdate("dangdang", "book", "1").setDoc(bookJson, XContentType.JSON).get();
System.out.println(updateResponse.status());
}
3.3、删除一条文档
//删除一条文档
@Test
public void deleteDoc(){
DeleteResponse deleteResponse = transportClient.prepareDelete("dangdang", "book", "kMQLynQBebkfspAecwNI").get();
System.out.println(deleteResponse.status());
}
3.4、查询文档
查询一条文档
//查询一条文档
@Test
public void findOneDoc(){
GetResponse documentFields = transportClient.prepareGet("dangdang", "book", "1").get();
System.out.println(documentFields.getSourceAsString());
}
各种查询,查询所有
@Test
public void testQuery(){
//查询所有
MatchAllQueryBuilder matchAllQueryBuilder=QueryBuilders.matchAllQuery();
//termQuery
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("content", "最好");
//rangeQuery...
//调用方法
testResult(termQueryBuilder);
}
public void testResult(QueryBuilder queryBuilder){
SearchResponse searchResponse = transportClient.prepareSearch("dangdang")//索引
.setTypes("book")//类型
.setQuery(queryBuilder)//查询条件
.setFrom(2) //起始条数 默认是0,(当前页-1)*size
.setSize(5) //每页展示记录数
.addSort("bir", SortOrder.DESC) //年龄升序排序
.get();
System.out.println("总条数:"+ searchResponse.getHits().getTotalHits());
System.out.println("最大得分:"+searchResponse.getHits().getMaxScore());
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println("每一条记录:"+hit.getSourceAsString());
}
}
高亮查询
//高亮查询
@Test
public void highlight(){
//创建highlightBuilder对象
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("*")
.requireFieldMatch(false)
.preTags("<span style='color:red'>;")
.postTags("</span>");
SearchResponse searchResponse = transportClient.prepareSearch("dangdang")
.setTypes("book")
.setQuery(QueryBuilders.multiMatchQuery("java", "content", "name"))
.highlighter(highlightBuilder)
.get();
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
Book book=new Book();
//封装原始结果
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
book.setId(hit.getId());
book.setName(sourceAsMap.get("name").toString());
book.setContent(sourceAsMap.get("content").toString());
book.setBir(null);
//高亮处理
Map<String, HighlightField> highlightFieldMap=hit.getHighlightFields();
if(highlightFieldMap.containsKey("name")){
String highName = highlightFieldMap.get("name").fragments()[0].toString();
System.out.println(highName);
book.setName(highName);
}
if(highlightFieldMap.containsKey("content")){
String highContent = highlightFieldMap.get("content").fragments()[0].toString();
System.out.println(highContent);
book.setContent(highContent);
}
}
}
过滤查询
//过滤查询
@Test
public void FilterQuery(){
SearchResponse searchResponse = transportClient.prepareSearch("dangdang")//索引
.setTypes("book")//类型
.setPostFilter(QueryBuilders.termQuery("content", "java"))//查询过滤
.setQuery(QueryBuilders.matchAllQuery())//查询
.get();
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
3.5、批量操作
//批量操作
@Test
public void Bulk(){
//添加
Book book=new Book("3","python入门是一本好书",new Date(),"python入门");
IndexRequest source = new IndexRequest("dangdang", "book")
.source(JSONObject.toJSONStringWithDateFormat(book, "yyyy-MM-dd"), XContentType.JSON);
//删除
DeleteRequest deleteRequest = new DeleteRequest("dangdang","book","2");
//修改
Book book1=new Book();
book1.setContent("是真的很不错");
UpdateRequest updateRequest = new UpdateRequest("dangdang","book","3").doc(JSONObject.toJSONString(book1));
//返回批量更新对象
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
BulkResponse bulkItemResponses = bulkRequestBuilder
.add(source)
.add(deleteRequest)
.add(updateRequest)
.get();
BulkItemResponse[] items = bulkItemResponses.getItems();
for (BulkItemResponse item : items) {
System.out.println(item.status());
}
}
11、SpringBoot操作ElasticSearch
11.1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
11.2、去spring data官网将该配置复制下来
@Configuration
public class ElasticSearchRestClientConfig extends AbstractElasticsearchConfiguration {
//这个client用来替换 transportClient(9300)-->tcp对象
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("123.56.226.106:9200") //http
.build();
return RestClients.create(clientConfiguration).rest();
}
}
11.3、使用RestHighLevelClient对象操作es
文档删除
@Test
public void test() throws IOException {
DeleteRequest deleteRequest = new DeleteRequest("dangdang","book","1");
DeleteResponse delete = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(delete.status());
}
文档添加
@Test
public void test1() throws IOException {
IndexRequest indexRequest = new IndexRequest("dangdang","book","6");
indexRequest.source("{\"name\":\"小黑\",\"content\":\"小黑黑\"}", XContentType.JSON);
IndexResponse index = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
System.out.println(index.status());
}
高级搜索
@Test
public void test2() throws IOException {
SearchRequest searchRequest = new SearchRequest("dangdang");
//搜索构建对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery())
.from(0)
.size(5);
//.sort("");
//...
//创建搜索请求
searchRequest.types("book").source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
System.out.println("总条数:"+ searchResponse.getHits().getTotalHits());
System.out.println("最大得分:"+searchResponse.getHits().getMaxScore());
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println("每一条记录:"+hit.getSourceAsString());
}
}
11.4、使用Repository继承ElasticsearchRepository接口的方式
优点:可自动将对象转换成json,或者将查询出来的json封装成对象
缺点:只能进行较为简单的操作,复杂操作还是需要使用RestHighLevelClient
创建要创建的索引和类型对象的实体类
@Data
/**
* 用在类上 作用:将emp的对象映射成Es的一条json格式的文档
* indexName:用来指定这个对象的转为json文档存在索引
* type :这个索引下创建的类型名称
*/
@Document(indexName = "ems",type = "emp")
public class Emp {
@Id //使对象的id和文档的_id 一一对应
private String id;
//@Field:用在属性上 代表mapping 的一个字段 type:指定字段类型 analyzer:指定分词器
@Field(type = FieldType.Text,analyzer = "ik_max-word")
private String name;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Date)
@JsonFormat(pattern = "yyyy-MM-dd")
private Date bir;
@Field(type = FieldType.Text,analyzer = "ik_max-word")
private String content;
@Field(type = FieldType.Keyword)
private String address;
}
自定义EmpRepository接口继承ElasticsearchRepository接口
//自定义EmpRepository
public interface EmpRepository extends ElasticsearchRepository<Emp,String> {
}
使用该接口进行基本的增删改查
@SpringBootTest
public class TestEmpRepository {
@Autowired
private EmpRepository empRepository;
//保存|更新一条文档(setId为已经存在的id就是更新)
@Test
public void testSave(){
Emp emp = new Emp();
emp.setId(UUID.randomUUID().toString());
emp.setName("张三丰");
emp.setBir(new Date());
emp.setAddress("武当山学院");
emp.setAge(23);
emp.setContent("武当大师,一生创建多种武功,如太极,武当剑法");
empRepository.save(emp);
}
//删除一条文档deleteById
//删除所有deleteAll
//检索一条记录(自动封装为对象)
@Test
public void testFindOne(){
Optional<Emp> byId = empRepository.findById("c8e403cb-cb21-4c1f-9a74-2947c3470901");
System.out.println(byId.get());
}
//查询所有(排序)
@Test
public void testFindAll(){
Iterable<Emp> empRepositoryAll = empRepository.findAll(Sort.by(Sort.Order.asc("age")));
empRepositoryAll.forEach(System.out::println);
}
//分页
@Test
public void testPage(){
Page<Emp> search = empRepository.search(QueryBuilders.matchAllQuery(), PageRequest.of(0, 3));
search.forEach(System.out::println);
}
}
根据姓名 姓名和年龄 地址查询
在自定义的接口添加相应的方法,如果根据名字就findByName,根据名字和年龄findByNameAndAge…
//自定义EmpRepository
public interface EmpRepository extends ElasticsearchRepository<Emp,String> {
List<Emp> findByName(String name);
List<Emp> findByNameAndAge(String name,Integer age);
}
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And | findByNameAndPrice | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }} |
Or | findByNameOrPrice | { "query" : { "bool" : { "should" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }} |
Is | findByName | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }} |
Not | findByNameNot | { "query" : { "bool" : { "must_not" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }} |
Between | findByPriceBetween | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
LessThan | findByPriceLessThan | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } } ] } }} |
LessThanEqual | findByPriceLessThanEqual | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
GreaterThan | findByPriceGreaterThan | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } } ] } }} |
GreaterThanEqual | findByPriceGreaterThan | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }} |
Before | findByPriceBefore | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
After | findByPriceAfter | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }} |
Like | findByNameLike | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
StartingWith | findByNameStartingWith | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
EndingWith | findByNameEndingWith | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
Contains/Containing | findByNameContaining | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
In | findByNameIn(Collection<String>names) | { "query" : { "bool" : { "must" : [ {"bool" : {"must" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }} |
NotIn | findByNameNotIn(Collection<String>names) | { "query" : { "bool" : { "must" : [ {"bool" : {"must_not" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }} |
Near | findByStoreNear | Not Supported Yet ! |
True | findByAvailableTrue | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }} |
False | findByAvailableFalse | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "false", "fields" : [ "available" ] } } ] } }} |
OrderBy | findByAvailableTrueOrderByNameDesc | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }, "sort":[{"name":{"order":"desc"}}] } |
测试:
//根据姓名 姓名和年龄 地址查询
@Test
public void testFindByName(){
List<Emp> search = empRepository.findByName("张三丰");
search.forEach(System.out::println);
}
@Test
public void testFindByNameAndAge(){
List<Emp> search = empRepository.findByNameAndAge("张三丰",23);
search.forEach(System.out::println);
}
高亮、分页、排序等查询还是需要RestHighLevelClient
12、es中的集群
12.1、相关概念
集群 cluster
一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。
一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。一个节点只能通过指定某个集群的名字,来加入这个集群。
节点 node
一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,
在一个集群里,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
分片和复制 shards&replicas
每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。
分片重要的原因:
1)允许你水平分割/扩展你的内容容量。
2)允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。
Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。
复制重要的原因:
1)在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上。
2)扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行。(为了保证在分片/节点失败的情况下,数据不会查询不准确)
本笔记资料学习来自该视频