spring data elasticsearch
yml文件4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.2.RELEASE cn.tedu es 0.0.1-SNAPSHOT es Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-data-elasticsearch org.projectlombok lombok org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin
spring:
elasticsearch:
rest:
uris:
- http://192.168.64.181:9200
- http://192.168.64.181:9201
- http://192.168.64.181:9202
logging:
level:
tracer: TRACE
Student 实体类
package cn.tedu.es.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@document(indexName = "students",shards = 3,replicas = 2)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
@Id
private Long id;
@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;
}
@document 注解
@Documnet注解对索引的参数进行设置。
上面代码中,把 students 索引的分片数设置为3,副本数设置为2。
@Id 注解在 Elasticsearch 中创建文档时,使用 @Id 注解的字段作为文档的 _id 值
@Field 注解通过 @Field 注解设置字段的数据类型和其他属性。
文本类型 text 和 keywordtext 类型会进行分词。
keyword 不会分词。
analyzer 指定分词器通过 analyzer 设置可以指定分词器,例如 ik_smart、ik_max_word 等。
我们这个例子中,对学生姓名字段使用的分词器是 ngram 分词器,其分词效果如下面例子所示:
| 字符串 | 分词结果 |
|---|---|
| 刘德华 | 刘 刘德 刘德华 德 德华 华 |
Spring Data 的 Repository 接口提供了一种声明式的数据操作规范,无序编写任何代码,只需遵循 Spring Data 的方法定义规范即可完成数据的 CRUD 操作。
ElasticsearchRepository 继承自 Repository,其中已经预定义了基本的 CURD 方法,我们可以通过继承 ElasticsearchRepository,添加自定义的数据操作方法。
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”}}] } |
package cn.tedu.es.repo; import cn.tedu.es.entity.Student; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import java.util.List; public interface StudentRepository extends ElasticsearchRepository测试学生数据的 CRUD 操作{ //在name字段中查找关键字 List findByName(String name); //在name字段中搜索关键词,或者birthDate字段匹配日期 List findByNameOrBirthDate(String name, String birthDate); }
添加测试类,对学生数据进行 CRUD 测试
package cn.tedu.es;
import cn.tedu.es.entity.Student;
import cn.tedu.es.repo.StudentRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import java.util.Optional;
@SpringBootTest
public class Test1 {
@Autowired
private StudentRepository repository;
@Test
public void test1(){
//在 es 服务器的 student索引中保存学生
repository.save(new Student(9527L,"唐伯虎",'男',"2021-11-12"));
repository.save(new Student(9528L,"华夫人",'女',"2021-11-12"));
repository.save(new Student(9529L,"祝枝山",'男',"2021-11-12"));
repository.save(new Student(9530L,"小强",'男',"2021-11-12"));
repository.save(new Student(9531L,"旺财",'男',"2021-11-12"));
repository.save(new Student(9532L,"如花",'女',"2021-11-12"));
}
@Test
public void test2(){
repository.save(new Student(9533L,"华太师",'男',"2020-11-20"));
}
@Test
public void test3(){
Optional stu = repository.findById(9527L);
if(stu.isPresent()){ //Optional对象中是否存在Student对象
System.out.println(stu);
}
System.out.println("------------------------------------");
Iterable it = repository.findAll();
for (Student s : it){
System.out.println(s);
}
}
@Test
public void test4(){
repository.deleteById(9531L);
}
@Test
public void test5(){
List list = repository.findByName("唐");
for(Student s :list){
System.out.println(s);
}
}
@Test
public void test6(){
List list = repository.findByNameOrBirthDate("唐", "2021-11-12");
for(Student s :list){
System.out.println(s);
}
}
}
依次运行每个测试方法,并使用 head 观察测试结果
package cn.tedu.esspringboot.es;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StudentService {
@Autowired
private StudentRepository studentRepo;
public void save(Student student) {
studentRepo.save(student);
}
public void delete(Long id) {
studentRepo.deleteById(id);
}
public void update(Student student) {
save(student);
}
public List findByName(String name) {
return studentRepo.findByName(name);
}
public List findByNameOrBirthDate(String name, String birthDate) {
return studentRepo.findByNameOrBirthDate(name, birthDate);
}
}
在 Elasticsearch 中创建 students 索引
在开始运行测试之前,在 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"
}
}
}
}
使用 Criteria 构建查询
Spring Data Elasticsearch 中,可以使用 SearchOperations 工具执行一些更复杂的查询,这些查询操作接收一个 Query 对象封装的查询操作。
Spring Data Elasticsearch 中的 Query 有三种:
- CriteriaQuery
- StringQuery
- NativeSearchQuery
多数情况下,CriteriaQuery 都可以满足我们的查询求。下面来看两个 Criteria 查询示例:
StudentSearcherpackage cn.tedu.es.creteria;
import cn.tedu.es.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class StudentSearcher {
@Autowired
private ElasticsearchOperations operations;
public List searchByName(String key) {
//在 name 字段中搜索关键词
Criteria c = new Criteria("name");//新建条件对象
c.is(key); //设置条件
return exec(c);
}
public List searchByBirthDate(String from,String to) {
Criteria c = new Criteria("birthDate");
c.between(from, to);
return exec(c);
}
private List exec(Criteria c) {
// 搜索条件封装到 Query 对象
CriteriaQuery query = new CriteriaQuery(c);
//使用 ElasticsearchOperations 工具来执行查询
//SearchHits 对象中包含学生数据、相关度得分、高亮字段
SearchHits search = operations.search(query, Student.class);
List list = new ArrayList<>();
for(SearchHit sh : search){
Student stu = sh.getContent();
list.add(stu);
}
return list;
}
}
新建测试类测试
package cn.tedu.es;
import cn.tedu.es.creteria.StudentSearcher;
import cn.tedu.es.entity.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class Test2 {
@Autowired
private StudentSearcher searcher;
@Test
public void test1(){
List list = searcher.searchByName("唐");
for(Student s : list){
System.out.println(s);
}
}
@Test
public void test2(){
List list = searcher.searchByBirthDate("2020-01-01", "2022-01-01");
for(Student s : list){
System.out.println(s);
}
}
}
分页查询
在 StudentRepository 添加方法
新建测试类
package cn.tedu.es;
import cn.tedu.es.entity.Student;
import cn.tedu.es.repo.StudentRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@SpringBootTest
public class Test3 {
@Autowired
private StudentRepository repository;
@Test
public void test1(){
Pageable pageable = PageRequest.of(0, 2);
Page list = repository.findByNameOrBirthDate("唐", "2021-11-12",pageable);
System.out.println("共有几页:"+list.getTotalPages());
System.out.println("是否有上一页:"+list.hasPrevious());
System.out.println("是否有下一页:"+list.hasNext());
System.out.println("每页大小(每页显示几条数据):"+list.getSize());
for(Student s :list){
System.out.println(s);
}
}
}
查看分页数据



