1. Spring Data Elasticsearch
Spring Data Elasticsearch 是 Elasticsearch 搜索引擎开发的解决方案。它提供:
模板对象,用于存储、搜索、排序文档和构建聚合的高级API。
例如,Repository 使开发者能够通过定义具有自定义方法名称的接口来表达查询。
2. 创建项目
2.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
或者
2.2 application.yml文件
spring:
elasticsearch:
rest:
# 连接es主节点
uris: 192.168.126.130:9200
logging:
level:
tracer: trace
logging.level.tracer=trace的作用是在控制台中显示底层的查询日志
2.3 pojo类Student
/**
* ES的注解@Document,
* indexName: student数据存储的位置,存在那个索引中
* shards: 指定分片的数量 默认1
* replicas: 指定副本的数量 默认1
* createIndex: 是否自动创建es索引 默认true
* 分片和副本,我们在kibana上已经创建好了这里不用管
* */
@Document(indexName = "students", shards=3, replicas =2)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
@Id //使用学生的学号作为索引ID
private Long id;
@Field(name="name") /**es索引中的属性名,对应实体类中的变量
相同可以不设置*/
private String name;
private Character gender;
private String birthDate;
}
@Document注解
@Documnet注解对索引的参数进行设置, 上面把 Students 索引的分片数设置为3,副本数设置为2。
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Document {
//要连接的索引名称
String indexName();
/** @deprecated */
@Deprecated
String type() default "";
boolean useServerConfiguration() default false;
//默认分片数量
short shards() default 1;
//默认副本数量
short replicas() default 1;
String refreshInterval() default "1s";
String indexStoreType() default "fs";
//是否自动创建索引
boolean createIndex() default true;
VersionType versionType() default VersionType.EXTERNAL;
}
@Id注解
在 Elasticsearch 中创建文档时,使用 @Id 注解的字段作为文档的 _id 值
@Field注解
通过 @Field 注解设置字段的数据类型和其他属性。
name: 设置字段名
FieldType: 字段数据类型(默认Auto自动)
- 文本类型 text 和 keyword
- text 类型会进行分词。
- keyword 不会分词。
- analyzer 指定分词器
- 通过 analyzer 设置可以指定分词器,例如 ik_smart、ik_max_word 等。
2.4 通过ElasticsearchRepository实现CRUD操作
Spring Data 的 Repository 接口提供了一种声明式的数据操作规范,无序编写任何代码,只需遵循 Spring Data 的方法定义规范即可完成数据的 CRUD 操作。
ElasticsearchRepository 继承自 Repository,其中已经预定义了基本的 CURD 方法,我们可以通过继承 ElasticsearchRepository,添加自定义的数据操作方法。
2.4.1 Repository 方法命名规范
自定义数据操作方法需要遵循 Repository 规范(方法命名规范)
关键词 | 方法名 | es查询 |
---|---|---|
And | findByNameAndPrice | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }} |
Or | findByNameOrPrice | { “query” : { “bool” : { “should” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }} |
Is | findByName | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }} |
Not | findByNameNot | { “query” : { “bool” : { “must_not” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }} |
Between | findByPriceBetween | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }} |
LessThan | findByPriceLessThan | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : false } } } ] } }} |
LessThanEqual | findByPriceLessThanEqual | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }} |
GreaterThan | findByPriceGreaterThan | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : false, “include_upper” : true } } } ] } }} |
GreaterThanEqual | findByPriceGreaterThan | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }} |
Before | findByPriceBefore | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }} |
After | findByPriceAfter | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }} |
Like | findByNameLike | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }} |
StartingWith | findByNameStartingWith | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }} |
EndingWith | findByNameEndingWith | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “*?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }} |
Contains/Containing | findByNameContaining | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }} |
In (when annotated as FieldType.Keyword) | findByNameIn(Collectionnames) | { “query” : { “bool” : { “must” : [ {“bool” : {“must” : [ {“terms” : {“name” : ["?","?"]}} ] } } ] } }} |
In | findByNameIn(Collectionnames) | { “query”: {“bool”: {“must”: [{“query_string”:{“query”: “”?" “?”", “fields”: [“name”]}}]}}} |
NotIn (when annotated as FieldType.Keyword) | findByNameNotIn(Collectionnames) | { “query” : { “bool” : { “must” : [ {“bool” : {“must_not” : [ {“terms” : {“name” : ["?","?"]}} ] } } ] } }} |
NotIn | findByNameNotIn(Collectionnames) | {“query”: {“bool”: {“must”: [{“query_string”: {“query”: “NOT(”?" “?”)", “fields”: [“name”]}}]}}} |
Near | findByStoreNear | Not Supported Yet ! |
True | findByAvailableTrue | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }} |
False | findByAvailableFalse | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “false”, “fields” : [ “available” ] } } ] } }} |
OrderBy | findByAvailableTrueOrderByNameDesc | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }, “sort”:[{“name”:{“order”:“desc”}}] } |
2.4.2 创建StuRepository接口
/**
* Repository规范,只要实现Repository接口,就可以访问数据,具体代码不需要自己完成(方法名的创建需要满足规范)
* spring data 会自动创建动态代理对象
* <Student,Long> 访问的数据类型, 索引的id类型
* */
public interface StuRepository extends ElasticsearchRepository<Student,Long> {
//根据name搜索
List<Student> findByName(String name);
//根据nema或者birthDate搜索
List<Student> findByNameOrBirthDate(String name , String birthDate, Pageable pageable);
}
2.4.3 在ES中创建Student索引
也可以在Student类的@Document注解上添加参数, 自动创建索引
PUT /students
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2,
"index.max_ngram_diff":30,
"analysis": {
"analyzer": {
"ngram_analyzer": {
"tokenizer": "ngram_tokenizer"
}
},
"tokenizer": {
"ngram_tokenizer": {
"type": "ngram",
"min_gram": 1,
"max_gram": 30,
"token_chars": [
"letter",
"digit"
]
}
}
}
},
"mappings": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text",
"analyzer": "ngram_analyzer"
},
"gender": {
"type": "keyword"
},
"birthDate": {
"type": "date",
"format": "yyyy-MM-dd"
}
}
}
}
2.4.4 实现新增/修改操作
@Autowired
private StuRepository stuRepository;
//新增数据
@Test
public void test(){
Student s1 = new Student(9527L,"唐伯虎",'男',"2021-07-12");
Student s2 = new Student(9528L,"华夫人",'女',"2021-03-22");
stuRepository.save(s1);
stuRepository.save(s2);
}
注入接口(代理)对象
直接调用接口自带save方法即可新增/修改
2.4.5 实现查询操作
Repository自带的方法查询
@Test
void test3(){
//查询单个学生
Optional<Student> s1 = stuRepository.findById(9527L);
Student student = s1.get();
System.out.println(student);
//查询所有数据
Iterable<Student> it = stuRepository.findAll();
for (Student s2: it){
System.out.println(s2);
}
}
自定义方法实现查询
//根据姓名搜索,通过自己创建的接口函数
@Test
void test5(){
List<Student> list = stuRepository.findByName("唐");
for (Student s:list){
System.out.println(s);
}
}
//通过name或者birthdate查询
//加分页参数
@Test
void test6(){
//page 从0开始
PageRequest page = PageRequest.of(1, 1);
List<Student> list = stuRepository.findByNameOrBirthDate("虎", "2021-03-22", page);
for (Student student : list){
System.out.println(student);
}
}
2.4.6 实现删除操作
Id在ES中的类型是long类型,这里参数需要是long类型的
//删除数据
@Test
void test4(){
stuRepository.deleteById(9527L);
}
2.4.7 在ES中查看数据
2.5 使用Criteria 构建查询-SearchOperations工具
使用 SearchOperations 工具执行一些更复杂的查询,这些查询操作接收一个 Query 对象封装的查询操作。
Spring Data Elasticsearch 中的 Query 有三种:
- CriteriaQuery
- StringQuery
- NativeSearchQuery
多数情况下,CriteriaQuery 都可以满足我们的查询求。
2.5.1 创建查询方法类StuSearcher
//通过Criteria 构建查询,通过API进行搜索
@Component
public class StuSearcher {
@Autowired
private ElasticsearchOperations op;
//根据birthDate搜索,
public List<Student> findByBirthdate(String birthDate){
//用Criteria设置搜索条件
Criteria c1 = new Criteria("birthDate").is(birthDate);
//有分页参数,设置分页参数,没有null值
return search(c1,null);
}
//用birthDate的范围搜索
//搜索条件
public List<Student> findByBirthdate(String ge, String le) {
//用Criteria构建搜索条件between(),在一定范围内搜索
Criteria c = new Criteria("birthDate").between(ge, le);
//设置分页对象
PageRequest page = PageRequest.of(0, 10);
return search(c,page);
}
//构建搜索方式search方法
public List<Student> search(Criteria c,PageRequest page){
//搜索条件封装到Query中
CriteriaQuery q = new CriteriaQuery(c);
//有分页参数,设置分页参数
if(page!=null){
q.setPageable(page);
}
//执行搜索
SearchHits<Student> hits = op.search(q, Student.class);
//基本的集合api频繁操作
// ArrayList<Student> list = new ArrayList<>();
// for (SearchHit<Student> h: hits){
// Student stu = h.getContent();
// list.add(stu);
// }
//集合的流操作代码
List<Student> list = hits.stream().map(SearchHit::getContent).collect(Collectors.toList());
return list;
}
}
2.5.2 实现数据查询
@Autowired
private StuSearcher stuSearcher;
//根据Searcher,搜索birthDate查询
@Test
void test7(){
List<Student> byBirthdate = stuSearcher.findByBirthdate("2021-09-11");
System.out.println(byBirthdate);
}
//根据Searcher,查询birthDate一定范围内的数据
@Test
void test8(){
List<Student> list = stuSearcher.findByBirthdate("2021-06-16");
for (Student s:list){
System.out.println(s);
}
}