ES在项目中的使用思路
以为ES在处理事务(数据一致性)方面比Database要若很多,所以在项目实战中,ES需要和Database配合使用。
ES中存储的数据相当于Database中对应数据的简略版,只可用于搜索结果展示,真正获取详细的信息还得去Database中获取。
如果不使用ES生态,使用原生ES,那么项目中对于数据的操作如下:
数据一致性
在ES和Database数据的同步时,存在两种数据一致性:
- 数据强一致性
- 数据最终一致性
数据强一致性表示,Database中的数据和ES中的数据保证实时一致,也就是说Database数据变更后立即同步到ES,数据的同步存在实时性。
数据最终一致性表示,Database中的数据和ES中的数据在过了某个特定的时间段之后保证一致,也就是说Database数据变更后隔一段时间再同步到ES,数据的同步存在延时性。
1. 数据一致性的实现
数据强一致性的具体实现,可以在Database更新数据时,同时调用Java代码更新ES中的数据,这样做会导致效率低而且不易维护。在企业级解决方案中,会将ES分成一个独立的服务,并配合消息队列实现ES数据的同步更新。
数据最终一致性的具体实现,可以设置定时任务,设置在每天某个并发量低的时刻,参考Database同步ES中的数据。
2. 数据一致性的选择
选择选择强一致性还是最终一致性得看具体的业务,如果该业务的数据实时更新很重要,比如商品价格的调整,那么需要使用强一致性。如果数据的实时更新不那么重要,比如一个商品的日访问量,那么就使用最终一致性。
Spring Boot整合ES
1. Spring Data
Spring Boot整合ES需要另外一个Spring开源的项目,Spring Data。
Spring Data官网:spring.io/projects/sp…
Spring Data项目的目的是为了简化构建基于Spring框架应用的数据访问计数,包括非关系数据库、Map-Reduce 框架、云数据服务等等;另外也包含对关系数据库的访问支持。
常见的子项目有:
- Spring Data JDBC:对JDBC的Spring Data存储库支持。
- Spring Data JPA:对JPA的Spring Data存储库支持。
- Spring Data MongoDB:对MongoDB的基于Spring对象文档的存储库支持。
- Spring Data Redis:从Spring应用程序轻松配置和访问Redis。
- Spring Data Elasticsearch:从Spring应用程序轻松配置和访问Elasticsearch。
Spring Boot整合ES实际上是Spring Boot整合了Spring Data,通过Spring Data来操作Elasticsearch。
2. 基础环境搭建
- Spring Boot版本:2.2.5.RELEASE
- 在pom.xml中引入依赖
1 | xml复制代码<dependency> |
- 创建config包
- 创建RestClient配置类
1 | java复制代码@Configuration |
3. 操作ES两种方式
在Spring data 2.x
~ 3.x
时,推荐使用ElasticTemplate来操作ES,ElasticTemplate底层调用的是TransportClient,使用的是ES的TCP端口9300,但是TransportClient在ES 6.x
~ 7.x
时就已经不推荐使用,在 8.x
已经废弃。所以在最新版的Spring Data 4.x
中,已经弃用ElasticTemplate,推荐使用RestHighLevelClient(高等级REST客户端)和ElasticsearchRepository接口来操作ES,使用的是ES的Web端口9200,类似于Kibana。
- RestHighLevelClient:用来实现ES的复杂检索。
- ElasticsearchRepository:用来实现ES的常规操作。
通常只用RestHighLevelClient完成高亮检索,剩下的都可以用ElasticsearchRepository完成。
RestHighLevelClient操作ES
1. 注入
1 | java复制代码@Autowired |
2. 新增Document
1 | java复制代码@Test |
3. 删除Document
1 | java复制代码@Test |
4. 更新Document
1 | java复制代码@Test |
注意:该更新保留原始数据。
5. 查询所有Document
1 | java复制代码@Test |
6. 批量混合操作(插入+删除+更新)
1 | java复制代码@Test |
7. 自定义查询Document
可以实现前文《ES高级检索》中所有的查询,支持链式调用组合多个查询模式。
配置不同的查询条件和查询模式,仅仅需要修改SearchSourceBuilder即可,其他地方不变。
1 | java复制代码@Test |
ElasticsearchRepository操作ES
上述案例描述了如何使用RestHighLevelClient来操作ES,我们会发现一个问题,如果我们需要通过RestHighLevelClient来新增和更新Document,那么我们需要必须将Bean转换成JSON格式。当然RestHighLevelClient这种方式更强大,更灵活,但是在处理一些简单检索时,就显得有些麻烦。
ElasticsearchRepository更具有面向对象的思想,配合注解可以将Bean自动JSON序列化,不需要再把Bean手动转换成JSON格式。所以在对ES进行一些常规操作时,推荐使用ElasticsearchRepository。
1. 配置
- 配置需要存储进ES的Bean
1 | java复制代码@Data |
* @Document:构建指定index和type,并能够自动将该类构建的Bean通过JSON序列化成Document存入ES的该index的type中。
* @Field:构建指定mapping,给某些必要的字段设置分词器。
* @id:创建Document的同时将Bean中的id赋值给 \_id。注意:
1. 在第一次向ES存储该Document时,ES就会自动去ES中创建该index,type和mapping,因此指定的index和type在原ES中不能已经存在。
2. 在开发中,**ES的Bean和业务中的Bean可以共用同一个类**,ES中使用的Bean只是业务中使用的Bean的一部分,所以我们只需要在需要存入ES的字段上构建mapping即可(上文构建了id,username,age三个字段)。
- 创建repository包
- 创建该Bean对应的Repository并继承ElasticsearchRepository
1 | java复制代码public interface UserRepository extends ElasticsearchRepository<User, String> { |
注意:泛型中第一个参数是该Repository对应的是哪一个Bean,第二个参数为Bean中id的类型。
4. 在使用处注入
1 | java复制代码@Autowired |
2. 新增Document
1 | java复制代码@Test |
3. 删除Document
1 | java复制代码@Test |
4. 更新Document
save方法不仅能完成新增,还能完成更新。
当Bean的id在ES中不存在时,为新增;当Bean的id在ES中存在时,为更新。
1 | java复制代码@Test |
5. 查询指定Document
1 | java复制代码@Test |
6. 查询所有Document
1 | java复制代码@Test |
7. 查询所有Document并排序
1 | java复制代码@Test |
8. 分页查询Document
1 | java复制代码@Test |
9. 模糊查询Document
模糊查询规则参考前文。
1 | java复制代码@Test |
10. 自定义查询Document
因为ElasticsearchRepository只提供了基本的ES操作接口,所以如果我们要使用ElasticsearchRepository完成更灵活的操作,比如我要根据某个字段关键词进行查询。
因此ElasticsearchRepository提供了一种DIY操作接口的功能,我们只需要在Repository中按照规定对接口进行命名和设计,ElasticsearchRepository会根据我们命名的接口,自动判断其功能并将其实现。
例如:我们需要根据查询出所有username字段数据中包含”张三”的Document(假设username的类型为text)
- 在Repository中设计接口
接口命名规则为:findBy + 字段名
1 | java复制代码public interface UserRepository extends ElasticsearchRepository<User, String> { |
- 调用接口完成操作
1 | java复制代码@Test |
该接口的实现等价于QueryDSL:
1 | bash复制代码GET /postilhub/user/_search |
针对各个接口的设计规则,Spring Data Elasticsearch给开发人员提供了一张表格:
注意:表格中 ? 表示参数,参数类型需要和ES中匹配。返回值统一为:List
命名关键词 | 命名示例 | QueryDSL示例 |
---|---|---|
Is | findByUsername | {“query”:{“bool”:{“must”:[{“term”:{“username”:{“value”:”?“}}}]}}} |
And | findByUsernameAndAge | {“query”:{“bool”:{“must”:[{“term”:{“username”:{“value”:”?“}}},{“term”:{“age”:{“value”:?}}}]}}} |
Or | findByUsernameOrAge | {“query”:{“bool”:{“should”:[{“term”:{“username”:{“value”:”?“}}},{“term”:{“age”:{“value”:?}}}]}}} |
Not | findByUsernameNot | {“query”:{“bool”:{“must_not”:[{“term”:{“username”:{“value”:”?“}}}]}}} |
Between | findByAgeBetween | {“query”:{“bool”:{“must”:[{“range”:{“age”:{“gt”:?,”lt”:?}}}]}}} |
LessThanEqual | findByAgeLessThanEqual | {“query”:{“bool”:{“must”:[{“range”:{“age”:{“gte”:null,”lte”:?}}}]}}} |
GreaterThanEqual | findByAgeGreaterThanEqual | {“query”:{“bool”:{“must”:[{“range”:{“age”:{“gte”:?,”lte”:null}}}]}}} |
LessThan | findByAgeLessThan | {“query”:{“bool”:{“must”:[{“range”:{“age”:{“gt”:null,”lt”:?}}}]}}} |
GreaterThan | findByAgeGreaterThan | {“query”:{“bool”:{“must”:[{“range”:{“age”:{“gt”:?,”lt”:null}}}]}}} |
Like | findByUsernameLike | {“query”:{“wildcard”:{“username”:{“value”:”*?*“}}}} |
StartingWith | findByUsernameStartingWith | {“query”:{“wildcard”:{“username”:{“value”:”?*“}}}} |
EndingWith | findByUsernameEndingWith | {“query”:{“wildcard”:{“username”:{“value”:”*?“}}}} |
本文转载自: 掘金