springboot操作elasticsearch

springboot操作elasticsearch

    • elasticsearch介绍
    • 前期准备
      • 1.安装es
      • 2.es基本概念
    • 操作elasticsearch
      • springdata操作
      • 环境准备
    • 注意事项
    • 结束语

elasticsearch介绍

Elasticsearch是一个开源的分布式、RESTful 风格的搜索和数据分析引擎,它的底层是开源库Apache Lucene。
  Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库——无论是开源还是私有,但它也仅仅只是一个库。为了充分发挥其功能,你需要使用 Java 并将 Lucene 直接集成到应用程序中。 更糟糕的是,您可能需要获得信息检索学位才能了解其工作原理,因为Lucene 非常复杂。
  为了解决Lucene使用时的繁复性,于是Elasticsearch便应运而生。它使用 Java 编写,内部采用 Lucene 做索引与搜索,但是它的目标是使全文检索变得更简单,简单来说,就是对Lucene 做了一层封装,它提供了一套简单一致的 RESTful API 来帮助我们实现存储和检索。
  当然,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确地形容:

  1. 一个分布式的实时文档存储,每个字段可以被索引与搜索;
  2. 一个分布式实时分析搜索引擎;
    能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。

前期准备

1.安装es

安装 Elasticsearch 之前,你需要先安装一个较新版本的 Java,目前市面的主流java是jdk1.8。
  你可以从 elastic 的官网 elastic.co/downloads/elasticsearch 获取最新版本的Elasticsearch。解压文档后,按照下面的操作,即可在前台(foregroud)启动 Elasticsearch:

cd elasticsearch-<version>
./bin/elasticsearch

此时,Elasticsearch运行在本地的9200端口,在浏览器中输入网址“http://localhost:9200/”,如果看到以下信息就说明你的电脑已成功安装Elasticsearch:

