Springboot整合elasticsearch

前言

本文整合基于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依赖

    
        org.springframework.boot
        spring-boot-starter-parent
        2.0.3.RELEASE
        
    
    
    
        org.springframework.boot
        spring-boot-starter-data-elasticsearch
    

配置参数

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 , PagingAndSortingRepository {

        List findByNameAndPrice(String name, Long price);

        List findByNameOrPrice(String name, Long price);
        
        Page findByName(String name,Pageable page);

        Page findByNameNot(String name,Pageable page);

        Page findByPriceBetween(long price,Pageable page);

        Page findByNameLike(String name,Pageable page);

        @Query("{\"bool\" : {\"must\" : {\"term\" : {\"message\" : \"?0\"}}}}")
        Page findByMessage(String message, Pageable pageable);
}

es的操作主要通过自定义的Repository对象完成,该对象可以通过继承模板接口ElasticsearchRepository实现,该模板提供了savefindByIdfindAllsearch等通用方法的实现,同时还支持通过规定的名称格式自定义操作方法,自定义的规则见上述方法名。(通过规定格式的名称注入方法实现的方式非常有趣,暂时还不了解原理,后面可以研究一下源码)

调用过程
@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 result = repository.findById(1L);
        return result.get();
    }

    @RequestMapping(value = "/find")
    public List find(String name,Pageable page){
        return repository.findByName(name, page);
    }

    @GetMapping(value = "/search")
    public Page 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 sampleEntities = elasticsearchTemplate.queryForPage(searchQuery, TestGoodsBo.class);

//        //Pageable对象的手动实现
//        Sort sort = new Sort(Sort.Direction.ASC,"name");
//        Pageable page = PageRequest.of(0,10,sort);

        Page 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 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倒序排列

你可能感兴趣的:(Springboot整合elasticsearch)