本文主要是介绍SpringBoot简单整合ElasticSearch实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《SpringBoot简单整合ElasticSearch实践》Elasticsearch支持结构化和非结构化数据检索,通过索引创建和倒排索引文档,提高搜索效率,它基于Lucene封装,分为索引库、类型...
一:ElasticSearch支持对结构化和非结构化的数据进行检索
我的理解是:把采集的数据(一般是从数据库中的数据如商品数据)转化为`一种有结构的数据`进行搜索,从而提高搜索效率,通常而言有结构的数据`查询是比较快`的。而这种结构在ES中被叫做 - `倒排索引文档`.
数据转换为倒排索引文档的整个过程叫`索引创建`,如下图:

原始数据经过:分词,词大小写转换,自然排序,单词合并形成倒排索引。
- 1.分词需要使用到分词器,对于中文的内容更要使用中文分词器,比如:IK分词器
- 2.为了方便搜索,忽略大小写敏感所以进行了词态和大小写转换
- 3.单词进行排序,有序的数据可以提高查询速度,比如二分查找
- 4.相同单词进行合并后单词只需要进行一次搜索即可。
ElasticSearch基于Lucene进行封装的,对于ElasticSearch来说他的数据是进行分片存储的,而一个分片就是一个Lucene的索引库,而Lucene的索引库分为索引区和数据区两部分。
结构如下:

