开发者

SpringBoot简单整合ElasticSearch实践

开发者 https://www.devze.com 2025-12-07 10:22 出处:网络 作者: Kevin_wcl
价值2999元 Java视频教程限时免费下载
专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
立即下载
目录一:ElasticSearch支持对结构化和非结构化的数据进行检索二:ES的核心概念Index:索引库Type:类型Document&field索引库index的CRUD文档映射ES中的数据类型创建简单映射文档的CRUD三:SpringBoot操作ES总结一
目录
  • 一:ElasticSearch支持对结构化和非结构化的数据进行检索
  • 二:ES的核心概念
    • Index:索引库
    • Type:类型
    • Document&field
    • 索引库index的CRUD
    • 文档映射
    • ES中的数据类型
    • 创建简单映射
    • 文档的CRUD
  • 三:SpringBoot操作ES
    • 总结

      一:ElasticSearch支持对结构化和非结构化的数据进行检索

      我的理解是:把采集的数据(一般是从数据库中的数据如商品数据)转化为`一种有结构的数据`进行搜索,从而提高搜索效率,通常而言有结构的数据`查询是比较快`的。而这种结构在ES中被叫做 - `倒排索引文档`.

      数据转换为倒排索引文档的整个过程叫`索引创建`,如下图:

      SpringBoot简单整合ElasticSearch实践

      原始数据经过:分词,词大小写转换,自然排序,单词合并形成倒排索引。

      • 1.分词需要使用到分词器,对于中文的内容更要使用中文分词器,比如:IK分词器
      • 2.为了方便搜索,忽略大小写敏感所以进行了词态和大小写转换
      • 3.单词进行排序,有序的数据可以提高查询速度,比如二分查找
      • 4.相同单词进行合并后单词只需要进行一次搜索即可。

      ElasticSearch基于Lucene进行封装的,对于ElasticSearch来说他的数据是进行分片存储的,而一个分片就是一个Lucene的索引库,而Lucene的索引库分为索引区和数据区两部分。

      结构如下:

      SpringBoot简单整合ElasticSearch实践

      二: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
                  }
                }
              }
            ]
          编程客栈}
        },
        "from": 0,
        "size": 10,
        "sort": [
          {
            "amount": "desc"
          }
        ]
      }

      高亮查询

      GET /orders/_search
      {
          "query" : {
              "match": { "title": "鼠标" }
          },
          "highlight" : {
              "fields" : {
                  "title": {
                      "pre_tags": [
           编程客栈               "<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:9200

      3.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的一些简单操作。

      这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

      0
      价值2999元 Java视频教程限时免费下载
      专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
      立即下载

      精彩评论

      暂无评论...
      验证码 换一张
      取 消