Elastic search入门到集群实战操作详解(原生API操作、springboot整合操作)-step2

上一篇详细讲解了使用kibana RestFul风格操作es,接下来就是ES集群、操作原生API 以及SpringDataElasticsearch

https://blog.csdn.net/qq_45441466/article/details/120110968

1.Elasticsearch集群

单点的elasticsearch存在哪些可能出现的问题呢?

  • 单台机器存储容量有限,无法实现高存储
  • 单服务器容易出现单点故障,无法实现高可用
  • 单服务的并发处理能力有限,无法实现高并发

所以,为了应对这些问题,我们需要对elasticsearch搭建集群

1.2.集群的结构

1.2.1.数据分片

首先,我们面临的第一个问题就是数据量太大,单点存储量有限的问题。

大家觉得应该如何解决?

没错,我们可以把数据拆分成多份,每一份存储到不同机器节点(node),从而实现减少每个节点数 据量的目的。这就是数据的分布式存储,也叫做: 数据分片(Shard) 。

 

 

1.2.2.数据备份

数据分片解决了海量数据存储的问题,但是如果出现单点故障,那么分片数据就不再完整,这又该如何 解决呢?

没错,就像大家为了备份手机数据,会额外存储一份到移动硬盘一样。我们可以给每个分片数据进行备 份,存储到其它节点,防止数据丢失,这就是数据备份,也叫 数据副本(replica)

数据备份可以保证高可用,但是每个分片备份一份,所需要的节点数量就会翻一倍,成本实在是太高 了!

为了在高可用和成本间寻求平衡,我们可以这样做:

  • 首先对数据分片,存储到不同节点
  • 然后对每个分片进行备份,放到对方节点,完成互相备份

这样可以大大减少所需要的服务节点数量,如图,我们以3分片,每个分片备份一份为例:

 在这个集群中,如果出现单节点故障,并不会导致数据缺失,所以保证了集群的高可用,同时也减少了 节点中数据存储量。并且因为是多个节点存储数据,因此用户请求也会分发到不同服务器,并发能力也 得到了一定的提升。

1.3.搭建集群

集群需要多台机器,我们这里用一台机器来模拟,因此我们需要在一台虚拟机中部署多个elasticsearch 节点,每个elasticsearch的端口都必须不一样。

一台机器进行模拟:将我们的ES的安装包复制三份,修改端口号,data和log存放位置的不同。

实际开发中:将每个ES节点放在不同的服务器上。

我们计划集群名称为:lagou-elastic,部署3个elasticsearch节点,分别是:

http:表示使用http协议进行访问时使用 端口,elasticsearch-head、kibana、postman,默认端口号 是9200。

tcp:集群间的各个节点进行通讯的端口,默认9300

第一步:复制es软件粘贴3次,分别改名

第二步:修改每一个节点的配置文件 config下的elasticsearch.yml,下面已第一份配置文件为例

 node-01:

#必须为本机的ip地址
network.host: 127.0.0.1

#允许跨域名访问
http.cors.enabled: true
#当设置允许跨域,默认为*,表示支持所有域名
http.cors.allow-origin: "*"
#允许所有节点访问
#network.host: 0.0.0.0
# 集群的名称,同一个集群下所有节点的集群名称应该一致
cluster.name: lagou-elastic
#当前节点名称 每个节点不一样
node.name: node-01
#数据的存放路径 每个节点不一样,不同es服务器对应的data和log存储的路径不能一样
path.data: D:\class\es-9201\data
#日志的存放路径 每个节点不一样
path.logs: D:\class\es-9201\logs
# http协议的对外端口 每个节点不一样,默认:9200
http.port: 9201
# TCP协议对外端口 每个节点不一样,默认:9300
transport.tcp.port: 9301
#三个节点相互发现,包含自己,使用tcp协议的端口号
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
#声明大于几个的投票主节点有效,请设置为(nodes / 2) + 1
#discovery.zen.minimum_master_nodes: 2
# 是否为主节点
#node.master: true

