1 Elasticsearch安装与运行
- 安装
进入官网下载并解压即可
运行
sh /Users/cuiyichen/SpringCloudComponents/elasticsearch-7.16.2/bin/elasticsearch
- 运行后,9300端口为Elasticsearch集群间组件的通信端口,9200端口为浏览器访问的http协议RESTful端口。
- 可以访问地址http://localhost:9200来测试Elasticsearch是否启动成功,成功将返回如下结果
{ "name" : "cuiyichendeMacBook-Pro.local", "cluster_name" : "elasticsearch", "cluster_uuid" : "OebuWdYpRp2UPvKh73jy4Q", "version" : { "number" : "7.16.2", "build_flavor" : "default", "build_type" : "tar", "build_hash" : "2b937c44140b6559905130a8650c64dbd0879cfb", "build_date" : "2021-12-18T19:42:46.604893745Z", "build_snapshot" : false, "lucene_version" : "8.10.1", "minimum_wire_compatibility_version" : "6.8.0", "minimum_index_compatibility_version" : "6.0.0-beta1" }, "tagline" : "You Know, for Search" }
2 Elasticsearch基本操作
1 索引操作
1 创建索引
创建索引,对比MySQL就是创建表
在ES地址后直接追加的路径即为索引名,当以PUT方式请求时就是新建索引
localhost:9200/shopping
- 上述请求即为新建一个名为shopping的索引
2 查看索引
1 查看单个索引
在ES地址后直接追加的路径即为索引名,当以GET方式请求时就是查看指定索引
localhost:9200/shopping
- 上述请求的返回值如下
{ "shopping": { "aliases": {}, "mappings": {}, "settings": { "index": { "routing": { "allocation": { "include": { "_tier_preference": "data_content" } } }, "number_of_shards": "1", "provided_name": "shopping", "creation_date": "1640247662704", "number_of_replicas": "1", "uuid": "hzkgH--QQq2VbL5I0k9TpQ", "version": { "created": "7160299" } } } } }
- 上述请求的返回值如下
2 查看所有索引
查看所有索引的请求路径为
/_cat/indices
,请求方式为GETlocalhost:9200/_cat/indices?v
- 追加参数v用于显示表头
- 上述请求的返回值如下
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size green open .geoip_databases 4stg-s4XQ7yW1GDwtsDnlw 1 0 43 0 40.8mb 40.8mb yellow open shopping hzkgH--QQq2VbL5I0k9TpQ 1 1 0 0 226b 226b
3 删除索引
- 删除索引和新建索引以及查看单个索引一样,不同的是请求方式为DELETE
localhost:9200/shopping
2 文档操作
1 创建文档
索引已经建好了,现在需要向索引中添加文档。这里的文档可以类比为MySQL中的行
添加文档的请求方式为POST,请求路径为_doc,表示创建的是文档数据,当指定的索引不存在时将自动创建索引,http请求格式如下
localhost:9200/shopping/_doc
Request body如下
{ "title": "小米手机", "category": "手机", "image": "http//qiniuyu.xxx", "price": 3999.00 }
Response body如下
{ "_index": "shopping", "_type": "_doc", "_id": "tI6H5n0Bdgb4LMWkNTNi", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 0, "_primary_term": 1 }
- 其中_id是ES随机生成的文档主键id,该id唯一性标识文档,也可以用于正排索引来查询文档
- 如果不希望使用ES随机生成的id,则可以在_doc路径后追加指定的id值
2 修改文档
1 覆盖
- 覆盖文档操作和创建文档操作的请求路径都是_doc,只是查询方式需要变为PUT然后在_doc路径后追加文档id,还需要在Request body中添加新的文档数据
localhost:9200/shopping/_doc/tI6H5n0Bdgb4LMWkNTNi
2 局部更新
局部更新使用的路径是_update,并且更新的值需要放在doc中
localhost:9200/shopping/_update/tI6H5n0Bdgb4LMWkNTNi
- Request body
{ "doc": { "title": "黑莓手机" } }
- Request body
3 查询文档
1 查询单个文档
查询单个文档需要通过文档的id(即正排索引)来查询,在_doc路径后追加id并采用GET方式发起请求
localhost:9200/shopping/_doc/tI6H5n0Bdgb4LMWkNTNi
- Response body如下
{ "_index": "shopping", "_type": "_doc", "_id": "tI6H5n0Bdgb4LMWkNTNi", "_version": 1, "_seq_no": 0, "_primary_term": 1, "found": true, "_source": { "title": "小米手机", "category": "手机", "image": "http//qiniuyu.xxx", "price": 3999.00 } }
- Response body如下
2 查询全部文档
- 查询某个索引的全部文档则需要使用_search来代替_doc,同样需要使用GET方式
localhost:9200/shopping/_search
3 条件查询
- 条件查询的请求方式和请求路径与查询全部文档相同,不同的是需要在body中添加查询条件
如以下查询条件
{
"query":{
"match":{
"title":"小米黑"
}
}
}
- 上述查询的返回结果如下
{ "took": 4, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 1.7509375, "hits": [ { "_index": "shopping", "_type": "_doc", "_id": "tY6i5n0Bdgb4LMWkNTPQ", "_score": 1.7509375, "_source": { "title": "小米手机", "category": "手机", "image": "http//qiniuyu.xxx", "price": 3999.00 } }, { "_index": "shopping", "_type": "_doc", "_id": "to6i5n0Bdgb4LMWkOTOK", "_score": 1.7509375, "_source": { "title": "小米手机", "category": "手机", "image": "http//qiniuyu.xxx", "price": 3999.00 } }, { "_index": "shopping", "_type": "_doc", "_id": "tI6H5n0Bdgb4LMWkNTNi", "_score": 0.87546873, "_source": { "title": "黑莓手机", "category": "手机", "image": "http//qiniuyu.xxx", "price": 9999.0 } } ] } }
match匹配 - 对查询条件进行分词
- 从上述查询中可以发现,虽然查询条件是“小米黑”,但是title中包含“小米”和“黑”都判断为匹配成功
- 这是因为以match方式进行匹配时,匹配条件将被执行分词(对于汉字来说一个字就是一个词),底层进行匹配时将使用多个关键字分别进行倒排索引匹配
- 在返回的一个个查询结果中还包含一个score属性,该属性表示与查询条件的关联性得分
term匹配 - 不对查询条件进行分词
- 该方式的匹配则不进行分词,直接使用查询条件作为关键词来执行匹配
- 由于索引分词时默认的粒度为一个单词,因此term匹配的查询条件不能包括多个单词,否则查询一定失败,除非改动分词粒度
3 创建映射
采用PUT方式,在索引名后追加_mapping路径,并在body中添加映射设置
请求格式
localhost:9200/shopping/_mapping
Request body
{ "properties": { "title": { "type": "keyword", "index": true }, "category": { "type": "text", "index": true }, "image": { "type": "text", "index": false }, "price": { "type": "double", "index": true } } }
3 Java操作Elasticsearch
1 maven依赖
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.6.2</version>
</dependency>
<!-- elasticsearch 的客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.2</version>
</dependency>
2 索引操作
@SpringBootTest
@Slf4j
class IndicesTest {
@Autowired
RestHighLevelClient esClient;
@Test
void indexCreate() throws IOException {
// 创建索引的请求对象
CreateIndexRequest request = new CreateIndexRequest("student");
// 发送请求,接收响应
CreateIndexResponse response = esClient.indices().create(request, RequestOptions.DEFAULT);
log.info("响应状态:{}", response.isAcknowledged());
}
@Test
void indexGet() throws IOException {
GetIndexRequest request = new GetIndexRequest("shopping");
GetIndexResponse response = esClient.indices().get(request, RequestOptions.DEFAULT);
log.info("settings: {}", response.getSettings());
}
@Test
void indexDelete() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("student");
AcknowledgedResponse response = esClient.indices().delete(request, RequestOptions.DEFAULT);
log.info("响应状态: {}", response.isAcknowledged());
}
}
3 文档操作
@SpringBootTest
@Slf4j
class DocTest {
@Autowired
RestHighLevelClient esClient;
/**
* 插入单个文档
*/
@Test
void docInsert() throws IOException {
// 要插入到索引中的数据对象
Student student = new Student("许晴", "女", 53);
// 将对象转为json格式
ObjectMapper mapper = new ObjectMapper();
String studentJson = mapper.writeValueAsString(student);
IndexRequest request = new IndexRequest();
// 设置索引名
request.index("student");
// 设置文档id
request.id("1009");
// 设置数据源
request.source(studentJson, XContentType.JSON);
IndexResponse response = esClient.index(request, RequestOptions.DEFAULT);
log.info("请求结果: {}", response.getResult());
}
/**
* 批量插入文档
*/
@Test
void docBulkInsert() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Student student1 = new Student("崔伟", "男", 52);
Student student2 = new Student("崔红", "女", 54);
Student student3 = new Student("艾雪珂", "女", 23);
BulkRequest request = new BulkRequest();
request.add(new IndexRequest().index("student").id("1006")
.source(mapper.writeValueAsString(student1), XContentType.JSON));
request.add(new IndexRequest().index("student").id("1007")
.source(mapper.writeValueAsString(student2), XContentType.JSON));
request.add(new IndexRequest().index("student").id("1008")
.source(mapper.writeValueAsString(student3), XContentType.JSON));
BulkResponse response = esClient.bulk(request, RequestOptions.DEFAULT);
log.info("失败信息: {}", Arrays.toString(Arrays.stream(response.getItems()).map(o -> o.getFailureMessage()).toArray(String[]::new)));
}
/**
* 根据文档id获取文档
*/
@Test
void docGet() throws IOException {
GetRequest request = new GetRequest();
request.index("student");
request.id("1001");
GetResponse response = esClient.get(request, RequestOptions.DEFAULT);
log.info("文档详情: {}", response.getSourceAsString());
}
/**
* 全量搜索
*/
@Test
void docSearchAll() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("shopping", "student");
// A query that matches on all documents.
QueryBuilder query = QueryBuilders.matchAllQuery();
request.source(new SearchSourceBuilder().query(query));
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
log.info("命中数量: {}", hits.getTotalHits());
for (SearchHit hit : hits) {
log.info(hit.getSourceAsString());
}
}
/**
* 分页全量搜索
*/
@Test
void docPageSearchAll() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("shopping", "student");
QueryBuilder query = QueryBuilders.matchAllQuery();
// 这里进行了一个分页
request.source(new SearchSourceBuilder().query(query).from(0).size(2));
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
log.info("命中数量: {}", hits.getTotalHits());
for (SearchHit hit : hits) {
log.info(hit.getSourceAsString());
}
}
/**
* 全量搜索,但是是搜索结果只要name
*/
@Test
void docSearchAllNames() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("shopping", "student");
QueryBuilder query = QueryBuilders.matchAllQuery();
// 这里进行了include和exclude设置
String[] includes = {"name"};
String[] excludes = {};
request.source(new SearchSourceBuilder().query(query).fetchSource(includes, excludes));
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
log.info("命中数量: {}", hits.getTotalHits());
for (SearchHit hit : hits) {
log.info(hit.getSourceAsString());
}
}
/**
* match搜索/分词搜索
*/
@Test
void docSearchByMatch() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("student");
// Creates a match query with type "BOOLEAN" for the provided field name and text
QueryBuilder query = QueryBuilders.matchQuery("name", "崔奕宸");
request.source(new SearchSourceBuilder().query(query));
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
log.info("命中数量: {}", hits.getTotalHits());
for (SearchHit hit : hits) {
log.info(hit.getSourceAsString());
}
}
/**
* term搜索/不分词搜索
* (如果term值为多个单词则搜索失败,因为termQuery不会分词,而且索引默认分词粒度为一个单词)
*/
@Test
void docSearchByTerm() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("student");
// A Query that matches documents containing a term.
QueryBuilder query = QueryBuilders.termQuery("name", "宸");
request.source(new SearchSourceBuilder().query(query));
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
log.info("命中数量: {}", hits.getTotalHits());
for (SearchHit hit : hits) {
log.info(hit.getSourceAsString());
}
}
/**
* 范围查询
*/
@Test
void docRangeSearch() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("student");
// A Query that matches documents within an range of terms.
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("age");
// 大于等于 greater than or equal, gte
rangeQuery.gte(20);
// 小于等于 lower than or equal, lte
rangeQuery.lte(40);
request.source(new SearchSourceBuilder().query(rangeQuery));
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
log.info("命中数量: {}", hits.getTotalHits());
for (SearchHit hit : hits) {
log.info(hit.getSourceAsString());
}
}
/**
* 组合查询,可以组合多种match搜索、term搜索等
*/
@Test
void docSearchByCondition() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("student");
// A Query that matches documents matching boolean combinations of other queries.
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.matchQuery("name", "崔陈邱艾常许"));
boolQuery.must(QueryBuilders.termQuery("sex", "女"));
boolQuery.must(QueryBuilders.rangeQuery("age").lte(30));
boolQuery.mustNot(QueryBuilders.termQuery("name", "红"));
request.source(new SearchSourceBuilder().query(boolQuery));
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
log.info("命中数量: {}", hits.getTotalHits());
for (SearchHit hit : hits) {
log.info(hit.getSourceAsString());
}
}
}