1.Spring Data ElasticSeach
1.1 前言
es
本身提供java客户端去操作es,但是有一些缺点:
- 很多地方需要自己手动拼接
json
- 存储时,需要开发人员把对象自己序列为
json
对象才能存储 - 查询时,也需要将结果反序列化为对象
同时 现在很多开发都是基于SpringBoot,ES
提供的客户端我们还需要手动去整合到SpringBoot项目中去,这是非常麻烦的。所以我们可以采用 Spring Data ElasticSearch.
1.2 介绍
-
前言
如果接触过
Spring Data JPA
就知道JPA
是Spring Data
下面的一个子项目,专门用来操作关系型数据库。同样的道理Spring Data ElasticSearch
也是Spring Data
下面的一个子项目,专门用来ElasticSearch
-
官网 : https://spring.io/projects/spring-data
image-20200426112818223.png
大体意思如下:
Spring Data的任务是为数据访问提供一个熟悉的、一致的、基于Spring的编程模型,同时仍然保留底层数据存储的特殊特性。
它使使用数据访问技术、关系数据库和非关系数据库、map-reduce框架以及基于云的数据服务变得容易。这是一个伞形项目,包含许多特定于给定数据库的子项目。这些项目是通过与这些令人兴奋的技术背后的许多公司和开发人员合作开发的。
-
子项目
Spring Data 下面有很多子项目, 不同的子项目访问的数据类型也不一样,同时底层实现原理也不一样。
image-20200426113417639.png
-
Spring Data ElasticSearch 介绍
image-20200426113539352.png
大体意思如下:
用于Elasticsearch的Spring数据是伞形Spring数据项目的一部分,该项目旨在为新的数据存储提供一个熟悉且一致的基于Spring的编程模型,同时保留存储特定的特性和功能。
Spring Data Elasticsearch项目提供了与Elasticsearch搜索引擎的集成。Spring Data Elasticsearch的关键功能区域是一个以POJO为中心的模型,用于与Elastichsearch文档交互并轻松编写存储库样式的数据访问层。
-
spring data elasticsearch 特点
image-20200426113720865.png
大体意思如下:
Spring配置支持使用基于Java的@configuration类或用于ES客户机实例的XML命名空间。
ElasticsearchTemplate帮助程序类,可提高执行常见ES操作的效率。包括文档和POJO之间的集成对象映射。
与Spring转换服务集成的功能丰富的对象映射
基于注释的映射元数据,但可扩展以支持其他元数据格式
存储库接口的自动实现,包括对自定义查找器方法的支持。
对存储库的CDI支持
总结:
以前繁琐的操作,如果引入spring data elasticsearch后将变得非常进键。
1.3 版本问题
由于不同spring data elasticsearch 对 es的支持也不一样。我们在先前进行api操作时,使用的es版本是6.2.4,
如果我们采用spring data elasticsearch 最新的 版本去测试学习,可能就对这个es版本不支持了。
所以,这次我们采用的springboot版本为 2.1.11.RELEASE
1.4 编码实现
代码同步在这个地址: https://github.com/smallCodeWangzh/es-demo.git
1.4.1 创建项目


1.4.2 pom.xml 修改版本
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.11.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.elasticsearch</groupId>
<artifactId>es-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.4.3 增加配置
修改application.yml
配置。增加es配置条件
spring:
data:
elasticsearch:
cluster-name: docker-cluster
cluster-nodes: 192.168.169.130:9300
注意:
这里的端口是9300而不是9200
java客户端默认访问的集群客户端
1.4.4 新建一个POJO
package com.elasticsearch.bean;
import lombok.Data;
@Data
public class Goods {
/**
* 编号
*/
private int id;
/**
* 标题
*/
private String title;
/**
* 价格
*/
private double price;
/**
* 品牌
*/
private String brand;
/**
* 封面图片地址
*/
private String images;
}
1.4.5 添加ES配置
SpringData ElasticSearch 通过在POJO类加上一些注解来与索引进行绑定。这跟Spring Data JPA非常类型
-
@Document
文档对象注解,加在pojo类上,表明该类是一个文档对象类。这个类所产生的对象就是文档对象-
indexName 文档对象注解属性之一,索引名称
-
type 文档属性之一 类型名称
-
shards 文档属性之一 分片数量 , 默认5
-
replicas 副本数量,默认 1
image-20200426145310118.png
-
-
@Id
加在成员变量上,标记一个普通属性为主键image-20200426145334576.png
-
@Filed
加在成员变量上,标记一个属性为文档字段。属性如下:-
type 字段数据类型
-
index 是否索引
-
store是否存储
-
analyzer 分词器名称
image-20200426145612616.png
-
-
示例:
package com.elasticsearch.bean; import lombok.Data; 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; @Data @Document(indexName = "goods",type = "docs",shards = 3,replicas = 2) public class Goods { /** * 编号 */ @Id private int id; /** * 标题 */ @Field(type = FieldType.Text,analyzer = "ik_max_word",store = false,index = true) private String title; /** * 价格 */ @Field(type = FieldType.Double) private double price; /** * 品牌 */ @Field(type = FieldType.Keyword) private String brand; /** * 封面图片地址 */ @Field(type = FieldType.Keyword,index = false) private String images; }
1.5 测试
1.5.1 模板对象