第三步:启动集群 把三个节点分别启动,启动时不要着急,要一个一个地启动

使用head插件查看:

 

1.4.测试集群中创建索引库

配置kibana,再重启

 

搭建集群以后就要创建索引库了,那么问题来了,当我们创建一个索引库后,数据会保存到哪个服务节 点上呢?如果我们对索引库分片,那么每个片会在哪个节点呢?

这个要亲自尝试才知道。

 

这里给搭建看看集群中分片和备份的设置方式,示例:

PUT /lagou { "settings": { "number_of_shards": 3, "number_of_replicas": 1 } }

这里有两个配置:

  •  number_of_shards:分片数量,这里设置为3
  • number_of_replicas:副本数量,这里设置为1,每个分片一个备份,一个原始数据,共2份。

通过chrome浏览器的head查看,我们可以查看到分片的存储结构:

可以看到,test这个索引库,有三个分片,分别是0、1、2,每个分片有1个副本,共6份。

node-01上保存了1号分片和2号分片的副本

node-02上保存了0号分片和2号分片的副本

node-03上保存了0号分片和1号分片的副本

1.5.集群工作原理

1.5.1.shad与replica机制

(1)一个index包含多个shard,也就是一个index存在多个服务器上

(2)每个shard都是一个最小工作单元,承载部分数据,比如有三台服务器,现在有三条数据,这三条数 据在三台服务器上各方一条. (

3)增减节点时,shard会自动在nodes中负载均衡

(4)primary shard(主分片)和replica shard(副本分片),每个document肯定只存在于某一个 primary shard以及其对应的replica shard中,不可能存在于多个primary shard

(5)replica shard是primary shard的副本,负责容错,以及承担读请求负载

(6)primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改

(7)primary shard的默认数量是5,replica默认是1(每个主分片一个副本分片),默认有10个 shard,5个primary shard,5个replica shard

(8)primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和 副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上

1.5.2.集群写入数据

1. 客户端选择一个node发送请求过去,这个node就是coordinating node (协调节点)

2. coordinating node,对document进行路由,将请求转发给对应的node。(根据一定的算法选择 对应的节点进行存储)

3. 实际上的node上的primary shard处理请求,将数据保存在本地,然后将数据同步到replica node

4. coordinating node,如果发现primary node和所有的replica node都搞定之后,就会返回请求到 客户端

这个路由简单的说就是取模算法,比如说现在有3太服务器,这个时候传过来的id是5,那么5%3=2,就 放在第2台服务器

1.5.3.ES查询数据

倒排序算法

查询有个算法叫倒排序:简单的说就是:通过分词把词语出现的id进行记录下来,再查询的时候先去查到哪 些id包含这个数据,然后再根据id把数据查出来

查询过程

1. 客户端发送一个请求给coordinate node

2. 协调节点将搜索的请求转发给所有的shard对应的primary shard 或replica shard

3. query phase(查询阶段):每一个shard 将自己搜索的结果(其实也就是一些唯一标识),返回 给协调节点,有协调节点进行数据的合并,排序,分页等操作,产出最后的结果

4. fetch phase(获取阶段) ,接着由协调节点,根据唯一标识去各个节点进行拉取数据,最终返回 给客户端

2.Elasticsearch客户端

2.1.客户端介绍

在elasticsearch官网中提供了各种语言的客户端:https://www.elastic.co/guide/en/elasticsearch/clie nt/index.html

注意点击进入后,选择版本到 6.2.4 ,因为我们之前按照的都是 6.2.4 版本:

2.2.创建Demo工程

2.2.1.初始化项目

创建springboot项目。

2.2.2.pom文件

注意,这里我们直接导入了SpringBoot的启动器,方便后续讲解。不过还需要手动引入elasticsearch的 High-level-Rest-Client的依赖:

<properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
        <!--Apache开源组织提供的用于操作JAVA BEAN的工具包-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.1</version>
        </dependency>
        <!--ES高级Rest Client-->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>6.4.3</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.4.3</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>6.4.3</version>
        </dependency>

