前言
本文整合基于Springboot2.0+,es版本6.5.4,使用spring-boot-starter-data-elasticsearch
包。由于该包实际引用的是spring-data-elasticsearch
,所以需要注意spring-data-elasticsearch
和es版本的对应关系,具体可在这里查看。
- 注:虽然官网标明es的6.5.0+版本需要对应
spring-data-elasticsearch
的3.2.X,但由于项目中Springboot版本限制在2.0.3,因此spring-data-elasticsearch
的版本也被限制在了3.0.8,经过测试基本的插入查询等功能均可以正常使用,但是否会有一些高版本的功能受到影响暂不可知。
pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置参数
spring:
data:
elasticsearch:
cluster-name: esCluster
cluster-nodes: 127.0.0.1:9300 #配置es节点信息,逗号分隔,如果没有指定,则启动ClientNode(9200端口是http查询使用的。9300集群使用。这里使用9300.)
代码实现
创建baen对象
@Data
@Document(indexName = "testgoods", type = "goods")
public class TestGoodsBo {
@Id
private long id;
//@Field(type = FieldType.Text)
private String name;
private BigDecimal price;
private long stock;
@Version
private Long version;
}
首先创建bean对象,其中几个常用注解含义如下
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Document {
String indexName();//索引库的名称,个人建议以项目的名称命名
String type() default "";//类型,个人建议以实体的名称命名
short shards() default 5;//默认分区数
short replicas() default 1;//每个分区默认的备份数
String refreshInterval() default "1s";//刷新间隔
String indexStoreType() default "fs";//索引文件存储类型
}
@Document
作用于类上,经测试代码初始化时若es中没有对应的索引,则会在es中创建一个。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface Field {
FieldType type() default FieldType.Auto;#自动检测属性的类型
FieldIndex index() default FieldIndex.analyzed;#默认情况下分词
DateFormat format() default DateFormat.none;
String pattern() default "";
boolean store() default false;#默认情况下不存储原文
String searchAnalyzer() default "";#指定字段搜索时使用的分词器
String indexAnalyzer() default "";#指定字段建立索引时指定的分词器
String[] ignoreFields() default {};#如果某个字段需要被忽略
boolean includeInParent() default false;
}
@Field
作用于属性上,经测试该注解的属性有时会与现有的属性冲突,造成异常,错误信息如下,所以建议es中映射已建立的情况下,不要使用该注解。
Caused by: java.lang.IllegalArgumentException: Mapper for [name] conflicts with existing mapping in other types:
[mapper [name] has different [analyzer]]
@Id
和@Version
分别用来绑定es中的_id
和_version
字段。
创建Repository对象
public interface GoodsRepository extends ElasticsearchRepository<TestGoodsBo,Long> , PagingAndSortingRepository<TestGoodsBo,Long> {
List<TestGoodsBo> findByNameAndPrice(String name, Long price);
List<TestGoodsBo> findByNameOrPrice(String name, Long price);
Page<TestGoodsBo> findByName(String name,Pageable page);
Page<TestGoodsBo> findByNameNot(String name,Pageable page);
Page<TestGoodsBo> findByPriceBetween(long price,Pageable page);
Page<TestGoodsBo> findByNameLike(String name,Pageable page);
@Query("{\"bool\" : {\"must\" : {\"term\" : {\"message\" : \"?0\"}}}}")
Page<TestGoodsBo> findByMessage(String message, Pageable pageable);
}
es的操作主要通过自定义的Repository对象完成,该对象可以通过继承模板接口ElasticsearchRepository<T, ID extends Serializable>
实现,该模板提供了save
、findById
、findAll
和search
等通用方法的实现,同时还支持通过规定的名称格式自定义操作方法,自定义的规则见上述方法名。(通过规定格式的名称注入方法实现的方式非常有趣,暂时还不了解原理,后面可以研究一下源码)
调用过程
@Slf4j
@RestController
@RequestMapping(value = "/search")
public class SearchController {
@Autowired
private GoodsRepository repository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@RequestMapping(value = "/insert")
public TestGoodsBo insert(@RequestBody TestGoodsBo bo) {
repository.save(bo);
return bo;
}
@RequestMapping(value = "/get")
public TestGoodsBo get() {
Optional<TestGoodsBo> result = repository.findById(1L);
return result.get();
}
@RequestMapping(value = "/find")
public List<TestGoodsBo> find(String name,Pageable page){
return repository.findByName(name, page);
}
@GetMapping(value = "/search")
public Page<TestGoodsBo> search(String name, @PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)Pageable pageable) {
// //通过ElasticsearchTemplate实现
// QueryBuilder queryBuilder = QueryBuilders.matchQuery("name", name);
// SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder).withHighlightFields().build();
// Page<TestGoodsBo> sampleEntities = elasticsearchTemplate.queryForPage(searchQuery, TestGoodsBo.class);
// //Pageable对象的手动实现
// Sort sort = new Sort(Sort.Direction.ASC,"name");
// Pageable page = PageRequest.of(0,10,sort);
Page<TestGoodsBo> sampleEntities = repository.search(QueryBuilders.matchQuery("name", name),pageable);
return sampleEntities;
}
}
Controller的实现比较简单,以从es搜索文档为例,可以通过注入之前的Repository
对象或者是注入ElasticsearchTemplate
对象实现,查询规则不复杂的情况下前者更为简单一些,具体实现看上述代码片段便能理解。
说明:这里要特别说明的一点是经过我的测试
findByName
相较于search
方法有一定的局限性,比如我的es设置了ik和pinyin混合的分词器时,中文搜索两者都没问题,但使用拼音首字母搜索只有后者能搜索到结果,所以选择使用哪个方法需要慎重。
Pageable对象
然后要重点讲解一下Pageable
类型的对象,该对象可以帮助我们完成分页和排序操作,有手动和自动两种方式实现。
- 手动方式
Sort sort = new Sort(Sort.Direction.ASC,"name");
Pageable page = PageRequest.of(0,10,sort);
- 自动方式
@GetMapping(value = "/search")
public Page<TestGoodsBo> search(String name, @PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)Pageable pageable)
自动方式可以在request传参的同时就根据传入的参数来组装Pageable
对象,同时还能使用@PageableDefault
注解设定默认值,因此更推荐使用。Spring支持的request参数如下:
- page,第几页,从0开始,默认为第0页
- size,每一页的大小,默认为20
- sort,排序相关的信息,以property,property(,ASC|DESC)的方式组织,例如sort=firstname&sort=lastname,desc表示在按firstname正序排列基础上按lastname倒序排列