这个对象封装了大量的对es操作。
1.5.2 创建索引
-
代码
image-20200426171100005.png
-
查询
image-20200426171158120.png
-
代码
package com.elasticsearch; import com.elasticsearch.bean.Goods; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class EsDemoApplicationTests { @Autowired private ElasticsearchTemplate elasticsearchTemplate; @Test public void createIndex() { // 传入class对象,根据配置的@Document信息创建索引 elasticsearchTemplate.createIndex(Goods.class); // 根据字符串创建索引 //elasticsearchTemplate.createIndex("goods"); } }
1.5.3 创建映射
@Test
public void createMapping() {
elasticsearchTemplate.putMapping(Goods.class);
}

1.5.4 删除索引
@Test
public void deleteMapping() {
elasticsearchTemplate.deleteIndex(Goods.class);
}

1.5.5 文档操作(CRUD)
1.5.5.1 前言
熟悉Spring Data JPA
就知道,当对数据库进行增删改查时,我们不需要写任何的接口处理。会自动根据方法和POJO类信息去进行CRUD。
同样的 Spring Data ElasticSearch
也是一样,也需要继承一个接口,就可以实现基本的CRUD了.这个接口
就是ElasticsearchRepossitory
1.5.5.2 ElasticsearchRepository
-
ElasticsearchRepository
image-20200427091035064.png
从上图源码我们可以看出,ElasticsearchRepository
继承了ElasticsearchCrudRepository
,同时自己还定义了一些方法。具体的方法我们稍后再说。我们接着看继承关系
-
ElasticsearchCrudRepository
image-20200427091220618.png
从上图源码我们得知,
ElasticsearchCrudRepository
继承了PagingAndSortingRepository
-
PagingAndSortingRepository
image-20200427091459441.png
从上图源码可知,PagingAndSortingRepository
继承了CrudRepository
-
CrudRepository
image-20200427091602296.png
从上图源码可知,CrudRepository
继承Repository
-
Respository
image-20200427091715816.png
从上图源码中看出 ,这个接口没有继承任何接口,也就是这个接口是一个顶级接口。
-
用图表示继承关系
image-20200427092445939.png
从图中的继承关系看出,ElasticsearchRepository
处于最底层,继承了上面接口所有的方法。因此当我们自己去写Dao层接口时,继承ElasticsearchRepository
即可。
1.5.5.4 新增文档
-
编写接口
image-20200427093211910.png
-
测试
image-20200427093304562.png
image.png
image-20200427093544186.png
-
通过kibana查询
image-20200427093630703.png
注意:_id
与_source
里面的id
保持一致的
1.5.5.5 批量新增

通过kibana查询

1.5.5.6 修改文档
修改与新增的方法是一致的,原理就是id存在就修改,不存在就新增

通过kibana查询

1.5.5.7 基本查询
-
查询所有
image-20200427094810676.png
默认提供一些查询所有的方法。同时有的方法还提供了排序的功能。
-
查询所有(默认排序)
image-20200427095015185.png
为了测试出更加直观的的效果,在POJO类加上toString方法

运行测试代码

-
查询所有(手动指定排序)
image-20200427095328720.png
手动指定以价格降序排序

-
查询所有(分页查询)
image-20200427095421555.png
分页查询,同时还指定排序。

1.5.5.8 自定义查询
1.5.5.8.1 介绍
在刚刚的例子中我们基本上使用的是默认给我提供的方法去进行操作,但有时候这些方法并不能满足我们的要求,此时我们就需要自定义方法(规则与Spring Data JPA
相同)
在GoodsRespoitry
接口自定义方法,这个方法只要满足一定的规则,开发人员不需要实现,框架就会帮我们去实现。
规则如下:



同时Spring Data ElasticSearch 支持源生的查询

1.5.5.8.2 测试查询
-
根据名字匹配查询最后按照价格排序
image-20200427102920187.png
image-20200427102930021.png
image-20200427103012195.png
-
缺陷
image-20200427103837683.png
当我们的title是 苹果荣耀
,发现我们查询不到数据:

这是因为当我们去进行查询时默认是and
查询,同时在实际开发过程中,有些复杂查询这种自定义方法实现不了,例如聚合。但是一些简单的查询可以这样去实现。
那么复杂的查询的实现不了,那么我们该使用什么去进行查询呢。此时我们就需要使用ElasticSearch原生查询。
1.5.5.8.3 高级查询
1. QueryBuilder
通过上面的例子我们知道 Spring Data Elasticserch 满足不了复杂的查询。所以我们需要使用源生查询。我们可以借助Respostiroy
的search
查询。

从图中的方法名我们知道,要进行查询需要借助一个QuerBuilder
对象。这个对象需要借助QueryBuilders
去产生。
QueryBuilders
这个里面定义了大量的方法,不同的方法就会产生不同类型的对象

接下来我们来使用一个简单的词条匹配查询
-
简单词条匹配查询
image-20200427110940631.png
image-20200427111035080.png
-
带分页
同时我们还可进行分页查询
image-20200427111619869.png
image-20200427111309737.png
2. SearchQuery
通过上面的例子,我们发现search
方法还可以传入一个SearchQuery
对象,并且同时返回的是一个Page对象

SearchQuery
对象是通过 NativeSearchQueryBuilder
得到。


我们发现其实NativeSearchQueryBuilder
就是一个条件整合器,把各个条件整合到一块。所以也推荐大家使用
NativeSearchQueryBuilder
去查询条件
当然我们也可以在这里面去添加分页操作。


当然通过Page对象我们也可以去得到一些分页信息:

1.5.6 聚合
1.桶
划分桶,我们这里可以品牌(brand)去划分桶,先用kibana方式去访问一下:

java代码展示:
@Test
public void aggs() {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// 不查询任何结果,相当于设置 size = 0
nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(null,null));
// 添加聚合
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("brand_aggs").field("brand"));
// 查询 得到分页对象
Page<Goods> page = goodsRepository.search(nativeSearchQueryBuilder.build());
// 将分页对象强转成 聚合分页对象(AggregationPage)
AggregatedPage<Goods> aggregatedPage = (AggregatedPage<Goods>) page;
// 获取所有聚合,因为聚合里面也可以嵌套聚合
// System.out.println(aggregatedPage.getAggregations());
// 根据名字获取聚合
Aggregation aggregation = aggregatedPage.getAggregation("brand_aggs");
//System.out.println(aggregation);
/**
* 获取聚合里面的桶
* 因为我们去进行聚合的时候,是根据 String 类型 brand字段去进行terms聚合
* 所以我们需要强转成 StringTerms对象才能获取里面的桶,当然除了该类型对象以外,当然还有其他的类型对象
*/
StringTerms stringTerms = (StringTerms) aggregation;
// 得到所有的桶
List<StringTerms.Bucket> buckets = stringTerms.getBuckets();
buckets.forEach(t -> {
System.out.print(t.getKeyAsString() + ":"); // 得到桶里面的key
System.out.print(t.getDocCount()); // 得到桶里面的 doc_count
System.out.println();
});
}