<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>-->
<!--        </dependency>-->

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

2.2.3.配置文件

我们在resource下创建application.yml

2.3.索引库及映射

 创建索引库的同时,我们也会创建type及其映射关系,但是这些操作不建议使用java客户端完成,原因 如下:

  • 索引库和映射往往是初始化时完成,不需要频繁操作,不如提前配置好
  • 官方提供的创建索引库及映射API非常繁琐,需要通过字符串拼接json结构:

 

因此,这些操作建议还是使用我们昨天学习的Rest风格API去实现。

我们接下来以这样一个商品数据为例来创建索引库:

public class Product {
private Long id;
private String title; //标题
private String category;// 分类
private String brand; // 品牌
private Double price; // 价格
private String images; // 图片地址
}

分析一下数据结构:

  • id:可以认为是主键,将来判断数据是否重复的标示,不分词,可以使用keyword类型
  • title:搜索字段,需要分词,可以用text类型
  • category:商品分类,这个是整体,不分词,可以使用keyword类型 brand:品牌,与分类类似,不分词,可以使用keyword类型
  • price:价格,这个是double类型
  • images:图片,用来展示的字段,不搜索,index为false,不分词,可以使用keyword类型

我们可以编写这样的映射配置:

PUT /lgt
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"item": {
"properties": {
"id": {
"type": "keyword"
},
"title": {
"type": "text","analyzer": "ik_max_word"
},
"category": {
"type": "keyword"
},
"brand": {
"type": "keyword"
},
"images": {
"type": "keyword",
"index": false
},
"price": {
"type": "double"
}
}
}
}
}

2.4.索引数据操作

有了索引库,我们接下来看看如何新增索引数据

操作MYSQL数据库:

1.获取数据库连接

2.完成数据的增删改查

3.释放资源

2.4.1.初始化客户端

完成任何操作都需要通过HighLevelRestClient客户端,看下如何创建。

private static RestHighLevelClient restHighLevelClient;
    private Gson gson = new Gson();
    /**
     * 初始化客户端
     */
    @BeforeAll
    static void init() {
        System.out.println("执行了");
        RestClientBuilder clientBuilder = RestClient.builder(
                new HttpHost("127.0.0.1", 9201, "http"),
                new HttpHost("127.0.0.1", 9202, "http"),
                new HttpHost("127.0.0.1", 9203, "http")
        );
        restHighLevelClient = new RestHighLevelClient(clientBuilder);
    }

    /**
     * 关闭客户端
     */
    @AfterAll
    static void close() throws IOException {
        restHighLevelClient.close();

    }

2.4.2.新增文档

/**
     * 新增文档
     * @throws IOException
     */
    @Test
    void addDoc() throws IOException {
        //1.创建文档
        Product product = new Product();
        product.setBrand("华为");
        product.setCategory("手机");
        product.setId(1L);
        product.setImages("http://image.huawei.com/1.jpg");
        product.setTitle("华为P50就是棒");
        product.setPrice(88.88d);
        //2.将文档数据转换为json格式
        String source = gson.toJson(product);
        //3.创建索引请求对象
        //    public IndexRequest(String index, String type, String id)
        IndexRequest request = new IndexRequest("lagou","item",product.getId().toString());
        //4.发出请求
        request.source(source, XContentType.JSON);
        IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        System.out.println(response);
    }

2.4.3.查看文档

 /**
     * 查看文档
     * @throws IOException
     */
    @Test
    void queryDoc() throws IOException {
        //创建请求对象GetRequest,并指定id
        GetRequest request = new GetRequest("lagou","item","1");
        //执行查询
        GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
        System.out.println(response);
        String source = response.getSourceAsString();
        Product product = gson.fromJson(source, Product.class);
        System.out.println(product);
    }

2.4.4.修改文档