二:ES的核心概念
Index:索引库
index被叫做索引库,类似于mysql的数据库database ; 一个index中包含一堆有相似结构的文档数据(Document),比如说建立一个Goods index 商品索引,里面可能就存放了所有的商品数据,通常一个商品数据(一行数据)在ES中被描述为一个document。
Type:类型
每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,就好比一个表中的多行数据拥有相同的列 。在ES 7.x以上的版本中取消了这个逻辑分类。
Document&field
document是一个文档数据,也是ES中的最小数据单元,一个document 可以是一条商品数据,一个订单数据,通常用jsON数据结构表示,每个index下的type中,都可以去存储多个document。一个document里面有多个field,每个field就是一个数据字段。
索引库index的CRUD
索引库有点像关系型数据库的database数据库,比如:对于商品数据我们可以建立一个索引库,对于订单数据库我们也可以建立一个索引库
# 创建索引库
PUT /orders
{
"settings":{
"number_of_shards":5,
"number_of_replicas":1
}
}- number_of_shards : 主分片数量
- number_of_replicas :每个主分片有一个备分片
文档映射
ES的文档映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型,也就是说我们存储到ES中的数据到底用什么类型去存储。
就如同Mysql创建表时候指定的每个column列的类型。 为了方便字段的检索,我们会指定存储在ES中的字段是否进行分词,但是有些字段类型可以分词,有些字段类型不可以分词,所以对于字段的类型需要我们自己去指定。 所以我们的ES的操作流程应该是
- 1.创建索引
- 2.创建映射
- 3.添加数据
ES中的数据类型
- 字符串:常用的类型有 text(分词) ;keyword(不分词) ;如果某个字段被指定为text那么这个字段的值就会被分词然后创建倒排索引。 如果是keyword也会创建倒排索引,只不过是把字段的值作为整体形成倒排索引,不会分词。
- 数字:与Java差不多。有 long ; integer ; short ; double ; float
- 日期:date
- 逻辑:booleanpython
- 对象:Object
- 数组:array
- 位置:geo_point;geo_shape
创建简单映射
语法:
PUT /索引名
{
"mappings": {
"properties": {
"字段名": {
"type": "类型"
}
}
}
}例如:创建订单索引库(orders),该索引库中的字段有id,title,amount,count;
id 使用long类型 ,title使用text类型(分词且索引),amount使用double ,count使用 integer类型。
PUT /orders
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"id": {
"type": "long"
},
"title": {
"type": "text"
},
"amount":{
"type": "double"
},
"count":{
"type": "integer"
}
}
}
}文档的CRUD
数据是以Docuement进行存储,通常一行数据就是一个Document。
添加文档
语法 `PUT index/type/id` : index是索引库 ,type是类型 ,id是文档的ID 。添加一个订单数据演示如下:
PUT orders/_doc/1
{
"id":1,
"title":"买了个表",
"amount":120.00,
"count":1
}获取文档
语法 `GET index/type/id`
GET orders/_doc/1
修改文档
- 全量修改:全量修改文档和添加文档的语法一样 ,只要ID已经存在,那么添加就会变成修改,当时要注意,之所以叫全量修改是如果已有的数据是 4个列,而修改的数据只给了3个列,那么最终的数据就只有3个列。
- 局部修改:只是修改指定的列,其他列不动,语法:
POST /index/_update/id
{
"doc":{
"列" : 值,
"列": "值"
}
}例如:
POST orders/_update/1
{
"doc":{
"amount" : 150.00
}
}ES简单查询
# 查询所有数据 GET orders/_search # 查询分页 size是每页条数; from是跳过的条数,和mysql的limit是一样的含义,效果如下: GET orders/_search?size=2&from=2 # 携带查询参数可以通过 q= ,比如查询count为1的 GET orders/_search?q=count:1&size=10&from=0 # 需要带排序条件通过 sort=列:desc 指定 desc是倒排,正排是asc ,比如按在价格倒排 GET orders/_search?q=count:1&sort=amount:desc&size=10&from=0
ES条件查询
match : 标准匹配,会把搜索的关键字分词后再进行匹配,效果如同: where title = 鼠 or title = 标
GET /orders/_search
{
"query": {
"match": {
android "title": "了"
}
},
"from": 0,
"size": 10,
"_source": [
"id",
"title",
"amount",
"count"
],
"sort": [
{
"amount": "desc"
}
]
}- bool:代表的是组合查询,把多种查询方式组合到一起,bool下面包含了must和filter;must和filter里面都可以包含多个查询条件
- must:bool组合了must和filter , must中的语句是DSL查询,filter中的语句是DSL过滤。must代表其中的条件是必须满足,还可以把must指定为 should 和 must_not;这个位置的语句会进行相关性计算,且按照分数排序,一般会把关键字查询放到这里。
should下面会带一个以上的条件,至少满足一个条件,这个文档就符合should
- must_not : 文档必须不匹配条件
- filter : 过滤,里面的查询语句不会处理相关性等,但是会对查询的结果进行缓存,性能好
- range : 指的是范围 ;get是大于等于 ;let是小于等于
- term :词元匹配,可以理解为精准匹配,可以用于字符串,数字等类型
- from : 第2页应该是 (2 - 1 )* 每页条数10
GET /orders/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "买了个表"
}
}
],
"filter": [
{
"range": {
"amount": {
"gtphpe": 100,
"lte": 200
}
}
}
]
China编程}
},
"from": 0,
"size": 10,
"sort": [
{
"amount": "desc"
}
]
}高亮查询
GET /orders/_search
{
"query" : {
"match": { "title": "鼠标" }
},
"highlight" : {
"fields" : {
"title": {
"pre_tags": [
China编程 "<span style='color:red'>"
],
"post_tags": [
"</span>"
]
}
}
}
}三:SpringBoot操作ES
3.1 导入SpringBoot整合ES的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>3.2 application.yml配置
yml中对ES进行配置 , 如果是集群配置增加uri即可,单个配置如下:
spring:
elasticsearch:
rest:
uris: http://192.168.231.128:92003.3 编写Document对象 ,该对象是对存储到ES中的数据的封装,同时文档映射也是通过它来实现
package com.wcl.es.doc;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.math.BigDecimal;
//标记该对象是ES的文档对象
//indexName 索引库
//type 类型
@Document(indexName = "orders",type = "_doc")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderDoc {
//标记为文档ID,该ID的值会作为document的id值
@Id
private Long id;
/**
* 标题需要分词,指定为text;并使用IK分词器
* 一般需要作为关键字搜索的字段都要指定为text,因为需要分词且创建索引
*/
@Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
//@Field(type = FieldType.Keyword)
private String title;
/**
* 指定为integer类型
*/
@Field(type = FieldType.Integer)
private int count;
/**
* 状态指定为 integer类型
*/
@Field(type = FieldType.Integer)
private int status;
/**
* 金额
*/
@Field(type = FieldType.Double)
private BigDecimal amount;
}
3.4 SpringBootData提供了ElasticsearchRepository 来操作ES,该接口中包含了针对ES的CRUD方法,我们编写接口集成它即可使用
@Repository
public interface OrderRepository extends ElasticsearchRepository<OrderDoc,Long> {
}3.5 新建测试service类,注入
@Autowired
private OrdersRepository ordersRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;3.6 测试
创建索引库
public void createIndex() {
// 创建索引库
elasticsearchTemplate.createIndex(OrderDoc.class);
// 创建映射
elasticsearchTemplate.putMapping(OrderDoc.class);
}查询数据
public void findById() {
Optional<OrderDoc> byId = ordersRepository.findById(1L);
System.out.println(byId.get());
}删除数据
public void deleteById() {
ordersRepository.deleteById(1L);
}新增(修改)数据
public void addData() {
ordersRepository.save(
new OrderDoc(
3L,
"测试数据1",
1,
1,
new BigDecimal(1)
)
);
}条件查询
组合查询:
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "买了个表"
}
}
],
"filter": [
{
"range": {
"amount": {
"gte": 100,
"lte": 200
}
},
"term":{
"status":1
}
}
]
}
},
"from": 0,
"size": 10,
"sort": [
{
"amount": "desc"
}
]
}该语句在java中的写法:
public void search(){
// 查询构建器
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
// 设置分页 0 页数 10 每页显示多少条
builder.withPageable(PageRequest.of(0, 10));
// 设置排序(根据自己设置的字段排序)
builder.withSort(SortBuilders.fieldSort("amount").order(SortOrder.DESC));
// 构建组合查询 bool
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// must:{match:{title:"鼠标"}}
boolQueryBuilder.must(QueryBuilders.matchQuery("title", "鼠标"))
// filter:{range:{amount:{gte:100,lte:200}}}
.filter(QueryBuilders.rangeQuery("amount").gte(100).lte(200))
// filter:{term:{status:1}}
.filter(QueryBuilders.termQuery("status", 1));
// 添加查询条件
builder.withQuery(boolQueryBuilder);
// 执行查询
Page<OrderDoc> page = ordersRepository.search(builder.build());
// 获取条数
long total = page.getTotalElements();
System.out.println("总条数:" + total);
// 获取列表
page.getContent().forEach(System.out::println);
}高亮查询
{
"query" : {
"match": { "title": "鼠标" }
},
"highlight" : {
"fields" : {
"title": {
"pre_tags": [
"<span style='color:red'>"
],
"post_tags": [
"</span>"
]
}
}
}
}该语句在java中的写法
public void highlight(){
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(QueryBuilders.matchQuery("title", "鼠标"));
// 设置高亮
// 高亮的字段 fields.title
HighlightBuilder.Field highlightField = new HighlightBuilder.Field("title")
.preTags("<span style='color:red'>")
.postTags("</span>");
// 设置高亮
builder.withHighlightFields(highlightField);
Page<OrderDoc> orderDocs = elasticsearchTemplate.queryForPage(
builder.build(),
OrderDoc.class,
new HighlightResultMapper()
);
// 获取条数
long total = orderDocs.getTotalElements();
System.out.println("总条数:" + total);
// 获取列表
orderDocs.getContent().forEach(System.out::println);
}聚合查询(查询最大,最小,平均,和等数据)
{
"aggs":{
"statsAmount":{
"stats":{
"field":"amount"
}
}
}
}该语句在java中的写法:
public void aggregation(){
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.addAggregation(AggregationBuilders.max("maxAmount").field("amount"));
// 聚合查询
AggregatedPage<OrderDoc> orderDocs = elasticsearchTemplate.queryForPage(
builder.build(),
OrderDoc.class
);
// 获取条数
long total = orderDocs.getTotalElements();
System.out.println("总条数:" + total);
// 获取列表
orderDocs.getContent().forEach(System.out::println);
// 获取聚合结果
Map<String, Aggregation> asMap = orderDocs.getAggregations().getAsMap();
asMap.entrySet().forEach(aggregationEntry -> {
//聚合名字
String aggName = aggregationEntry.getKey();
Aggregation aggregation = aggregationEntry.getValue();
System.out.println("聚合名字 = "+aggName);
if(aggregation instanceof ParsedLongTerms){
//对应terms聚合
ParsedLongTerms agg = (ParsedLongTerms) aggregation;
agg.getBuckets().forEach(bucket->{
String key = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
System.out.println("key = "+key +" ; docCount = "+docCount);
});
}
if(aggregation instanceof ParsedStats){
//对应stats聚合
ParsedStats agg = (ParsedStats) aggregation;
System.out.println(agg.getAvg());
System.out.println(agg.getMax());
System.out.println(agg.getCount());
System.out.println(agg.getSum());
System.out.println(agg.getMin());
}
if(aggregation instanceof ParsedSum){
//对应sum聚合
ParsedSum agg = (ParsedSum) aggregation;
System.out.println(agg.getValue());
}
});
}总结
以上就是springboot整合es的一些简单操作。
这篇关于SpringBoot简单整合ElasticSearch实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!