Spring Boot集成ElasticSearch实现搜索引擎的示例

Elastic Search是一个开源的,分布式,实时搜索和分析引擎。Spring Boot为Elasticsearch及Spring Data Elasticsearch提供的基于它的抽象提供了基本的配置。Spring Boot提供了一个用于聚集依赖的spring-boot-starter-data-elasticsearch 'StarterPOM'。

ElasticSearch作为搜索引擎,我们需要解决2大问题:

1,  如何将被搜索的数据在ES上创建反向索引
2,  Java代码如何与ES交互

其中第一个大问题又分为两个小问题

1.1,如何初始化已有的数据
1.2,如何同步增量数据

第二个大问题也有两种集成方式

2.1 Spring Data 9300端口集成
2.2 Restful API 9200端口集成

本篇先解决第二大问题。

第一种方式,利用RestAPI方式,也叫Jest方式:

示例代码:https://github.com/yejingtao/forblog/tree/master/demo-jest-elasticsearch

Pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion> 

 <groupId>yejingtao.demo.springcloud</groupId>
 <artifactId>demo-jest-elasticsearch</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging> 

 <name>demo-jest-elasticsearch</name>
 <url>http://maven.apache.org</url> 

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties> 

 <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.6.RELEASE</version>
  </parent> 

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    <dependency>
      <groupId>io.searchbox</groupId>
      <artifactId>jest</artifactId>
    </dependency>
    <dependency>
      <groupId>net.java.dev.jna</groupId>
      <artifactId>jna</artifactId>
    </dependency>
  </dependencies>
</project>

Application.yml:

server:
 port: 7081 

spring:
 elasticsearch:
  jest:
   uris:
   - http://192.168.226.133:9200
   read-timeout: 5000

注意这里是9200端口

主程序:最简单的Spring boot启动程序:

@SpringBootApplication
public class ESApplication { 

  public static void main(String[] args) {
    SpringApplication.run(ESApplication.class);
  }
} 

定义好ES中的实体类和对ES操作的接口:

public class Entity implements Serializable{ 

  private static final long serialVersionUID = -763638353551774166L; 

  public static final String INDEX_NAME = "index_entity"; 

  public static final String TYPE = "tstype"; 

  private Long id; 

  private String name; 

  public Entity() {
    super();
  } 

  public Entity(Long id, String name) {
    this.id = id;
    this.name = name;
  } 

  public Long getId() {
    return id;
  } 

  public void setId(Long id) {
    this.id = id;
  } 

  public String getName() {
    return name;
  } 

  public void setName(String name) {
    this.name = name;
  } 

}
public interface CityESService { 

  void saveEntity(Entity entity); 

  void saveEntity(List<Entity> entityList); 

  List<Entity> searchEntity(String searchContent);
}

接口实现:

@Service
public class CityESServiceImpl implements CityESService{ 

  private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class); 

  @Autowired
  private JestClient jestClient; 

  @Override
  public void saveEntity(Entity entity) {
    Index index = new Index.Builder(entity).index(Entity.INDEX_NAME).type(Entity.TYPE).build();
    try {
      jestClient.execute(index);
      LOGGER.info("ES 插入完成");
    } catch (IOException e) {
      e.printStackTrace();
      LOGGER.error(e.getMessage());
    }
  } 

  /**
   * 批量保存内容到ES
   */
  @Override
  public void saveEntity(List<Entity> entityList) {
    Bulk.Builder bulk = new Bulk.Builder();
    for(Entity entity : entityList) {
      Index index = new Index.Builder(entity).index(Entity.INDEX_NAME).type(Entity.TYPE).build();
      bulk.addAction(index);
    }
    try {
      jestClient.execute(bulk.build());
      LOGGER.info("ES 插入完成");
    } catch (IOException e) {
      e.printStackTrace();
      LOGGER.error(e.getMessage());
    }
  } 

  /**
   * 在ES中搜索内容
   */
  @Override
  public List<Entity> searchEntity(String searchContent){
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    //searchSourceBuilder.query(QueryBuilders.queryStringQuery(searchContent));
    //searchSourceBuilder.field("name");
    searchSourceBuilder.query(QueryBuilders.matchQuery("name",searchContent));
    Search search = new Search.Builder(searchSourceBuilder.toString())
        .addIndex(Entity.INDEX_NAME).addType(Entity.TYPE).build();
    try {
      JestResult result = jestClient.execute(search);
      return result.getSourceAsObjectList(Entity.class);
    } catch (IOException e) {
      LOGGER.error(e.getMessage());
      e.printStackTrace();
    }
    return null;
  }
}