/**
     * 修改文档
     * @throws IOException
     */
    @Test
    void updateDoc() throws IOException {
        Product product = new Product();
        product.setBrand("华为");
        product.setCategory("手机");
        product.setId(1L);
        product.setImages("http://image.huawei.com/1.jpg");
        product.setTitle("华为P50就是棒");
        product.setPrice(88.99d);
        //创建请求对象GetRequest,并指定id
        IndexRequest request = new IndexRequest("lagou","item",product.getId().toString());
        String source = gson.toJson(product);
        request.source(source,XContentType.JSON);
        //执行查询
        IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        System.out.println(response);

    }

2.4.5.删除文档

/**
     * 删除文档
     * @throws IOException
     */
    @Test
    void deleteDoc() throws IOException {
        //创建请求对象GetRequest,并指定id
        DeleteRequest request = new DeleteRequest("lagou","item","1");
        //执行查询
        DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        System.out.println(response);
    }

2.5.搜索数据

2.5.1.查询所有match_all

/**
     * 匹配所有
     * @throws IOException
     */
    @Test
    void matchAll() throws IOException {
        //创建搜索对象
        SearchRequest request = new SearchRequest();
        //查询构建工具
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //添加查询条件,通过QueryBuilders获取各种查询
        sourceBuilder.query(QueryBuilders.matchAllQuery());
        request.source(sourceBuilder);
        //执行查询
        SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        //获取查询结果
        SearchHits hits = search.getHits();
        //获取文件数组
        SearchHit[] searchHits = hits.getHits();
        List<Product> productList = new ArrayList<>();
        for (SearchHit searchHit : searchHits) {
            String source = searchHit.getSourceAsString();
            Product product = gson.fromJson(source, Product.class);
            productList.add(product);
        }
        System.out.println(productList);
    }

注意,上面的代码中,搜索条件是通过 sourceBuilder.query(QueryBuilders.matchAllQuery()) 来添加的。这个 query() 方法接受的参数是: QueryBuilder 接口类型。

这个接口提供了很多实现类,分别对应我们在之前中学习的不同类型的查询,例如:term查询、match 查询、range查询、boolean查询等,如图:

 因此,我们如果要使用各种不同查询,其实仅仅是传递给 sourceBuilder.query() 方法的参数不同而 已。而这些实现类不需要我们去 new ,官方提供了 QueryBuilders 工厂帮我们构建各种实现类:

2.5.2.关键字搜索match

封装基础查询方法:

 /**
     * 基础查询方法
     *         //查询构建工具
     *         SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
     *         //添加查询条件,通过QueryBuilders获取各种查询
     *         sourceBuilder.query(QueryBuilders.matchAllQuery());
     */
    void basicQuery(SearchSourceBuilder sourceBuilder) throws IOException {
        //创建搜索对象
        SearchRequest request = new SearchRequest();
        request.source(sourceBuilder);
        //执行查询
        SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        //获取查询结果
        SearchHits hits = search.getHits();
        //获取文件数组
        SearchHit[] searchHits = hits.getHits();
        List<Product> productList = new ArrayList<>();
        for (SearchHit searchHit : searchHits) {
            String source = searchHit.getSourceAsString();
            Product product = gson.fromJson(source, Product.class);
            productList.add(product);
        }
        System.out.println(productList);
    }
    /**
     * 关键字查询
     * @throws IOException
     */
    @Test
    void match() throws IOException {
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //设置查询类型的查询条件
        builder.query(QueryBuilders.matchQuery("title","P50"));
        //调用基础查询方法
        basicQuery(builder);
    }

2.5.3.范围查询range

RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price");

方法说明
gt(Object from)大于
gte(Object from)大于等于
lt(Object from)小于
lte(Object from)小于等于

示例:

 /**
     * 范围查询 30-100
     * @throws IOException
     */
    @Test
    void range() throws IOException {
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //设置查询类型的查询条件
        builder.query(QueryBuilders.matchQuery("title","P50"));
        builder.query(QueryBuilders.rangeQuery("price").gt(30).lt(100));
        //调用基础查询方法
        basicQuery(builder);
    }

2.5.4.source过滤

_source:存储原始文档