{
  "name" : "YTK8L4q",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "hB2CZPlvSJavhJxx85fUqQ",
  "version" : {
    "number" : "6.5.4",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "d2ef93d",
    "build_date" : "2018-12-17T21:17:40.758843Z",
    "build_snapshot" : false,
    "lucene_version" : "7.5.0",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

这里,我们安装的Elasticsearch版本号为6.5.4。
  Kibana 是一个开源的分析和可视化平台,旨在与 Elasticsearch 合作。Kibana 提供搜索、查看和与存储在 Elasticsearch 索引中的数据进行交互的功能。开发者或运维人员可以轻松地执行高级数据分析,并在各种图表、表格和地图中可视化数据。
  你可以从 elastic 的官网 https://www.elastic.co/downloads/kibana 获取最新版本的Kibana。解压文档后,按照下面的操作,即可在前台(foregroud)启动Kibana:

cd kibana-<version>
./bin/kabana

此时,Kibana运行在本地的5601端口,在浏览器中输入网址“http://localhost:5601”,即可看到以下界面:
springboot操作elasticsearch_第1张图片

2.es基本概念

  • 全文搜索(Full-text Search)

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。
  在全文搜索的世界中,存在着几个庞大的帝国,也就是主流工具,主要有:

Apache Lucene
Elasticsearch
Solr
Ferret

  • 倒排索引(Inverted Index)

该索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。Elasticsearch能够实现快速、高效的搜索功能,正是基于倒排索引原理。

  • 节点 & 集群(Node & Cluster)

Elasticsearch 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个Elasticsearch实例。单个Elasticsearch实例称为一个节点(Node),一组节点构成一个集群(Cluster)。

  • 索引(Index)

Elasticsearch 数据管理的顶层单位就叫做 Index(索引),相当于关系型数据库里的数据库的概念。另外,每个Index的名字必须是小写。

  • 文档(Document)

Index里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。Document 使用 JSON 格式表示。同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。

  • 类型(Type)

Document 可以分组,比如employee这个 Index 里面,可以按部门分组,也可以按职级分组。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document,类似关系型数据库中的数据表。
  不同的 Type 应该有相似的结构(Schema),性质完全不同的数据(比如 products 和 logs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。

  • 文档元数据(Document metadata)

文档元数据为_index, _type, _id, 这三者可以唯一表示一个文档,_index表示文档在哪存放,_type表示文档的对象类别,_id为文档的唯一标识。

  • 字段(Fields)

每个Document都类似一个JSON结构,它包含了许多字段,每个字段都有其对应的值,多个字段组成了一个 Document,可以类比关系型数据库数据表中的字段。
  在 Elasticsearch 中,文档(Document)归属于一种类型(Type),而这些类型存在于索引(Index)中,下图展示了Elasticsearch与传统关系型数据库的类比:
在这里插入图片描述

操作elasticsearch

elasticsearch的操作主要是通过http请求,restful风格,json作为请求体来操作的;比较方便操作,且比较容易,本次暂时分享 springdata 来操作es,至于用es的http请求和java原生操作的api,下次分享

springdata操作

环境准备

在安装好es后,我们首先创建springdata项目,这里就不一一详细说明了

  1. 依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
  1. yml文件的配置
spring:
  data:
    elasticsearch:
      # 连接的es名称
      cluster-name: elasticsearch
      # 连接的节点地址,如果是集群则用 多个地址连接用 , 相隔 
      # Java调用时端口是9300,restfu调用端口是9200,注意区分
      cluster-nodes: 192.168.66.134:9300 
  1. 实体类创建
    springdata的特点都是通过对实体类的操作,来对数据源操作,所以这里我们也是要建立对应es库的实体类
//文档 相当于数据库的数据表 indexName 文档名称 type 类型 shards 分片片数  replicas每个分区默认的备份数
@Document(indexName = "goods", type = "docs", shards = 3, replicas = 1)
@Data
public class Goods {
    @Id //主键id
    private Long id;
    //属性
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String title;//标题
    @Field(type = FieldType.Keyword)
    private String category;//分类
    @Field(type = FieldType.Keyword)
    private String brand;//品牌
    @Field(type = FieldType.Double)
    private Double price;//价格
    @Field(type = FieldType.Keyword,index = false)
    private String images;//图片
    public Goods(){}

    public Goods(Long id, String title, String category, String brand, Double price, String images) {
        this.id = id;
        this.title = title;
        this.category = category;
        this.brand = brand;
        this.price = price;
        this.images = images;
    }
}

注解解释:
@Document 注解:

public @interface Field {
  
FieldType type() default FieldType.Auto; //自动检测属性的类型,可以根据实际情况自己设置
  
FieldIndex index() default FieldIndex.analyzed; //默认情况下分词,一般默认分词就好,除非这个字段你确定查询时不会用到
  
DateFormat format() default DateFormat.none; //时间类型的格式化
  
String pattern() default "";
  
boolean store() default false; //默认情况下不存储原文
  
String searchAnalyzer() default ""; //指定字段搜索时使用的分词器
  
String indexAnalyzer() default ""; //指定字段建立索引时指定的分词器
  
String[] ignoreFields() default {}; //如果某个字段需要被忽略
  
boolean includeInParent() default false;
}

@Field注解:

public @interface Field {
  
FieldType type() default FieldType.Auto; //自动检测属性的类型,可以根据实际情况自己设置
  
FieldIndex index() default FieldIndex.analyzed; //默认情况下分词,一般默认分词就好,除非这个字段你确定查询时不会用到
  
DateFormat format() default DateFormat.none; //时间类型的格式化
  
String pattern() default "";
  
boolean store() default false; //默认情况下不存储原文
  
String searchAnalyzer() default ""; //指定字段搜索时使用的分词器
  
String indexAnalyzer() default ""; //指定字段建立索引时指定的分词器
  
String[] ignoreFields() default {}; //如果某个字段需要被忽略
  
boolean includeInParent() default false;
}

FieldType类型:

public enum FieldType {
    Text, 
    Integer,
    Long,
    Date,
    Float,
    Double,
    Boolean,
    Object,
    Auto,
    Nested,
    Ip,
    Attachment,
    Keyword
}

Text类型:索引全文字段,如电子邮件正文的描述或者产品描述。这些字段被分析器将字符串转换为单个术语列表。分析过程允许es在每个的全文域中搜索单个单词。文本字段不用于排序,也很少用于聚合
Object类型:Json文档本质上是分层的,文档可能包含内部对象,而这些对象又可能包含内部对象本身。test是在搜索时候不会被分词,而Keyword会被分词

  1. 创建Repository
    类似mybatis的mapper操作数据库相同,这边是创建Repository,来操作对应的实体类,来达到操作es库的作用
//形参一个是实体类和主键属性
public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {
}
  1. 索引库操作
    对索引库的操作使用的是spring为我们提供的模板来操作
@Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

操作很简单,使用期api即可

    @Test
    public void demo2() {
        //创建索引库
        elasticsearchTemplate.createIndex(Goods.class);
        //删除索引库
        elasticsearchTemplate.deleteIndex(Goods.class);
    }
  1. 操作文档数据
    利用之前创建的Repository来操作实体类,来达到操作数据源,这里列举的api比较少,详细可以自己进入源码查看学习
    @Test
    public void demo() {
        // 准备文档数据:
        List<Goods> list = new ArrayList<>();
        list.add(new Goods(1L, "苹果11proMax", "手机", "苹果", 9200.00, "/13123.jpg"));
        list.add(new Goods(2L, "坚果手机R1", "手机", "锤子", 3699.00, "/13123.jpg"));
        list.add(new Goods(3L, "华为META30", "手机", "华为", 4499.00, "/13123.jpg"));
        list.add(new Goods(4L, "小米9pro", "手机", "小米", 4299.00, "/13123.jpg"));
        list.add(new Goods(5L, "荣耀V30", "手机", "华为", 2799.00, "/13123.jpg"));
        list.add(new Goods(6L, "华为p30", "手机", "华为", 4299.00, "/13123.jpg"));
        list.add(new Goods(7L, "苹果11pro", "手机", "苹果", 8399.00, "/13123.jpg"));
        list.add(new Goods(8L, "苹果11", "手机", "苹果", 5499.00, "/13123.jpg"));
        // 批量保存
        goodsRepository.saveAll(list);
        //保存单个
        goodsRepository.save(new Goods(8L, "苹果11", "手机", "苹果", 5499.00, "/13123.jpg"));
        //删除所有
        goodsRepository.deleteAll();
        List<Goods> arr = (List<Goods>) goodsRepository.findAll();
        //根据id查找
        Goods result = goodsRepository.findById(1L).get();
    }
  1. 复杂操作
    Repository完成的是比较基础的操作,复杂的操作是需要java操作es的原生api来操作了
    //自定义查询
    @Test
    public void demo4() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //过滤查询source 第一个参数是需要显示的,第二个参数是不需要显示
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[0], new String[]{"id", "category", "images"}));
        //搜索match 单个匹配单个字段 会根据分词器查询 第一个参数是 字段,第二个搜索内容
        queryBuilder.withQuery(QueryBuilders.matchQuery("title","苹果11proMax"));
        //搜索 multi_match 单个匹配多个字段 会根据分词器查询  第一个参数是 搜索内容,第二个是多个字段拼接
        queryBuilder.withQuery(QueryBuilders.multiMatchQuery("苹果","title","brand"));
        //精确匹配 term 不会分词匹配
        queryBuilder.withQuery(QueryBuilders.termQuery("title","华为"));
        //多词条精确匹配 第一个参数是 字段 其余都是 搜索词
        queryBuilder.withQuery(QueryBuilders.termsQuery("title","华为","苹果"));
        //布尔组合
        queryBuilder.withQuery(QueryBuilders.boolQuery()
                //与 必须包含当前 查询的结果
                .must(QueryBuilders.matchQuery("title", "苹果11proMax"))
                //非 不可以包含符合搜索的条件
                .mustNot(QueryBuilders.matchQuery("title", "苹果11proMax"))
                //或 可以包含查询的结果
                .should(QueryBuilders.matchQuery("title", "苹果11proMax"))
                //过滤符合当前查询结果的 搜索结果
                .filter(QueryBuilders.matchQuery("title", "苹果11proMax")));
        //范围查询 可以链式查询 第一个是 字段 gt 大于 gte 大于等于 lt 小于 lte小于等于
        queryBuilder.withQuery(QueryBuilders.rangeQuery("price").gt(1000.0).lt(4400.0));
        //模糊查询 第一个是字段  第二个是搜索的模糊条件
        queryBuilder.withQuery(QueryBuilders.fuzzyQuery("title", "华为"));
        //排序 同时可以设置开始页(es开始页为0) 和页码 设置排序字段 多字段排序时候 按照从前到后排序字段
        queryBuilder.withPageable(PageRequest.of(0, 5, Sort.by(Sort.Direction.ASC,"price")));
        //聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("brand").field("1"));
        AggregatedPage<Goods> page = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //返回的查询结果.
        //查询到的元素数量
        System.out.println(page.getTotalElements());
        //获取到查询的集合
        System.out.println(page.getContent());
        //获取查询的页数
        System.out.println(page.getTotalPages());
    }
  1. 聚合操作
    es之所以被用作作为数据报表分析,是因为他的聚合操作功能,类似mysql的group by,es的聚合数据后,称作为桶;
