SpringDataElasticSearch
# 80.SpringDataElasticSearch
使用 ES 原生的 API 还是有些麻烦的,之前我们介绍过 Spring 整合了很多框架,当然也包括 ES。
# Spring Data ElasticSearch 简介
# JPA
JPA 是 Java Persistence API 的简称,中文名 Java 持久层 API,是 Java 的一个 ORM 规范。它用于在 Java 对象和关系数据库之间保存数据。 JPA 充当面向对象的领域模型和关系数据库系统之间的桥梁。
Hibernate 是实现了 JPA 规范的一种 ORM 框架。
Sun 引入新的 JPA ORM 规范出于两个原因:
- 简化现有 Java EE 和 Java SE 应用开发工作
- Sun 希望整合 ORM 技术,实现天下归一。
# 什么是 Spring Data
Spring Data 是一个用于简化数据库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持 map-reduce 框架和云计算数据服务。 Spring Data 可以极大的简化 JPA 的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了 CRUD 外,还包括如分页、排序等一些常用的功能。
Spring Data 的官网:Spring Data (opens new window)
Spring Data 常用的功能模块如下:
- Spring Data JDBC (opens new window) - Spring Data repository support for JDBC.
- Spring Data JPA (opens new window) - Spring Data repository support for JPA.
- Spring Data MongoDB (opens new window) - Spring based, object-document support and repositories for MongoDB.
- Spring Data Redis (opens new window) - Easy configuration and access to Redis from Spring applications.
- Spring Data REST (opens new window) - Exports Spring Data repositories as hypermedia-driven RESTful resources.
- ...............
使用 Spring Data,可以简化对数据层的访问(例如 JDBC,Mybatis 和 Hibernate,Redis 等数据库)
# 什么是 Spring Data ElasticSearch
Spring Data ElasticSearch 基于 Spring data API 简化 ES 操作,将原始操作 ES 的客户端 API 进行封装 。
Spring Data 为 Elasticsearch 项目提供集成搜索引擎,可以和 Elastichsearch 交互文档和轻松地编写一个存储库数据访问层。
官网:Spring Data Elasticsearch (opens new window)
# 环境准备
相关依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.13</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>3.0.5.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>transport-netty4-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 新增 Spring 配置文件
在 resources 下新建 applicationContext.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/elasticsearch
http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
配置 elasticsearch: transport-client,用来连接 ES
<elasticsearch:transport-client id="esClient"
cluster-nodes="localhost:9301, localhost:9302, localhost:9303"
cluster-name="my-elasticsearch"/>
2
3
elasticsearch: repositories:包扫描器,扫描 dao
<elasticsearch:repositories base-package="com.peterjxl.es.repositories"/>
类似之前 Mybatis 的时候,只需定义实体类的接口所在地方,就能扫描并创建对象
配置 elasticsearchTemplate 对象,该对象是一个模板对象,可以简化我们的操作
<!-- 配置模板对象 -->
<bean id="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="esClient"/>
</bean>
2
3
4
# 管理索引
- 创建一个 Entity 类,其实就是一个 JavaBean(pojo)映射到一个 document,需要添加一些注解进行标注
- 创建一个 Dao,是一个接口,需要继承 ElasticSearchRepository 接口。
- 编写测试代码,使用 ElasticsearchTemplate 对象的 createIndex 方法创建索引,并配置映射关系。
新建实体类,注:自行生成 getter、setter 和 toString 方法
package com.peterjxl.es.entity;
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;
@Document(indexName = "spring-data-es-blog", type = "article")
public class Article {
@Id
@Field(type = FieldType.Long, store = true)
private long id;
// 默认会索引
@Field(type = FieldType.text, store = true, analyzer = "ik_smart")
private String title;
@Field(type = FieldType.text, store = true, analyzer = "ik_smart")
private String content;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
新建接口:
package com.peterjxl.es.repositories;
import com.peterjxl.es.Article;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface ArticleRepository extends ElasticsearchRepository<Article, Long> {
}
2
3
4
5
6
7
我们先试着创建索引:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDataESTest {
@Autowired
private ArticleRepository articleRepository;
@Autowired
private ElasticsearchTemplate template;
@Test
public void createIndex() throws Exception{
// 创建索引,并配置映射关系
template.createIndex(Article.class);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
测试结果:可以看到能成功创建索引,并且带有 mappings 信息
# 添加、更新文档
先来看看 ArticleRepository
的继承关系:
在 CrudRepository
中,有很多操作索引的方法,增删改查等:
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S var1);
<S extends T> Iterable<S> saveAll(Iterable<S> var1);
Optional<T> findById(ID var1);
boolean existsById(ID var1);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> var1);
long count();
void deleteById(ID var1);
void delete(T var1);
void deleteAll(Iterable<? extends T> var1);
void deleteAll();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
为此,我们的 ArticleRepository
能直接用来维护文档,步骤:
- 创建一个 Article 对象
- 使用 ArticleRepository 对象向索引库中添加文档。
@Test
public void addDocument() throws Exception{
Article article = new Article();
article.setId(1);
article.setTitle("Spring Data Elasticsearch");
article.setContent("基于Spring Data Elasticsearch的项目");
articleRepository.save(article);
}
2
3
4
5
6
7
8
运行结果:能看到数据
至于更新文档:ES 底层也是 Lucene,要更新文档也是删除后新增;在 ES 中,直接使用添加文档的方法即可完成更新(ID 要一样)
# 删除文档
直接使用 ArticleRepository 对象的 deleteById 方法直接删除即可:
@Test
public void deleteDocument() {
articleRepository.deleteById(1L);
}
2
3
4
运行结果:数据没有了
全部删除:articleRepository.deleteAll();
# 查询索引库
直接使用 ArticleRepository 对象的查询方法即可。
# 添加数据
我们先添加一些数据(还是使用之前用过的数据)
Article article = new Article();
article.setId(2);
article.setTitle("关于买房");
article.setContent("虚假的挥霍:花几千块钱去看看海。真正的挥霍:花100万交个首付买一个名义面积70平,实际面积55平,再还30年贷款的小户型住房");
articleRepository.save(article);
article.setId(4);
article.setTitle("关于梦想");
article.setContent("编程对我而言,就像是一颗小小的,微弱的希望的种子,我甚至都不愿意让人看见它。生怕有人看见了便要嘲讽它,它太脆弱了,经不起别人的质疑");
articleRepository.save(article);
article.setId(4);
article.setTitle("关于打工");
article.setContent("其实,我对公司是有点失望的。当初给我司定位为大厂,是高于公司简介的水平的。我是希望进来后,公司能够拼一把,快速成长起来的。大厂这个层级不是只发工资就可以的,公司需要有体系化的待遇。你们给的待遇,它的价值在哪里? 公司是否作出了壁垒形成了核心竞争力? 公司的待遇,和其他公司的差异化在哪里?为什么我来你这上班,我不能去别人那上班吗? 公司需要听从员工的意见,而不是想做什么就做什么,我需要符合劳动法的公司。");
articleRepository.save(article);
for (int i = 10; i < 20; i++) {
article.setId(i);
article.setTitle("测试查询" + i);
article.setContent("测试查询的内容" + i);
articleRepository.save(article);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
此时数据是不少的:
# 查询全部
@Test
public void findAll() {
Iterable<Article> articles = articleRepository.findAll();
for (Article article : articles) {
System.out.println(article);
}
}
2
3
4
5
6
7
此时能看到控制台打印了全部数据。
# 根据 Id 查询
@Test
public void findById() {
Article article = articleRepository.findById(3L).get();
System.out.println(article);
}
2
3
4
5
# 自定义查询方法
默认方法并不多,有时候查询规则比较复杂,此时就需要自定义查询了。
我们并不用自己实现,只需根据 SpringDataES 的命名规则来命名方法,就可以实现查询了!
命名规则:
关键字 | 命名规则 | 解释 | 示例 |
---|---|---|---|
and | findByField1AndField2 | 根据 Field1 和 Field2 查询 | findByTitleAndContent |
or | findByField1OrField2 | 根据 Field1 或 Field2 查询 | findByTitleOrContent |
is | findByField | 根据 Field1 查询 | findByTitle |
not | findByFieldNot | 查询不包含 Field1 的数据 | findByTitleNot |
between | findByFieldBetween | 获得指定范围的数据 | findByPriceBetween |
lessThanEqual | findByFieldLessThan | 获得小于等于指定值的数据 | findByPriceLessThan |
只需在接口定义方法:
public interface ArticleRepository extends ElasticsearchRepository<Article, Long> {
List<Article> findByTitle(String title);
List<Article> findByTitleOrContent(String title, String content);
}
2
3
4
在测试方法中使用:
@Test
public void testFindByTitle() {
List<Article> articles = articleRepository.findByTitle("测试查询");
articles.stream().forEach(System.out::println);
}
@Test
public void testFindByTitleOrContent() {
List<Article> articles = articleRepository.findByTitleOrContent("测试查询", "编程");
articles.stream().forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
我们运行测试方法,可以看到能正常查询出结果。
# 分页
注意,如果不设置分页信息,默认带分页,每页显示 10 条数据。
如果设置分页信息,应该在方法中添加一个参数 Pageable,我们在 dao 接口中新增一个方法:
List<Article> findByTitleOrContent(String title, String content, Pageable pageable);
在测试方法中使用:
@Test
public void testFindByTitleOrContentPage() {
Pageable pageable = PageRequest.of(0, 5);
List<Article> articles = articleRepository.findByTitleOrContent("测试查询", "编程", pageable);
articles.stream().forEach(System.out::println);
}
2
3
4
5
6
可以看到只查询了 5 条记录。PageRequest.of()
方法中,第一个参数指定第几页,第二个参数指定每页的个数
# 使用原生的查询条件查询
在刚刚的自定义查询方法中,默认对搜索的内容先分词然后再进行查询,每个词之间都是 and 的关系。比如我们查询是一句话:
List<Article> articles = articleRepository.findByTitle("测试查询天气之子");
那么就会对“测试查询天气之子”进行分词,然后查找 title 中包含所有这些单词的 title,和我们之前使用 queryString 的效果不一样,我们想要的是每个词之间是 or 的关系。
此时我们可以用 NativeSearchQuery 对象。使用方法:
- 创建一个 NativeSearchQuery 对象,设置查询条件(QueryBuilder 对象)
- 使用 ElasticSearchTemplate 对象执行查询
- 取查询结果
@Test
public void testNativeSearchQuery() {
// 创建一个查询对象
NativeSearchQuery query = new NativeSearchQueryBuilder()
// 添加基本的分词条件
.withQuery(QueryBuilders.matchQuery("title", "测试查询天气之子"))
.build();
// 执行查询
List<Article> articles = template.queryForList(query, Article.class);
articles.stream().forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
# 源码
已将源码上传到 Gitee (opens new window) 和 GitHub (opens new window) 上。并且创建了分支 demo3,读者可以通过切换分支来查看本文的示例代码