默认情况下,索引库中所有数据都会返回,如果我们想只返回部分字段,可以通过source filter来控 制。

    /**
     * _source过滤
     * @throws IOException
     */
    @Test
    void sourceFilter() throws IOException {
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //设置查询类型的查询条件
        builder.query(QueryBuilders.matchQuery("title","P50"));
        builder.query(QueryBuilders.rangeQuery("price").gt(30).lt(100));
        builder.fetchSource(new String[]{"id","price","title"},new String[0]);
        //调用基础查询方法
        basicQuery(builder);
    }

2.6.排序

依然是通过sourceBuilder来配置:

 

 /**
     * 排序
     * @throws IOException
     */
    @Test
    void sort() throws IOException {
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //设置查询类型的查询条件
        builder.query(QueryBuilders.matchQuery("title","P50"));
        builder.query(QueryBuilders.rangeQuery("price").gt(30).lt(100));
        builder.fetchSource(new String[]{"id","price","title"},new String[0]);

        builder.sort("price", SortOrder.DESC);
        //调用基础查询方法
        basicQuery(builder);
    }

2.7.分页

 

  /**
     * 分页
     * @throws IOException
     */
    @Test
    void page() throws IOException {
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //设置查询类型的查询条件
        builder.query(QueryBuilders.matchAllQuery());
        //添加分页
        int page = 1;
        int size =3;
        int start = (page-1)*size;
        //配置分页
        builder.from(start);
        builder.size(size);
        //调用基础查询方法
        basicQuery(builder);
    }

3.Spring Data Elasticsearch

接下来我们学习Spring提供的elasticsearch组件:Spring Data Elasticsearch

3.1.什么是SpringDataElasticsearch

Spring Data Elasticsearch(以后简称SDE)是Spring Data项目下的一个子模块。

Spring Data 的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是 非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提 高开发效率。

Spring Data Elasticsearch的页面:https://projects.spring.io/spring-data-elasticsearch/

特征:

  • 支持Spring的基于 @Configuration 的java配置方式,或者XML配置方式
  • 提供了用于操作ES的便捷工具类 ElasticsearchTemplate 。包括实现文档到POJO之间的自动智 能映射。
  • 利用Spring的数据转换服务实现的功能丰富的对象映射
  • 基于注解的元数据映射方式,而且可扩展以支持更多不同的数据格式,可以定义JavaBean:类 名、属性
  • 根据持久层接口自动生成对应实现方法,无需人工编写基本操作代码(类似mybatis,根据接口自 动得到实现)。当然,也支持人工定制查询

3.2.配置SpringDataElasticsearch

我们在pom文件中,引入SpringDataElasticsearch的启动器:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

然后,只需要在resources下新建application.yml文件,引入elasticsearch的host和port即可:

spring:
  data:
    elasticsearch:
      cluster-name: lagou-elastic
      cluster-nodes: 127.0.0.1:9301,127.0.0.1:9302,127.0.0.1:9303

需要注意的是,SpringDataElasticsearch底层使用的不是Elasticsearch提供的RestHighLevelClient, 而是TransportClient,并不采用Http协议通信,而是访问elasticsearch对外开放的tcp端口,我们之前 集群配置中,设置的分别是:9301,9302,9303

3.3.索引库操作

准备一个pojo对象 然后准备一个新的实体类,作为下面与索引库对应的文档:

@Data
@Document(indexName = "lat",type = "product",shards = 3,replicas = 1)
@AllArgsConstructor
@NoArgsConstructor
public class Product {
    @Id
    private Long id;
    @Field(value = "title",type = FieldType.Text,analyzer = "ik_max_word")
    private String title; //标题
    @Field(value = "category",type = FieldType.Keyword)
    private String category;// 分类
    @Field(value = "brand",type = FieldType.Keyword)
    private String brand; // 品牌
    @Field(value = "price",type = FieldType.Double)
    private Double price; // 价格
    @Field(value = "images",type = FieldType.Keyword,index = false)
    private String images; // 图片地址
}
  • @Document:声明索引库配置
    • indexName:索引库名称
    • type:类型名称,默认是“docs”
    • shards:分片数量,默认5
    • replicas:副本数量,默认1
  • @Id:声明实体类的id
  • @Field:声明字段属性
    • type:字段的数据类型
    • analyzer:指定分词器类型
    • index:是否创建索引

