springboot整合Elasticsearch7.8
es基础知识
依赖
使用springboot2.4版本
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置信息
根据springboot自动配置原理,查看elasticsearch的自动配置类
查看ElasticsearchRestClientAutoConfiguration
查看ElasticsearchRestClientProperties
根据这里面的属性配置相关数据。由于在新版的 spring-data-elasticsearch 中ElasticsearchRestTemplate 代替了原来的ElasticsearchTemplate。因此本次不采用自动配置方式。使用自定义配置方式
自定义配置类
ElasticsearchRestTemplate 是 spring-data-elasticsearch 项目中的一个类,和其他 spring 项目中的 template类似。在新版的 spring-data-elasticsearch 中,ElasticsearchRestTemplate 代替了原来的ElasticsearchTemplate。
原因是 ElasticsearchTemplate 基于 TransportClient,TransportClient 即将在 8.x 以后的版本中移除。所以,我们推荐使用 ElasticsearchRestTemplate。
ElasticsearchRestTemplate 基 于 RestHighLevelClient 客户端的。需要自定义配置类,继承
AbstractElasticsearchConfiguration,并实现 elasticsearchClient()抽象方法,创建 RestHighLevelClient对象。
配置类
package com.conformity.elastcsearch;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
@Configuration
@PropertySource(value = {"classpath:es.properties"},encoding = "utf-8")
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration{
@Value("${elasticsearch.address}")
private String address;
@Override
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo(address)
.build();
return RestClients.create(clientConfiguration).rest();
}
}
配置文件
es.properties
elasticsearch.address=192.168.3.118:9200
application.ynl
logging:
level:
# 打印es日志
org.springframework.data.elasticsearch.client.WIRE : trace
es实体映射关系
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Document(indexName = "shopping", shards = 3, replicas = 1)
public class Product {
//必须有 id,这里的 id 是全局唯一的标识,等同于 es 中的"_id"
@Id
private Long id;//商品唯一标识
/**
* type : 字段数据类型
* analyzer : 分词器类型
* index : 是否索引(默认:true)
* Keyword : 短语,不进行分词
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title;//商品名称
@Field(type = FieldType.Keyword)
private String category;//分类名称
@Field(type = FieldType.Double)
private Double price;//商品价格
@Field(type = FieldType.Keyword, index = false)
private String images;//图片地址
}
启动springboot,通过kibana访问,获取到shopping索引
CURD操作
kibana和postman访问
kibana访问的路径相当于postman访问中Ip:port 后面的路径
kibana访问的参数相当于postma json格式的参数
#获取所有索引状态
get /_cat/indices?v
#创建索引,分片,副本
put /user/
{
"settings":{
"number_of_shards":3,
"number_of_replicas":1
}
}
#为索引设置映射
PUT /user/_mapping
{
"properties":{
"name":{
"type":"text",
"index":true,
"analyzer" : "ik_max_word"
},
"sex":{
"type":"keyword",
"index":true
},
"tel":{
"type":"keyword",
"index":false
}
}
}
#获取索引信息
GET /user/
#删除索引
DELETE /user
#创建文档
POST /user/_doc/
{
"name":"张三",
"sex":"男",
"tel":"111111111"
}
#创建文档带有id
POST /user/_doc/1
{
"name":"zhangsan",
"sex":"男",
"tel":"111111111"
}
#根据id获取文档信息
GET /user/_doc/2
#根据id修改文档
POST /user/_doc/2
{
"name":"关羽",
"sex":"男",
"tel":"111111111"
}
#根据id删除文档
DELETE /user/_doc/2
#分页展示全部信息
#from:当前页的起始索引,默认从 0 开始。 from = (pageNum - 1) * size
#size:每页显示多少条
#查询类型,例如:match_all(代表查询所有), match,term ,range 等等
GET /shopping/_search
{
"query": {
"match_all": {}
},
"from":0,
"size":10
}
#分页,排序,返回指定字段
GET /shopping/_search
{
"query": {
"match_all": {}
},
"from":10,
"size":10,
"_source":["title","id","category","price"],
"sort":{
"id":{
"order":"asc"
}
}
}
#匹配查询
#match 匹配类型查询,会把查询条件进行分词,然后进行查询
GET /shopping/_search
{
"query":{
"match": {
"title": "小米夹"
}
}
}
#multi_match可以在多个字段中查询
GET /shopping/_search
{
"query":{
"multi_match": {
"query": "小米夹",
"fields": ["title","category"]
}
}
}
#term 查询 精确的关键词匹配查询,不对查询条件进行分词
GET /shopping/_search
{
"query":{
"term": {
"title": {
"value": "小米夹"
}
}
}
}
#terms 查询和 term查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件,类似于 mysql 的 in
GET /shopping/_search
{
"query":{
"terms": {
"title": ["小米夹","小米"]
}
}
}
#指定查询哪些字段
#"_source":["title","id","category","price"],
#过滤字段
#includes:来指定想要显示的字段
#excludes:来指定不想要显示的字段
GET /shopping/_search
{
"query":{
"terms": {
"title": ["小米夹","小米"]
}
},
"_source": ["title"]
}
GET /shopping/_search
{
"query":{
"terms": {
"title": ["小米夹","小米"]
}
},
"_source": {
"includes": "price"
}
}
GET /shopping/_search
{
"query":{
"terms": {
"title": ["小米夹","小米"]
}
},
"_source": {
"excludes": "price"
}
}
#多条件组合查询
#`bool`把各种其它查询通过must、must_not、should的方式进行组合
#must:必须 相当于 与
#must_not:必须不是 相当于 must的相反数据
#shoule: 应该 相当于 或
#查询名称包含小米加分词的数据 并且价格等于4399
GET /shopping/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "小米加"
}
},
{
"match": {
"price": "4399"
}
}
]
}
}
}
#查询名称包含小米加分词的数据 并且价格不等于4399
GET /shopping/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "小米加"
}
}
],
"must_not": [
{
"match": {
"price": "4399"
}
}
]
}
}
}
GET /shopping/_search
{
"query": {
"bool": {
"should": [
{
"term":{
"category":"手机"
}
}
]
}
}
}
#范围查询 range查询找出那些落在指定区间内的数字或者时间。range 查询允许以下字符
#gt 大于>
#gte 大于等于>=
#lt 小于<
#lte 小于等于<=
GET /shopping/_search
{
"query": {
"range": {
"price": {
"gte": 4399,
"lte": 4500
}
}
}
}
#模糊查询 返回包含与搜索字词相似的字词的文档
#搜索zhangs 可以搜索到zhangsan
GET /user/_search
{
"query": {
"fuzzy": {
"name": {
"value": "zhangs"
}
}
}
}
#多字段排序
GET /shopping/_search
{
"query": {
"range": {
"price": {
"gte": 4399,
"lte": 4500
}
}
},
"sort": [
{
"id": {
"order": "asc"
}
},
{
"price": {
"order": "desc"
}
}
]
}
#高亮查询
#Elasticsearch可以对查询内容中的关键字部分,进行标签和样式(高亮)的设置。
#在使用 match或fuzzy 查询的同时,加上一个 highlight 属性:
#pre_tags:前置标签
#post_tags:后置标签
#fields:需要高亮的字段
GET /user/_search
{
"query": {
"match": {
"name":"zhangsan"
}
},
"highlight": {
"pre_tags": "<font color='red'>",
"post_tags":"</font>",
"fields": {
"name": {}
}
},
"_source": "{highlight}"
}
GET /user/_search
{
"query": {
"fuzzy": {
"name":"zangsan"
}
},
"highlight": {
"pre_tags": "<font color='red'>",
"post_tags":"</font>",
"fields": {
"name": {}
}
},
"_source": "{highlight}"
}
#聚合查询
#聚合允许使用者对es文档进行统计分析,类似与关系型数库中的 group by,当然还有很多其他的聚合,例如取最大值、平均值等等。
#最大值
GET shopping/_search
{
"aggs": {
"price_max": {
"max": {
"field": "price"
}
}
},
"size": 0
}
#最小值
GET shopping/_search
{
"aggs": {
"price_min": {
"min": {
"field": "price"
}
}
},
"size": 0
}
#平均值
GET shopping/_search
{
"aggs": {
"price_avg": {
"avg": {
"field": "price"
}
}
},
"size": 0
}
#去重后总数
GET shopping/_search
{
"aggs": {
"distinct_age": {
"cardinality": {
"field": "price"
}
}
},
"size": 0
}
#求和
GET shopping/_search
{
"aggs": {
"sum_price": {
"sum": {
"field": "price"
}
}
},
"size": 0
}
#State 聚合 对某个字段一次性返回 count,max,min,avg 和 sum 五个指标
GET shopping/_search
{
"aggs": {
"state_price": {
"stats": {
"field": "price"
}
}
},
"size": 0
}
#terms 聚合,分组统计 相当于group by
GET shopping/_search
{
"aggs": {
"terms_title": {
"terms": {
"field": "category"
}
}
},
"size": 0
}
springboot客户端操作
使用spring-data-elastsearch操作,具体可查看spring官网
以下全部使用ElasticsearchRestTemplate进行对elasticsearch的操作
- ElasticsearchRestTemplate继承AbstractElasticsearchTemplate,AbstractElasticsearchTemplate实现ElasticsearchOperations,因此ElasticsearchRestTemplate具有DocumentOperations和SearchOperations的功能。
索引操作,文档插入新增修改
package com.conformity.general.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.conformity.general.entity.test.Product;
import com.conformity.general.entity.test.esuser;
import com.conformity.general.mapper.ProductMapper;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import springfox.documentation.annotations.ApiIgnore;
@RestController
@RequestMapping(value = "/es",method = RequestMethod.POST)
@Api(tags = {"es的测试类"})
public class ElasticsearchController {
@Resource
private ProductMapper mapper;
@Resource
private ElasticsearchRestTemplate esTemplate;
@RequestMapping("/deleteIndex")
@ApiOperation("es测试---删除索引")
@ApiOperationSupport(author = "lsx",order = 1)
public String deleteIndex() {
IndexOperations indexOps = esTemplate.indexOps(Product.class);
boolean flag = indexOps.delete();
// 不推荐使用
// boolean flag = esTemplate.deleteIndex(indexName);
if (flag) {
return "删除成功";
}else {
return "删除失败";
}
}
@RequestMapping("/createIndex")
@ApiOperation("es测试---创建索引副本分片")
@ApiOperationSupport(author = "lsx",order = 2)
public String createIndex() {
IndexOperations indexOps = esTemplate.indexOps(esuser.class);
Document settings = indexOps.createSettings();
//创建分片、副本信息
settings.put("number_of_shards", 3);
settings.put("number_of_replicas", 1);
boolean b = indexOps.create(settings);
if (b) {
return "索引创建成功";
}else {
return "索引创建失败";
}
}
@RequestMapping("/createIndexMapping")
@ApiOperation("es测试---创建索引映射")
@ApiOperationSupport(author = "lsx",order = 3)
public String createIndexMapping() {
IndexOperations indexOps = esTemplate.indexOps(esuser.class);
Document mapping = indexOps.createMapping();
//MybatisTestUser的属性
JSONObject properties = new JSONObject();
JSONObject age = new JSONObject();
JSONObject name = new JSONObject();
JSONObject email = new JSONObject();
//创建属性对应的映射关系
name.put("index", true);
name.put("type", "text");
name.put("analyzer", "ik_max_word");
age.put("index", true);
age.put("type", "integer");
email.put("index", false);
email.put("type", "keyword");
properties.put("name", name);
properties.put("age", age);
properties.put("email", email);
mapping.put("properties", properties);
boolean b = indexOps.putMapping(mapping);
if (b) {
return "索引创建成功";
}else {
return "索引创建失败";
}
}
@RequestMapping("/getIndex")
@ApiOperation("es测试---获取索引信息")
@ApiOperationSupport(author = "lsx",order = 4)
public Map<String, Object> getIndex() {
IndexOperations indexOps = esTemplate.indexOps(esuser.class);
Map<String, Object> mapping = indexOps.getMapping();
mapping.forEach((k,v)->{
System.out.println(k+":"+v);
});
JSONObject jsonObject = new JSONObject(mapping).getJSONObject("properties");
System.out.println("==========json==============");
jsonObject.forEach((k,v)->{
System.out.println(k+":"+v);
});
System.out.println("----------------------");
Map<String, Object> settings = indexOps.getSettings();
settings.forEach((k,v)->{
System.out.println(k+":"+v);
});
return mapping;
}
@RequestMapping("/addAndUpdate")
@ApiOperation("es测试---新增和修改")
@ApiOperationSupport(author = "lsx",order = 5)
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "主键", required = true, paramType = "query"),
@ApiImplicitParam(name = "title", value = "商品名称", required = false, paramType = "query"),
@ApiImplicitParam(name = "category", value = "分类名称", required = false, paramType = "query"),
@ApiImplicitParam(name = "price", value = "商品价格", required = false, paramType = "query"),
@ApiImplicitParam(name = "images", value = "图片地址", required = false, paramType = "query"),
})
public String add(@ApiIgnore Product entity) {
Product save = esTemplate.save(entity);
return save.toString();
}
@RequestMapping("/findById")
@ApiOperation("es测试---根据id查询")
@ApiOperationSupport(author = "lsx",order = 6)
public Product findById(String id) {
Product product = esTemplate.get(id, Product.class);
// Optional<Product> optional = mapper.findById(Long.parseLong(id));
// System.out.println(optional.get().toString());
return product;
}
@RequestMapping("/batchAdd")
@ApiOperation("es测试---批量插入")
@ApiOperationSupport(author = "lsx",order = 7)
public String batchAdd() {
try {
List<Product> productList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Product product = new Product();
product.setId(Long.valueOf(i));
product.setTitle("["+i+"]小米手机");
product.setCategory("手机");
product.setPrice(4399.0+i);
product.setImages("http://www.atguigu/xm.jpg");
productList.add(product);
}
esTemplate.save(productList);
// mapper.saveAll(productList);
return "批量插入成功";
} catch (Exception e) {
e.printStackTrace();
return "批量插入失败";
}
}
}
几个重要的接口和类
NativeSearchQuery:
所有查询条件,排序,分页,高亮等需要这个类传入到ElasticsearchRestTemplate中进行搜索
QueryBuilders:
构建所有的查询方式:bool、match、term等
QueryBuilder:
bool、match、term等查询方式的接口
**
文档搜索
@RequestMapping("/findAllByPage")
@ApiOperation("es测试---分页查询全部")
@ApiOperationSupport(author = "lsx",order = 8)
public Page<Product> findAllByPage() {
//id排序
Sort sort = Sort.by(Sort.Direction.DESC, "id");
//分页信息
int currentPage=0;//当前页,第一页从 0 开始,1 表示第二页
int pageSize = 5;//每页显示多少条
PageRequest pageRequest = PageRequest.of(currentPage, pageSize,sort);
//创建查询query
MatchAllQueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery)
.withPageable(pageRequest)
.build();
SearchHits<Product> searchHits = esTemplate.search(query, Product.class);
AggregatedPage<SearchHit<Product>> page = SearchHitSupport.page(searchHits, query.getPageable());
return (Page<Product>) SearchHitSupport.unwrapSearchHits(page);
}
@RequestMapping("/findMatch")
@ApiOperation("es测试---匹配查询")
@ApiOperationSupport(author = "lsx",order = 9)
public List<Product> findMatch() {
//创建查询query
QueryBuilder qeuryBuilder = QueryBuilders.matchQuery("title", "小米加");
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(qeuryBuilder)
.build();
SearchHits<Product> searchHits = esTemplate.search(query, Product.class);
List<Product> list = new ArrayList<>();
searchHits.get().forEach(s->{
list.add(s.getContent());
});
return list;
}
@RequestMapping("/findTerm")
@ApiOperation("es测试---term查询和字段过滤")
@ApiOperationSupport(author = "lsx",order = 10)
public List<Product> findTerm() {
//创建查询query
QueryBuilder qeuryBuilder = QueryBuilders.termQuery("title", "小米");
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(qeuryBuilder)
//指定查询字段
.withFields("title","id","category","price")
.build();
SearchHits<Product> searchHits = esTemplate.search(query, Product.class);
List<Product> list = new ArrayList<>();
searchHits.get().forEach(s->{
list.add(s.getContent());
});
return list;
}
@RequestMapping("/findByBool")
@ApiOperation("es测试---条件查询")
@ApiOperationSupport(author = "lsx",order = 11)
public List<Product> findByBool() {
//创建查询query
QueryBuilder qeuryBuilder = QueryBuilders.boolQuery()
//相当于 或
.should(QueryBuilders.termQuery("price", 4399))
.should(QueryBuilders.termQuery("price", 4400));
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(qeuryBuilder)
.build();
SearchHits<Product> searchHits = esTemplate.search(query, Product.class);
List<Product> list = new ArrayList<>();
searchHits.get().forEach(s->{
list.add(s.getContent());
});
return list;
}
@RequestMapping("/findByBoolRange")
@ApiOperation("es测试---条件范围查询")
@ApiOperationSupport(author = "lsx",order = 12)
public List<Product> findByBoolRange() {
//创建查询query
QueryBuilder qeuryBuilder = QueryBuilders.boolQuery()
.mustNot(QueryBuilders.termQuery("price", 4400))
//must 相当于与
//rangeQuery 范围查询
.must(QueryBuilders.rangeQuery("price").from(4398).to(4405));
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(qeuryBuilder)
.build();
SearchHits<Product> searchHits = esTemplate.search(query, Product.class);
List<Product> list = new ArrayList<>();
searchHits.get().forEach(s->{
list.add(s.getContent());
});
return list;
}
@RequestMapping("/findMH")
@ApiOperation("es测试---高亮查询")
@ApiOperationSupport(author = "lsx",order = 13)
public SearchHits<Product> findHL() {
//创建查询query
QueryBuilder qeuryBuilder = QueryBuilders.matchQuery("title", "手机");
HighlightBuilder highlightBuilder = new HighlightBuilder().field("title")
.preTags("<span style='color:red'>")
.postTags("</span>");
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(qeuryBuilder)
.withHighlightBuilder(highlightBuilder)
.build();
SearchHits<Product> searchHits = esTemplate.search(query, Product.class);
return searchHits;
}
@RequestMapping("/findState")
@ApiOperation("es测试---聚合查询")
@ApiOperationSupport(author = "lsx",order = 14)
public Aggregations findState() {
//模糊查询
QueryBuilder qeuryBuilder = QueryBuilders.fuzzyQuery("title", "手机");
// 创建聚合查询条件
StatsAggregationBuilder stateAgg = AggregationBuilders.stats("state_price").field("price");
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(qeuryBuilder)
.addAggregation(stateAgg)
.build();
SearchHits<Product> searchHits = esTemplate.search(query, Product.class);
Aggregations aggregations = searchHits.getAggregations();
return aggregations;
}