原文网址:Spring Data Elasticsearch--ElasticsearchRestTemplate--使用/用法/教程/实例_IT利刃出鞘的博客-CSDN博客
简介说明
本文用实例来介绍如何使用Spring Data Elasticsearch的ElasticsearchRestTemplate来操作ES。包括:索引的增删等、文档的增删改查、文档的动态查询(或者说:多条件查询、复杂查询)。
动态查询的含义:查询条件有多个时,某个查询条件可能有也可能没有,这时就需要手动判断,如果为空则不拼装搜索条件。
本文用博客的业务进行示例。索引名:blog,里边有标题、内容、作者、创建时间等。
官网
Elasticsearch Operations
为什么用ElasticsearchRestTemplate
在Spring Data Elasticsearch4.0之后,ElasticsearchRepository里边大部分搜索方法都已经被标记为废弃,注释中让我们用两种方法来操作:
- 在方法上使用@Query自定义DSL。
- 使用 ElasticsearchOperations来操作。
使用@Query自定义DSL不支持动态查询,其用法见:Spring Data Elasticsearch--入门/使用/用法/实例/教程/客户端--SpringBoot整合_IT利刃出鞘的博客-CSDN博客
ElasticsearchOperations支持动态查询,ElasticsearchRestTemplate是ElasticsearchOperations的实现类。
公共代码 依赖及配置依赖
主要是spring-boot-starter-data-elasticsearch这个依赖:
org.springframework.boot spring-boot-starter-data-elasticsearch
配置
spring:
elasticsearch:
rest:
uris: http://127.0.0.1:9200
# username: xxx
# password: yyy
# connection-timeout: 1
# read-timeout: 30
# 上边是客户端High Level REST Client的配置,推荐使用。
# 下边是reactive客户端的配置,非官方,不推荐。
# data:
# elasticsearch:
# client:
# reactive:
# endpoints: 127.0.0.1:9200
# username: xxx
# password: yyy
# connection-timeout: 1
# socket-timeout: 30
# use-ssl: false
整个pom.xml
索引结构及数据4.0.0 org.springframework.boot spring-boot-starter-parent2.3.12.RELEASE com.example demo_spring-data-elasticsearch0.0.1-SNAPSHOT demo_spring-data-elasticsearch demo_spring-data-elasticsearch 1.8 org.springframework.boot spring-boot-starter-weborg.projectlombok lomboktrue org.springframework.boot spring-boot-starter-data-elasticsearchorg.springframework.boot spring-boot-starter-testtest com.github.xiaoymin knife4j-spring-boot-starter3.0.3 org.springframework.boot spring-boot-maven-pluginorg.projectlombok lombok
索引结构
http://localhost:9200/
PUT blog
{
"mappings": {
"properties": {
"id":{
"type":"long"
},
"title": {
"type": "text"
},
"content": {
"type": "text"
},
"author":{
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"category":{
"type": "keyword"
},
"createTime": {
"type": "date",
"format":"yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd'T'HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss||epoch_millis"
},
"updateTime": {
"type": "date",
"format":"yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd'T'HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss||epoch_millis"
},
"status":{
"type":"integer"
},
"serialNum": {
"type": "keyword"
}
}
}
}
数据
- 每个文档必须独占一行,不能换行。
- 此命令要放到postman中去执行,如果用head执行会失败
http://localhost:9200/
POST _bulk
{"index":{"_index":"blog","_id":1}}
{"blogId":1,"title":"Spring Data ElasticSearch学习教程1","content":"这是批量添加的文档1","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"1","createTime":"2021-10-10 11:52:01.249","updateTime":null}
{"index":{"_index":"blog","_id":2}}
{"blogId":2,"title":"Spring Data ElasticSearch学习教程2","content":"这是批量添加的文档2","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"2","createTime":"2021-10-10 11:52:02.249","updateTime":null}
{"index":{"_index":"blog","_id":3}}
{"blogId":3,"title":"Spring Data ElasticSearch学习教程3","content":"这是批量添加的文档3","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"3","createTime":"2021-10-10 11:52:03.249","updateTime":null}
{"index":{"_index":"blog","_id":4}}
{"blogId":4,"title":"Spring Data ElasticSearch学习教程4","content":"这是批量添加的文档4","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"4","createTime":"2021-10-10 11:52:04.249","updateTime":null}
{"index":{"_index":"blog","_id":5}}
{"blogId":5,"title":"Spring Data ElasticSearch学习教程5","content":"这是批量添加的文档5","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"5","createTime":"2021-10-10 11:52:05.249","updateTime":null}
{"index":{"_index":"blog","_id":6}}
{"blogId":6,"title":"Java学习教程6","content":"这是批量添加的文档6","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"6","createTime":"2021-10-10 11:52:06.249","updateTime":null}
{"index":{"_index":"blog","_id":7}}
{"blogId":7,"title":"Java学习教程7","content":"这是批量添加的文档7","author":"Pepper","category":"ElasticSearch","status":1,"serialNum":"7","createTime":"2021-10-10 11:52:07.249","updateTime":null}
{"index":{"_index":"blog","_id":8}}
{"blogId":8,"title":"Java学习教程8","content":"这是批量添加的文档8","author":"Pepper","category":"ElasticSearch","status":1,"serialNum":"8","createTime":"2021-10-10 11:52:08.249","updateTime":null}
{"index":{"_index":"blog","_id":9}}
{"blogId":9,"title":"Java学习教程9","content":"这是批量添加的文档9","author":"Pepper","category":"ElasticSearch","status":1,"serialNum":"9","createTime":"2021-10-10 11:52:09.249","updateTime":null}
{"index":{"_index":"blog","_id":10}}
{"blogId":10,"title":"Java学习教程10","content":"这是批量添加的文档10","author":"Pepper","category":"ElasticSearch","status":1,"serialNum":"10","createTime":"2021-10-10 11:52:10.249","updateTime":null}
索引操作(IndexOperations)
本处只是展示索引的操作方法,项目中不会这样创建索引,而是手写DSL去创建。
package com.example.demo.controller;
import com.example.demo.entity.Blog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "索引操作(用不到)")
@RestController
@RequestMapping("indexViaRestTemplate")
public class IndexViaRestTemplateController {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@ApiOperation("创建索引")
@PostMapping("createIndex")
public String createIndex() {
// 创建索引,会根据Blog类的@document注解信息来创建
elasticsearchRestTemplate.createIndex(Blog.class);
// 配置映射,会根据Item类中的id、Field等字段来自动完成映射
elasticsearchRestTemplate.putMapping(Blog.class);
return "success";
}
@ApiOperation("创建索引")
@PostMapping("deleteIndex")
public String deleteIndex() {
// 删除索引,会根据Blog类的@document注解信息来删除
elasticsearchRestTemplate.deleteIndex(Blog.class);
return "success";
}
}
文档操作(documentOperations)
本处介绍常用的一些方法,这些方法能够满足开发中所有需求:
- 添加单个文档
- 添加多个文档
- 修改单个文档数据
- 修改单个文档部分数据
- 修改多个文档部分数据
- 查看单个文档
- 删除单个文档(根据id)
- 删除单个文档(根据条件)
- 删除所有文档。
package com.example.demo.controller;
import com.example.demo.entity.Blog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.document.document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Api(tags = "增删改查(RestTemplate方式)")
@RestController
@RequestMapping("crudViaRestTemplate")
public class CrudViaRestTemplateController {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@ApiOperation("添加单个文档")
@PostMapping("adddocument")
public Blog adddocument() {
Long id = 1L;
Blog blog = new Blog();
blog.setBlogId(id);
blog.setTitle("Spring Data ElasticSearch学习教程" + id);
blog.setContent("这是添加单个文档的实例" + id);
blog.setAuthor("Tony");
blog.setCategory("ElasticSearch");
blog.setCreateTime(new Date());
blog.setStatus(1);
blog.setSerialNum(id.toString());
return elasticsearchRestTemplate.save(blog);
}
@ApiOperation("添加多个文档")
@PostMapping("adddocuments")
public Object adddocuments(Integer count) {
List blogs = new ArrayList<>();
for (int i = 1; i <= count; i++) {
Long id = (long)i;
Blog blog = new Blog();
blog.setBlogId(id);
blog.setTitle("Spring Data ElasticSearch学习教程" + id);
blog.setContent("这是添加单个文档的实例" + id);
blog.setAuthor("Tony");
blog.setCategory("ElasticSearch");
blog.setCreateTime(new Date());
blog.setStatus(1);
blog.setSerialNum(id.toString());
blogs.add(blog);
}
return elasticsearchRestTemplate.save(blogs);
}
@ApiOperation("修改单个文档数据")
@PostMapping("editdocument")
public Blog editdocument() {
Long id = 1L;
Blog blog = new Blog();
blog.setBlogId(id);
blog.setTitle("Spring Data ElasticSearch学习教程" + id);
blog.setContent("这是修改单个文档的实例" + id);
return elasticsearchRestTemplate.save(blog);
}
@ApiOperation("修改单个文档部分数据")
@PostMapping("editdocumentPart")
public UpdateResponse editdocumentPart() {
long id = 1L;
document document = document.create();
document.put("title", "修改后的标题" + id);
document.put("content", "修改后的内容" + id);
UpdateQuery updateQuery = UpdateQuery.builder(Long.toString(id))
.withdocument(document)
.build();
UpdateResponse response = elasticsearchRestTemplate.update(updateQuery, IndexCoordinates.of("blog"));
return response;
}
@ApiOperation("修改多个文档部分数据")
@PostMapping("editdocumentsPart")
public void editdocumentsPart(int count) {
List updateQueryList = new ArrayList<>();
for (int i = 1; i <= count; i++) {
long id = (long) i;
document document = document.create();
document.put("title", "修改后的标题" + id);
document.put("content", "修改后的内容" + id);
UpdateQuery updateQuery = UpdateQuery.builder(Long.toString(id))
.withdocument(document)
.build();
updateQueryList.add(updateQuery);
}
elasticsearchRestTemplate.bulkUpdate(updateQueryList, IndexCoordinates.of("blog"));
}
@ApiOperation("查看单个文档")
@GetMapping("findById")
public Blog findById(Long id) {
return elasticsearchRestTemplate.get(
id.toString(), Blog.class, IndexCoordinates.of("blog"));
}
@ApiOperation("删除单个文档(根据id)")
@PostMapping("deletedocumentById")
public String deletedocumentById(Long id) {
return elasticsearchRestTemplate.delete(id.toString(), Blog.class);
}
@ApiOperation("删除单个文档(根据条件)")
@PostMapping("deletedocumentByQuery")
public void deletedocumentByQuery(String title) {
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("title", title))
.build();
elasticsearchRestTemplate.delete(nativeSearchQuery, Blog.class, IndexCoordinates.of("blog"));
}
@ApiOperation("删除所有文档")
@PostMapping("deletedocumentAll")
public void deletedocumentAll() {
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchAllQuery())
.build();
elasticsearchRestTemplate.delete(nativeSearchQuery, Blog.class, IndexCoordinates.of("blog"));
}
}
本处就不贴出测试的截图了。功能都是测试通过的。
查询操作(SearchOperations) 简介查询的方法
Query接口有一个抽象实现和三个实现:
本处我使用NativeSearchQuery。因为它更贴近ES,语法更偏向于ES原来的命令。CriteriaQuery的用法跟JPA的差不多。
构建Query
可通过new NativeSearchQueryBuilder()来构建NativeSearchQuery对象NativeSearchQuery中有众多的方法来为我们实现复杂的查询与筛选等操作。其中的build()返回NativeSearchQuery。
QueryBuilders构造复杂查询条件
NativeSearchQueryBuilder中接收QueryBuilder
public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) {
this.queryBuilder = queryBuilder;
return this;
}
可以用QueryBuilders构造QueryBuilder对象
基本查询通过标题和内容来查找博客。
package com.example.demo.controller;
import com.example.demo.dao.BlogRepository;
import com.example.demo.entity.Blog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.ParsedAvg;
import org.elasticsearch.search.aggregations.metrics.ParsedMax;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Range;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
import java.util.stream.Collectors;
@Api(tags = "动态查询")
@RestController
@RequestMapping("dynamicQuery")
public class DynamicQueryController {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@ApiOperation("简单查询")
@GetMapping("simple")
public List simple(String title, String content) {
NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (StringUtils.isNotBlank(title)) {
boolQueryBuilder.must(QueryBuilders.matchQuery("title", title));
}
if (StringUtils.isNotBlank(content)) {
boolQueryBuilder.must(QueryBuilders.matchQuery("content", content));
}
query.withQuery(boolQueryBuilder);
SearchHits searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class);
List blogs = new ArrayList<>();
for (SearchHit searchHit : searchHits) {
blogs.add(searchHit.getContent());
}
return blogs;
}
}
测试
分页排序通过标题和内容来查找博客,分页,并根据创建时间倒序排序。
@ApiOperation("分页排序")
@GetMapping("pageAndSort")
public Page pageAndSort(String title, String content) {
PageRequest pageRequest = PageRequest.of(0, 2);
NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (StringUtils.isNotBlank(title)) {
boolQueryBuilder.must(QueryBuilders.matchQuery("title", title));
}
if (StringUtils.isNotBlank(content)) {
boolQueryBuilder.must(QueryBuilders.matchQuery("content", content));
}
query.withQuery(boolQueryBuilder);
query.withPageable(pageRequest);
query.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
SearchHits searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class);
List blogs = new ArrayList<>();
for (SearchHit searchHit : searchHits) {
blogs.add(searchHit.getContent());
}
return new PageImpl(blogs, pageRequest, searchHits.getTotalHits());
}
测试
去重通过标题和内容来查找博客,根据作者去重。
@ApiOperation("去重")
@GetMapping("collapse")
public List collapse(String title, String content) {
PageRequest pageRequest = PageRequest.of(0, 2);
NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (StringUtils.isNotBlank(title)) {
boolQueryBuilder.must(QueryBuilders.matchQuery("title", title));
}
if (StringUtils.isNotBlank(content)) {
boolQueryBuilder.must(QueryBuilders.matchQuery("content", content));
}
query.withQuery(boolQueryBuilder);
query.withPageable(pageRequest);
query.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
// 去重的字段不能是text类型。所以,author的mapping要有keyword,且通过author.keyword聚合。
query.withCollapseField("author.keyword");
//query.withCollapseField("category");
SearchHits searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class);
List blogs = new ArrayList<>();
for (SearchHit searchHit : searchHits) {
blogs.add(searchHit.getContent());
}
return blogs;
}
测试
聚合查询标题中带有“java”的每个作者的文章数量。
@ApiOperation("聚合")
@GetMapping("aggregation")
public Map aggregation() {
NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("title", "java"));
query.withQuery(boolQueryBuilder);
// 作为聚合的字段不能是text类型。所以,author的mapping要有keyword,且通过author.keyword聚合。
query.addAggregation(AggregationBuilders.terms("per_count").field("author.keyword"));
// 不需要获取source结果集,在aggregation里可以获取结果
query.withSourceFilter(new FetchSourceFilterBuilder().build());
SearchHits searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class);
Aggregations aggregations = searchHits.getAggregations();
assert aggregations != null;
//因为结果为字符串类型 所以用ParsedStringTerms。其他还有ParsedLongTerms、ParsedDoubleTerms等
ParsedStringTerms per_count = aggregations.get("per_count");
Map map = new HashMap<>();
for (Terms.Bucket bucket : per_count.getBuckets()) {
map.put(bucket.getKeyAsString(), bucket.getDocCount());
}
return map;
}
测试
嵌套聚合查询标题中带有“java”的每个作者的文章数量,再查出各个作者的最新的发文时间。
@ApiOperation("嵌套聚合")
@GetMapping("subAggregation")
public Map> subAggregation() {
NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("title", "java"));
query.withQuery(boolQueryBuilder);
query.addAggregation(AggregationBuilders.terms("per_count").field("author.keyword")
.subAggregation(AggregationBuilders.max("latest_create_time").field("createTime"))
);
// 不需要获取source结果集,在aggregation里可以获取结果
query.withSourceFilter(new FetchSourceFilterBuilder().build());
SearchHits searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class);
Aggregations aggregations = searchHits.getAggregations();
assert aggregations != null;
//因为结果为字符串类型 所以用ParsedStringTerms。其他还有ParsedLongTerms、ParsedDoubleTerms等
ParsedStringTerms per_count = aggregations.get("per_count");
Map> map = new HashMap<>();
for (Terms.Bucket bucket : per_count.getBuckets()) {
Map objectMap = new HashMap<>();
objectMap.put("docCount", bucket.getDocCount());
ParsedMax parsedMax = bucket.getAggregations().get("latest_create_time");
objectMap.put("latestCreateTime", parsedMax.getValueAsString());
map.put(bucket.getKeyAsString(), objectMap);
}
return map;
}
测试
其他网址自己挖坑自己填 spring-data-elasticsearch 4.0.0.M4 简单实践_lixiang19971019的博客-CSDN博客
Elasticsearch 整合 Spring Boot(4) - 简书
Elasticsearch 聚合搜索报错:mapping 没有keyword 解决_sweetCandy_m的博客-CSDN博客