这里插入数据的方式给了两种,一种是单次API直接插入,一种是利用ES的bulk批量插入。

做一个controller方面我们测试:

启动后在浏览器中请求http://localhost:7081/entityController/search?name=%E4%BA%BA%E6%89%8B%E4%BA%95

得到结果:

这里只返回了9条记录,而理论上ES默认的size是10,应该不是分页的问题,而是只能检索出9条匹配记录,用Kibana连上相同的搜索确认下:

这里用的是standard分词方式,将每个中文都作为了一个term,凡是包含“人”“手”“井”的都被搜索了出来,只是评分不同,如果想支持只能中文索引需要依赖ik插件

OK,RestFul方式对ElasticSearch的检索已经搞定了,更多的扩展可以慢慢研究下QueryBuilders里的源码和批注。

第二种方式,利用Spring Data客户端方式:

事先说明此方式有个弊端,让我掉了坑里好久才爬上来,Spring Data ElasticSearch必须与ElasticSearch版本相匹配,否则在对接时ES端会报版本不匹配错误,例如我ES是5.6.1版本,Spring boot是1.5.6版本,错误如下:

为解决这个问题我查找了一些资料,Spring Data与elasticsearch版本对应关系如下:


spring data elasticsearch


elasticsearch


3.0.0.RC2


5.5.0


3.0.0.M4


5.4.0


2.0.4.RELEASE


2.4.0


2.0.0.RELEASE


2.2.0


1.4.0.M1


1.7.3


1.3.0.RELEASE


1.5.2


1.2.0.RELEASE


1.4.4


1.1.0.RELEASE


1.3.2


1.0.0.RELEASE


1.1.1

而我用的Spring Boot 1.5.6版本对应的Spring Data ElasticSearch是2.1.6版本,不支持5.X的ES,所以报错。到本博文撰写为止,Spring Boot的RELEASE版本最新的是1.5.8,对应的Spring Data ElasticSearch是2.1.8,仍不支持5.X的ES,所以如果一定要使用Java客户端方式集成ES只能放弃Spring Boot直接使用Spring Data和Spring MVC,或者降低ES的版本使之与Spring boot匹配。

示例代码:https://github.com/yejingtao/forblog/tree/master/demo-data-elasticsearch

pom.xml依赖:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion> 

 <groupId>yejingtao.demo.springcloud</groupId>
 <artifactId>demo-data-elasticsearch</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging> 

 <name>demo-data-elasticsearch</name>
 <url>http://maven.apache.org</url> 

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties> 

 <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.8.RELEASE</version>
  </parent> 

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
  </dependencies>
</project>

不再引用Jest。

application.yml:

server:
 port: 7081 

spring:
 data:
  elasticsearch:
   cluster-nodes: 192.168.226.133:9300
   cluster-name: my-es
   repositories:
    enabled: true

注意这里是9300端口

Controller、主程序、Service接口同Jest项目不变,不再罗列

实体类稍作变化,指定ES中的index和type:

@Document(indexName="index_entity", type="tstype")

多一个Repository接口,无需实现类,spring data标准用法:

/**
 * Entity ES操作类
 * @author yejingtao
 *
 */
public interface EntityRepository extends ElasticsearchRepository<Entity,Long>{ 

}

Service实现类与Jest的天壤之别了,从语法上可以看出更像是对数据库层的操作:

@Service
public class CityESServiceImpl implements CityESService{ 