当然远程的查询也可以用ElasticSearchTemplate
实现


2.指标(度量)
先通过kibana查询

桶内指标其实就是一个聚合里面嵌套另一个聚合。
java代码:
@Test
public void metirc() {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// 不含任何结果,相当于设置 size = 0
nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(null,null));
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("brand_aggs").field("brand")
.subAggregation(AggregationBuilders.avg("price_avg").field("price")));
AggregatedPage<Goods> aggregatedPage = (AggregatedPage<Goods>) goodsRepository.search(nativeSearchQueryBuilder.build());
/**
* 通过kibana访问我们知道:
* brand_aggs 聚合里面包含了
* price_avg 聚合
* 因此我们先要获取 brand_aggs 再获取 price_avg聚合
*/
StringTerms stringTerms = (StringTerms) aggregatedPage.getAggregation("brand_aggs");
List<StringTerms.Bucket> buckets = stringTerms.getBuckets();
buckets.forEach(t -> {
System.out.print(t.getKeyAsString() + ":"); // 得到桶里面的key
System.out.print(t.getDocCount()); // 得到桶里面的 doc_count
System.out.println();
// 桶里面还包含着指标计算的结果
Aggregation aggregation = t.getAggregations().asMap().get("price_avg");
// 将该聚合转换成 InternalAvg
InternalAvg internalAvg = (InternalAvg) aggregation;
System.out.println(internalAvg.getValue());
});
}

自此,java 去连接 es操作完成了。
网友评论