//聚合处理
    @Test
    public void demo41() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //聚合  第一个为聚合的组名称 第二个为聚合的字段
        //根据词条内容分组,词条内容完全匹配的为一组
        queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand"));
        //根据数值阶梯分组,与日期类似
        queryBuilder.addAggregation(AggregationBuilders.histogram("prices").field("price").interval(500.0));
        //根据日期阶梯分组
        queryBuilder.addAggregation(AggregationBuilders.dateHistogram("dataPrices").field("prices").dateHistogramInterval(DateHistogramInterval.QUARTER));
        //数值和日期的范围分组,指定开始和结束,然后按段分组
        queryBuilder.addAggregation(AggregationBuilders.range("rangePrice").field("price").addRange(1000.0,20000.0));
        AggregatedPage<Goods> result = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //返回的查询结果
        //查询到的元素数量
        System.out.println(result.getTotalElements());
        //获取到查询的集合
        result.getContent().stream().forEach(System.out::println);
        //获取查询的页数
        System.out.println(result.getTotalPages());
        //获取所有的聚合
        Aggregations aggregations = result.getAggregations();
        //词条聚合解析
        Terms brandTerms = aggregations.get("brands");
        brandTerms.getBuckets().forEach(b -> {
            System.out.println("品牌 = " + b.getKeyAsString());
            System.out.println("count = " + b.getDocCount());
        });
        //数值阶梯聚合解析
        Histogram price = aggregations.get("prices");
        price.getBuckets().forEach(b ->{
            System.out.println("价格 = " + b.getKeyAsString());
            System.out.println("数量" + b.getDocCount());
        });
        //日期范围聚合解析
        Histogram data = aggregations.get("dataPrices");
        data.getBuckets().forEach(b ->{
            System.out.println("价格 = " + b.getKeyAsString());
            System.out.println("数量" + b.getDocCount());
        });
        //范围聚合解析
        Range range = aggregations.get("rangePrice");
        range.getBuckets().forEach(b ->{
            System.out.println("价格2 = " + b.getKeyAsString());
            System.out.println("数量2" + b.getDocCount());
        });
    }

注意事项

在测试过程时候,使用springboot来操作redis和es的时候,可能会出现启动报错的问题,建议使用一下配置文件可处理

@Configuration
public class ElasticSearchConfig {
    /**
     * redis和elasticsearch整合时候会报错
     * 防止netty的bug
     * java.lang.IllegalStateException: availableProcessors is already set to [4], rejecting [4]
     */
    @PostConstruct //在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
    void init() {
        System.setProperty("es.set.netty.runtime.available.processors", "false");
    }
}

结束语

es是目前开发比较主流的工具之一,由于其优秀的搜索功能,使其也可以作为数据库之外的存储数据,用作数据分析也是很不错的工具;这里学习推荐先学习其 http请求操作es的,其他的封装操作es的工具市面有不少,有点公司也有自己封装的,但是都是用http请求为基础的;

注:es的springdata操作不太详细,具体详细 会慢慢补充

你可能感兴趣的:(springboot操作elasticsearch)