  private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class); 

  int PAGE_SIZE = 15; //默认分页大小 

  int PAGE_NUMBER = 0; //默认当前分页 

  String SCORE_MODE_SUM = "sum"; //权重分求和模式 

  Float MIN_SCORE = 10.0F; //由于无相关性的分值默认为1, 设置权重分最小值为10 

  @Autowired
  EntityRepository entityRepository; 

  /**
   * 保存内容到ES
   */
  @Override
  public Long saveEntity(Entity entity) {
    Entity entityResult = entityRepository.save(entity);
    return entityResult.getId();
  } 

  /**
   * 在ES中搜索内容
   */
  @Override
  public List<Entity> searchEntity(int pageNumber, int pageSize, String searchContent){
    if(pageSize==0) {
      pageSize = PAGE_SIZE;
    }
    if(pageNumber<0) {
      pageNumber = PAGE_NUMBER;
    } 

    SearchQuery searchQuery = getEntitySearchQuery(pageNumber,pageSize,searchContent); 

    LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n "
        + searchQuery.getQuery().toString()); 

    Page<Entity> cityPage = entityRepository.search(searchQuery);
    return cityPage.getContent();
  } 

  /**
   * 组装搜索Query对象
   * @param pageNumber
   * @param pageSize
   * @param searchContent
   * @return
   */
  private SearchQuery getEntitySearchQuery(int pageNumber, int pageSize, String searchContent) {
    FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
        .add(QueryBuilders.matchPhraseQuery("name", searchContent),
            ScoreFunctionBuilders.weightFactorFunction(1000))
        //.add(QueryBuilders.matchPhraseQuery("other", searchContent),
            //ScoreFunctionBuilders.weightFactorFunction(1000))
        .scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);
    //设置分页,否则只能按照ES默认的分页给
    Pageable pageable = new PageRequest(pageNumber, pageSize);
    return new NativeSearchQueryBuilder().withPageable(pageable).withQuery(functionScoreQueryBuilder).build();
  } 

}

测试方式同Jest。

