SpringBoot简单整合ElasticSearch

一: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

逻辑:boolean

对象: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": {
      "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": {
              "gte": 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的一些简单操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值