Spring Data Elasticsearch 是 Elasticsearch 搜索引擎开发的解决方案。它提供:模板对象,用于存储、搜索、排序文档和构建聚合的高级API。例如,Repository 使开发者能够通过定义具有自定义方法名称的接口来表达查询。学习文档: https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#reference
案例说明
在 Elasticsearch 中存储学生数据,并对学生数据进行搜索测试。
学号 | 姓名 | 性别 | 出生日期 |
---|---|---|---|
27 | 张三 | 男 | 2020-12-4 |
在 Elasticsearch 中创建 students 索引
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"
}
}
}
}
创建springboot工程,添加Spring Data Elasticsearch
依赖
yml配置
spring:
elasticsearch:
rest:
uris: http://192.168.64.181:9200
logging:
level:
tracer: trace #与es服务器通信的日志
Student 实体类
/**
* @Documnet注解对索引的参数进行设置。 上面代码中,把 students 索引的分片数设置为3,副本数设置为2。
* 指定学生数据,对应的索引
* 如果服务器的索引不存在,也可以根据这里的配置自动创建说明 shards = 3,replicas = 2
* 一般不使用自动创建,而是要手动创建索引
*/
@Document(indexName = "students")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
@Id//在 Elasticsearch 中创建文档时,使用 @Id 注解的字段作为文档的 _id 值
private Long id;
/**
* 通过 @Field 注解设置字段的数据类型和其他属性。
* 文本类型 text 会进行分词 和 keyword 不会分词
*
* analyzer 指定分词器
* 通过 analyzer 设置可以指定分词器,例如 ik_smart、ik_max_word 等。
* ngram 分词器 刘德华分词结果 刘、刘德、刘德华、德、德华、华
*/
@Field(analyzer = "ngram",type = FieldType.Text)
private String name;
@Field(type = FieldType.Keyword)
private Character gender;
@Field(type= FieldType.Date,format = DateFormat.custom,pattern = "yyyy-M-d")
private String birthDate;
}
Spring Data 的 Repository 接口提供了一种声明式的数据操作规范,无序编写任何代码,只需遵循 Spring Data 的方法定义规范即可完成数据的 CRUD 操作。
ElasticsearchRepository 继承自 Repository,其中已经预定义了基本的 CURD 方法,我们可以通过继承 ElasticsearchRepository
,添加自定义的数据操作方法。
Repository 方法命名规范
自定义数据操作方法需要遵循 Repository 规范,详情见官方文档
新建StudentRepository接口
/**
* Repository是Spring定义的一种数据操作规则,只定义抽象接口和方法,不需要添加任何方法,基础增删改查方法在父接口中已经定义好
*/
public interface StudentRepository extends ElasticsearchRepository<Student, Long> {
List<Student> findByName(String key);//在姓名中搜索关键词
List<Student> findByNameOrBirthDate(String name, String birthDate);//在姓名和出生日期中搜索关键词
}
学生数据的 CRUD 操作
@Autowired
private StudentRepository r;
@Test//测试添加和修改
void testSave() {
//save既是添加,也是修改,id重复就是修改
r.save(new Student(9527L, "唐伯虎1111", '男', "2021-09-01"));
r.save(new Student(9528L, "华夫人", '男', "2021-02-01"));
}
@Test//测试通过id查询
void testFindById(){
Optional<Student> opt=r.findById(9527L);
if(opt.isPresent()){
Student student = opt.get();
System.out.println(student);
}
}
@Test//测试查询所有数据
void testFineAll(){
Iterable<Student> all = r.findAll();
all.forEach(System.out::println);
}
@Test
void testFindByName(){
List<Student> list = r.findByName("唐");
list.stream().forEach(System.out::println);
}
@Test
void testFindByNameOrBirthDate(){
List<Student> list = r.findByNameOrBirthDate("唐", "2021-01-02");
list.stream().forEach(System.out::println);
}
依次运行每个测试方法,并使用 head 观察测试结果
Spring Data Elasticsearch 中,可以使用 ElasticsearchOperations
工具执行一些更复杂的查询,这些查询操作接收一个 Query 对象
封装的查询操作。
Spring Data Elasticsearch 中的 Query 有三种:
@Component
public class StudentSearcher {
@Autowired
private ElasticsearchOperations op;//用来执行查询的工具
public List<Student> findByBirthDate(String birthDate) {
//用Criteria设置搜索条件
Criteria c = new Criteria("birthDate");
c.is(birthDate);
return exec(c);
}
public List<Student> findByBirthDate(String from, String to) {
Criteria c = new Criteria("birthDate");
c.between(from, to);
return exec(c);
}
public List<Student> exec(Criteria c) {
//用查询对象,封装搜索条件
CriteriaQuery query=new CriteriaQuery(c);
SearchHits<Student> hits = op.search(query, Student.class);
//hit集合中取出所有学生对象,放入一个ArrayList集合
List<Student> studentList = hits.stream().map(SearchHit::getContent).collect(Collectors.toList());
return studentList;
}
}
编写测试类测试
修改StudentRepository接口,添加Pageable
参数,返回Page
结果
/**
* Repository是Spring定义的一种数据操作规则,只定义抽象接口和方法,不需要添加任何方法,基础增删改查方法在父接口中已经定义好
*/
public interface StudentRepository extends ElasticsearchRepository<Student, Long> {
Page<Student> findByName(String key, Pageable pageable);//在姓名中搜索关键词
}
测试
@Test
void testFindByName(){
PageRequest pageRequest = PageRequest.of(1, 1);
Page<Student> page = r.findByName("唐",pageRequest);
List<Student> content = page.getContent();
System.out.println(content);
System.out.println("当前页号:"+page.getNumber()+" ,是否有上一页:"+page.hasPrevious()+",是否有下一页:"+page.hasNext()+",最大页号:"+page.getTotalPages());
}
@Component
public class StudentSearcher {
@Autowired
private ElasticsearchOperations op;//用来执行查询的工具
public List<Student> findByBirthDate(String from, String to,Pageable pageable) {
Criteria c = new Criteria("birthDate");
c.between(from, to);
return exec(c,pageable);
}
public List<Student> exec(Criteria c, Pageable pageable) {
//用查询对象,封装搜索条件
CriteriaQuery query=new CriteriaQuery(c);
if(pageable!=null){
query.setPageable(pageable);
}
SearchHits<Student> hits = op.search(query, Student.class);
//hit集合中取出所有学生对象,放入一个ArrayList集合
List<Student> studentList = hits.stream().map(SearchHit::getContent).collect(Collectors.toList());
return studentList;
}
}
测试
@Autowired
private StudentSearcher studentSearcher;
@Test
void testElasticsearchOperations(){
PageRequest pageRequest = PageRequest.of(0, 2);
List<Student> list = studentSearcher.findByBirthDate("2021-04-01", "2021-09-01", pageRequest);
System.out.println(list);
}