这两种方式,从设计上来讲属于两种思路,Spring Data的思路就是将ElasticSearch当自家的数据仓库来管理,直接通过Java客户端代码操作ES;Jest的思路是将ElasticSearch当为独立的服务端,自己作为客户端用兼容性最强的RestFul格式来与之交互。
个人比较倾向于Jest方式,第一兼容性好,不需要考虑版本的问题。第二,从ElasticSearch本身的设计上来分析,9200是对外服务端口,9300是内部管理和集群通信端口,请求9200获取搜索服务更符合ES的设计初衷,不会影响集群内部的通信。
以上比较分析仅代表个人观点,欢迎大神么交流批评。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解spring-boot集成elasticsearch及其简单应用

    介绍 记录将elasticsearch集成到spring boot的过程,以及一些简单的应用和helper类使用. 接入方式 使用spring-boot中的spring-data-elasticsearch,可以使用两种内置客户端接入 1.节点客户端(node client): 配置文件中设置为local:false,节点客户端以无数据节点(node-master或node-client)身份加入集群,换言之,它自己不存储任何数据,但是它知道数据在集群中的具体位置,并且能够直接转发请求到对应的节

  • SpringBoot整合ElasticSearch的示例代码

    ElasticSearch作为基于Lucene的搜索服务器,既可以作为一个独立的服务部署,也可以签入Web应用中.SpringBoot作为Spring家族的全新框架,使得使用SpringBoot开发Spring应用变得非常简单.本文要介绍如何整合ElasticSearch与SpringBoot. 实体设计: 每一本书(Book)都属于一个分类(Classify),都有一个作者(Author). 生成这个三个实体类,并实现其get和set方法. SpringBoot配置修改: 1.修改pom.xm

  • SpringBoot整合ElasticSearch实践

    本节我们基于一个发表文章的案例来说明SpringBoot如何elasticsearch集成.elasticsearch本身可以是一个独立的服务,也可以嵌入我们的web应用中,在本案例中,我们讲解如何将elasticsearch嵌入我们的应用中. 案例背景:每个文章(Article)都要属于一个教程(Tutorial),而且每个文章都要有一个作者(Author). 一.实体设计: Tutorial.java public class Tutorial implements Serializable

  • Spring Boot集成ElasticSearch实现搜索引擎的示例

    Elastic Search是一个开源的,分布式,实时搜索和分析引擎.Spring Boot为Elasticsearch及Spring Data Elasticsearch提供的基于它的抽象提供了基本的配置.Spring Boot提供了一个用于聚集依赖的spring-boot-starter-data-elasticsearch 'StarterPOM'. ElasticSearch作为搜索引擎,我们需要解决2大问题: 1,  如何将被搜索的数据在ES上创建反向索引 2,  Java代码如何与E

  • spring boot集成redisson的最佳实践示例

    目录 前言 集成jedis实例,xml方式 集成前引用的jar springbean配置xml 集成redisson实例,javabean的方式 集成前引入的jar javabean配置如下 提供实例化javabean application.properties添加如下配置 前言 本文假使你了解spring boot并实践过,非spring boot用户可跳过也可借此研究一下. redisson是redis的java客户端程序,国内外很多公司都有在用,如下, 和spring的集成中官方给出的实

  • Spring Boot 集成Elasticsearch模块实现简单查询功能

    目录 背景 系统集成 引入jar包 application.yml文件中添加ES配置 创建文档实体 接口实现 具体实现 基础查询 新增文档 请求参数 Controller实现 返回结果 修改文档 通过id查询文档信息 Controller实现 删除文档 Controller实现 分页查询 Controller实现 返回结果 模糊查询 Controller实现 范围查询 Controller实现 总结 背景 项目中我们经常会用搜索功能,普通的搜索我们可以用一个SQL的like也能实现匹配,但是搜索

  • 基于Spring Batch向Elasticsearch批量导入数据示例

    1.介绍 当系统有大量数据需要从数据库导入Elasticsearch时,使用Spring Batch可以提高导入的效率.Spring Batch使用ItemReader分页读取数据,ItemWriter批量写数据.由于Spring Batch没有提供Elastisearch的ItemWriter和ItemReader,本示例中自定义一个ElasticsearchItemWriter(ElasticsearchItemReader),用于批量导入. 2.示例 2.1 pom.xml 本文使用spr

  • Spring Boot整合Elasticsearch实现全文搜索引擎案例解析

    简单说,ElasticSearch(简称 ES)是搜索引擎,是结构化数据的分布式搜索引擎.Elastic Search是一个开源的,分布式,实时搜索和分析引擎.Spring Boot为Elasticsearch及Spring Data Elasticsearch提供的基于它的抽象提供了基本的配置.Spring Boot提供了一个用于聚集依赖的spring-boot-starter-data-elasticsearch 'StarterPOM'. 引入spring-boot-starter-dat

  • 使用Spring Boot集成FastDFS的示例代码

    这篇文章我们介绍如何使用Spring Boot将文件上传到分布式文件系统FastDFS中. 这个项目会在上一个项目的基础上进行构建. 1.pom包配置 我们使用Spring Boot最新版本1.5.9.jdk使用1.8.tomcat8.0. <dependency> <groupId>org.csource</groupId> <artifactId>fastdfs-client-java</artifactId> <version>

  • Spring Boot集成Kafka的示例代码

    本文介绍了Spring Boot集成Kafka的示例代码,分享给大家,也给自己留个笔记 系统环境 使用远程服务器上搭建的kafka服务 Ubuntu 16.04 LTS kafka_2.12-0.11.0.0.tgz zookeeper-3.5.2-alpha.tar.gz 集成过程 1.创建spring boot工程,添加相关依赖: <?xml version="1.0" encoding="UTF-8"?> <project xmlns=&qu

  • spring boot集成mongodb的增删改查的示例代码

    添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> properties配置 spring.data.mongodb.host=127.0.0.1 spring.data.mongodb.port=27017 spring

  • Spring Boot 集成 Kafkad的实现示例

    Spring Boot 作为主流微服务框架,拥有成熟的社区生态.市场应用广泛,为了方便大家,整理了一个基于spring boot的常用中间件快速集成入门系列手册,涉及RPC.缓存.消息队列.分库分表.注册中心.分布式配置等常用开源组件,大概有几十篇文章,陆续会开放出来,感兴趣同学请提前关注&收藏 消息通信有两种基本模型,即发布-订阅(Pub-Sub)模型和点对点(Point to Point)模型,发布-订阅支持生产者消费者之间的一对多关系,而点对点模型中有且仅有一个消费者. 前言 Kafka是

  • Spring Boot集成ShedLock分布式定时任务的实现示例

    一.ShedLock是什么? 官方地址:github.com/lukas-kreca- 以下是ShedLock锁提供者,通过外部存储实现锁,由下图可知外部存储集成的库还是很丰富的 本篇教程我们基于JdbcTemplate存储为例来使用ShedLock锁. 二.落地实现 1.1 引入依赖包 shedlock所需依赖包: <dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>she

随机推荐