我们先创建一个测试类,然后注入ElasticsearchTemplate:

    @Autowired
    private ElasticsearchTemplate template;

下面是创建索引库的API示例:

    @Test
    public void createIndex() {
        //创建索引的方法
        template.createIndex(Product.class);
    }

 创建索引库需要指定的信息,比如:索引库名、类型名、分片、副本数量、还有映射信息都已经填写

3.3.2.创建映射

 @Test
    public void putMapping() {
        //创建类型映射
        template.putMapping(Product.class);
    }

3.4.索引数据CRUD

SDE的索引数据CRUD并没有封装在ElasticsearchTemplate中,而是有一个叫做 ElasticsearchRepository的接口:

 我们需要自定义接口,继承ElasticsearchRespository:

/**
 * @Author panghl
 * @Date 2021/9/5 0:17
 * @Description
 * 当SDE访问索引库时,需要定义一个持久层的接口去继承ElasticsearchRepository 即可,无需实现
 **/
public interface ProductRepository extends ElasticsearchRepository<Product, Long> {
    /**
     * 根据价格区间查询
     * @param from 开始价格
     * @param to 结束价格
     * @return 符合条件的goods
     */
    List<Product> findByPriceBetween(Double from, Double to);
}

3.4.1.创建索引数据

    @Autowired
    private ProductRepository productRepository;

    @Test
    public void addDoc() {
        Product product1 = new Product(1L, "锤子手机", "手机", "锤子", 3288.88d, "http://image.huawei.com/1.jpg");
        Product product2 = new Product(2L, "华为手机", "手机", "华为", 3288.88d, "http://image.huawei.com/1.jpg");
        Product product3 = new Product(3L, "小米手机", "手机", "小米", 3288.88d, "http://image.huawei.com/1.jpg");
        Product product4 = new Product(4L, "苹果手机", "手机", "苹果", 3288.88d, "http://image.huawei.com/1.jpg");
        Product product5 = new Product(5L, "OPPO手机", "手机", "OPPO", 3288.88d, "http://image.huawei.com/1.jpg");
        List<Product> productList = new ArrayList<>();
        productList.add(product1);
        productList.add(product2);
        productList.add(product3);
        productList.add(product4);
        productList.add(product5);
        productRepository.saveAll(productList);
        System.out.println("save success");
    }

3.4.2.查询索引数据

默认提供了根据id查询,查询所有两个功能:

 @Test
    public void queryIndexData() {
        Product product = productRepository.findById(1L).orElse(new Product());
        //取出数据
        //orElse 方法的作用:如果optional中封装的实体对象为空也就是没有从索引库中查询出匹配的文档,返回orElse的参数
        System.out.println("product=>" + product);
    }

3.4.3.自定义方法查询

ProductRepository提供的查询方法有限,但是它却提供了非常强大的自定义查询功能:

只要遵循SpringData提供的语法,我们可以任意定义方法声明:

    /**
     * 根据价格区间查询
     * @param from 开始价格
     * @param to 结束价格
     * @return 符合条件的goods
     */
    List<Product> findByPriceBetween(Double from, Double to);

无需写实现,SDE会自动帮我们实现该方法,我们只需要用即可:

    @Test
    public void querySelfIndexData() {

        List<Product> byPriceBetween = productRepository.findByPriceBetween(1000d, 4000d);
        System.out.println(byPriceBetween);
    }

支持的一些语法示例:

 

 

3.5.原生查询

 如果觉得上述接口依然不符合你的需求,SDE也支持原生查询,这个时候还是使用 ElasticsearchTemplate

而查询条件的构建是通过一个名为 NativeSearchQueryBuilder 的类来完成的,不过这个类的底层还 是使用的原生API中的 QueryBuilders 、 AggregationBuilders 、 HighlightBuilders 等工具。

