怎么在SpringBoot中使用ElasticSearch实现一个搜索引擎

这篇文章将为大家详细讲解有关怎么在Spring Boot中使用ElasticSearch实现一个搜索引擎,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

坚守“ 做人真诚 · 做事靠谱 · 口碑至上 · 高效敬业 ”的价值观,专业网站建设服务10余年为成都玻璃钢雕塑小微创业公司专业提供成都企业网站建设营销网站建设商城网站建设手机网站建设小程序网站建设网站改版,从内容策划、视觉设计、底层架构、网页布局、功能开发迭代于一体的高端网站建设服务。

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:

 
 4.0.0 
 
 yejingtao.demo.springcloud 
 demo-jest-elasticsearch 
 0.0.1-SNAPSHOT 
 jar 
 
 demo-jest-elasticsearch 
 http://maven.apache.org 
 
  
  UTF-8 
  
  
  
    org.springframework.boot 
    spring-boot-starter-parent 
    1.5.6.RELEASE 
   
   
   
     
      org.springframework.boot 
      spring-boot-starter-web 
     
     
      org.springframework.boot 
      spring-boot-starter-data-elasticsearch 
     
     
      io.searchbox 
      jest 
     
     
      net.java.dev.jna 
      jna 
     
   

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 entityList); 
   
  List 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 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 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

得到结果:

怎么在Spring Boot中使用ElasticSearch实现一个搜索引擎

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

怎么在Spring Boot中使用ElasticSearch实现一个搜索引擎

这里用的是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依赖:

 
 4.0.0 
 
 yejingtao.demo.springcloud 
 demo-data-elasticsearch 
 0.0.1-SNAPSHOT 
 jar 
 
 demo-data-elasticsearch 
 http://maven.apache.org 
 
  
  UTF-8 
  
  
  
    org.springframework.boot 
    spring-boot-starter-parent 
    1.5.8.RELEASE 
   
   
   
     
      org.springframework.boot 
      spring-boot-starter-web 
     
     
      org.springframework.boot 
      spring-boot-starter-data-elasticsearch 
     
   

不再引用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{ 
 
}

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 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 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(); 
  } 
   
}

关于怎么在Spring Boot中使用ElasticSearch实现一个搜索引擎就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。


文章名称:怎么在SpringBoot中使用ElasticSearch实现一个搜索引擎
URL地址:http://hbruida.cn/article/jppegs.html