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依赖

    <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>实现,该模板提供了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<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倒序排列
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容