需求: 查询title中包含小米手机的商品,以价格升序排序,分页查询:每页展示2条,查询第1页。 对查询结果进行聚合分析:获取品牌及个数

示例

/**
     * 查询title中包含小米手机的商品,以价格升序排序,分页查询:每页展示2条,查询第1页。
     * 对查询结果进行聚合分析:获取品牌及个数
     */
    @Test
    public void nativeQuery() {
        //1.构建一个原生查询器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.source过来
        //2.1参数: public FetchSourceFilter(String[] includes, String[] excludes)
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[0], new String[0]));
        //3.查询条件
        queryBuilder.withQuery(QueryBuilders.matchQuery("title","小米手机"));
        //4.设置分页,并排序
        queryBuilder.withPageable(PageRequest.of(0,10, Sort.by(Sort.Direction.ASC,"price")));

        //高亮
//        HighlightBuilder.Field field = new HighlightBuilder.Field("title");
        HighlightBuilder builder = new HighlightBuilder();
        builder.field("title");
        builder.preTags("<font style='color:red'>");
        builder.postTags("</font>");
        //设置为0即可返回完整内容 而非片段
        builder.numOfFragments(0);
        queryBuilder.withHighlightBuilder(builder);


        //5.对查询结果进行聚合分析:获取品牌及个数
        queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brand").missing("title"));


        //6.查询
        AggregatedPage<Product> result = template.queryForPage(queryBuilder.build(), Product.class,new ESSearchResultMapper());
        System.out.println(result);



        //7.获取结果
        //总数
        long total = result.getTotalElements();
        //页码
        int totalPage = result.getTotalPages();
        //获取本业的数据集合
        List<Product> content = result.getContent();
        content.stream().forEach(System.out::print);

        //获取聚合的结果
        Aggregations aggregations = result.getAggregations();
        Terms terms = aggregations.get("brandAgg");
        //获取桶并且遍历桶中的内容
        terms.getBuckets().forEach(b->{
            System.out.println("品牌->"+b.getKey());
            System.out.println("文档数->"+b.getDocCount());
        });

    }

注:上述查询不支持高亮结果。

高亮展示:

1、自定义搜索结果映射

/**
 * @Author panghl
 * @Date 2021/9/5 1:09
 * @Description 自定义结果映射,处理高亮
 **/
public class ESSearchResultMapper implements SearchResultMapper {

    /**
     * 完成结果映射
     * 操作的重点应该是将原有的结果: _source 取出来,放入高亮的数据
     *
     * @param searchResponse
     * @param aClass
     * @param pageable
     * @param <T>
     * @return AggregatedPage 需要三个参数进行构建:pageable,List<Product>,总记录数
     */
    @Override
    public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
        //获取总记录数
        SearchHits hits = searchResponse.getHits();

        long totalHits = hits.getTotalHits();
        System.out.println("总记录数->" + totalHits);
        Gson gson = new Gson();
        //记录列表
        List<T> productList = new ArrayList<>();
        for (SearchHit hit : hits) {
            if (hits.getHits().length <= 0) {
                return null;
            }
            //获取_source属性中的所有数据
            Map<String, Object> map = hit.getSourceAsMap();
            //获取高亮的字段
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            //每个高亮字段都需要进行设置
            for (Map.Entry<String, HighlightField> highlightField : highlightFields.entrySet()) {
                //获取高亮的key : 高亮的字段
                String key = highlightField.getKey();
                //获得value : 高亮之后的效果
                HighlightField value = highlightField.getValue();
                //将高亮字段和文本效果放入map中
                map.put(key, value.getFragments()[0].toString());
            }
            //将map转为对象
            T T = gson.fromJson(gson.toJson(map), aClass);
            productList.add(T);
        }
        //第四个参数response.getAggregations()  添加聚合结果
        return new AggregatedPageImpl<>(productList,pageable,totalHits,searchResponse.getAggregations(),searchResponse.getScrollId());
    }

    @Override
    public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
        return null;
    }


}

2、高亮实现:

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值