ElasticSearch作为搜索引擎-Spring Boot集成

引用地址:https://yejingtao.blog.csdn.net/article/details/78414874

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:

 

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

  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  3. <modelVersion>4.0.0</modelVersion>

  4.  
  5. <groupId>yejingtao.demo.springcloud</groupId>

  6. <artifactId>demo-jest-elasticsearch</artifactId>

  7. <version>0.0.1-SNAPSHOT</version>

  8. <packaging>jar</packaging>

  9.  
  10. <name>demo-jest-elasticsearch</name>

  11. <url>http://maven.apache.org</url>

  12.  
  13. <properties>

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

  15. </properties>

  16.  
  17. <parent>

  18. <groupId>org.springframework.boot</groupId>

  19. <artifactId>spring-boot-starter-parent</artifactId>

  20. <version>1.5.6.RELEASE</version>

  21. </parent>

  22.  
  23. <dependencies>

  24. <dependency>

  25. <groupId>org.springframework.boot</groupId>

  26. <artifactId>spring-boot-starter-web</artifactId>

  27. </dependency>

  28. <dependency>

  29. <groupId>org.springframework.boot</groupId>

  30. <artifactId>spring-boot-starter-data-elasticsearch</artifactId>

  31. </dependency>

  32. <dependency>

  33. <groupId>io.searchbox</groupId>

  34. <artifactId>jest</artifactId>

  35. </dependency>

  36. <dependency>

  37. <groupId>net.java.dev.jna</groupId>

  38. <artifactId>jna</artifactId>

  39. </dependency>

  40. </dependencies>

  41. </project>

 

Application.yml:

 

server:
  port: 7081

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

 

注意这里是9200端口

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

 

 
  1. @SpringBootApplication

  2. public class ESApplication {

  3.  
  4. public static void main(String[] args) {

  5. SpringApplication.run(ESApplication.class);

  6. }

  7. }

 

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

 

 
  1. public class Entity implements Serializable{

  2.  
  3. private static final long serialVersionUID = -763638353551774166L;

  4.  
  5. public static final String INDEX_NAME = "index_entity";

  6.  
  7. public static final String TYPE = "tstype";

  8.  
  9. private Long id;

  10.  
  11. private String name;

  12.  
  13. public Entity() {

  14. super();

  15. }

  16.  
  17. public Entity(Long id, String name) {

  18. this.id = id;

  19. this.name = name;

  20. }

  21.  
  22. public Long getId() {

  23. return id;

  24. }

  25.  
  26. public void setId(Long id) {

  27. this.id = id;

  28. }

  29.  
  30. public String getName() {

  31. return name;

  32. }

  33.  
  34. public void setName(String name) {

  35. this.name = name;

  36. }

  37.  
  38.  
  39. }

 

 

 
  1. public interface CityESService {

  2.  
  3. void saveEntity(Entity entity);

  4.  
  5. void saveEntity(List<Entity> entityList);

  6.  
  7. List<Entity> searchEntity(String searchContent);

  8. }

 

接口实现:

 

 
  1. @Service

  2. public class CityESServiceImpl implements CityESService{

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

  5.  
  6. @Autowired

  7. private JestClient jestClient;

  8.  
  9. @Override

  10. public void saveEntity(Entity entity) {

  11. Index index = new Index.Builder(entity).index(Entity.INDEX_NAME).type(Entity.TYPE).build();

  12. try {

  13. jestClient.execute(index);

  14. LOGGER.info("ES 插入完成");

  15. } catch (IOException e) {

  16. e.printStackTrace();

  17. LOGGER.error(e.getMessage());

  18. }

  19. }

  20.  
  21.  
  22. /**

  23. * 批量保存内容到ES

  24. */

  25. @Override

  26. public void saveEntity(List<Entity> entityList) {

  27. Bulk.Builder bulk = new Bulk.Builder();

  28. for(Entity entity : entityList) {

  29. Index index = new Index.Builder(entity).index(Entity.INDEX_NAME).type(Entity.TYPE).build();

  30. bulk.addAction(index);

  31. }

  32. try {

  33. jestClient.execute(bulk.build());

  34. LOGGER.info("ES 插入完成");

  35. } catch (IOException e) {

  36. e.printStackTrace();

  37. LOGGER.error(e.getMessage());

  38. }

  39. }

  40.  
  41. /**

  42. * 在ES中搜索内容

  43. */

  44. @Override

  45. public List<Entity> searchEntity(String searchContent){

  46. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

  47. //searchSourceBuilder.query(QueryBuilders.queryStringQuery(searchContent));

  48. //searchSourceBuilder.field("name");

  49. searchSourceBuilder.query(QueryBuilders.matchQuery("name",searchContent));

  50. Search search = new Search.Builder(searchSourceBuilder.toString())

  51. .addIndex(Entity.INDEX_NAME).addType(Entity.TYPE).build();

  52. try {

  53. JestResult result = jestClient.execute(search);

  54. return result.getSourceAsObjectList(Entity.class);

  55. } catch (IOException e) {

  56. LOGGER.error(e.getMessage());

  57. e.printStackTrace();

  58. }

  59. return null;

  60. }

  61. }

 

这里插入数据的方式给了两种,一种是单次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插件,对于ik的详细介绍请见《ElasticSearch中文检索支持-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依赖:

 

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

  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  3. <modelVersion>4.0.0</modelVersion>

  4.  
  5. <groupId>yejingtao.demo.springcloud</groupId>

  6. <artifactId>demo-data-elasticsearch</artifactId>

  7. <version>0.0.1-SNAPSHOT</version>

  8. <packaging>jar</packaging>

  9.  
  10. <name>demo-data-elasticsearch</name>

  11. <url>http://maven.apache.org</url>

  12.  
  13. <properties>

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

  15. </properties>

  16.  
  17. <parent>

  18. <groupId>org.springframework.boot</groupId>

  19. <artifactId>spring-boot-starter-parent</artifactId>

  20. <version>1.5.8.RELEASE</version>

  21. </parent>

  22.  
  23. <dependencies>

  24. <dependency>

  25. <groupId>org.springframework.boot</groupId>

  26. <artifactId>spring-boot-starter-web</artifactId>

  27. </dependency>

  28. <dependency>

  29. <groupId>org.springframework.boot</groupId>

  30. <artifactId>spring-boot-starter-data-elasticsearch</artifactId>

  31. </dependency>

  32. </dependencies>

  33. </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标准用法:

 

 
  1. /**

  2. * Entity ES操作类

  3. * @author yejingtao

  4. *

  5. */

  6. public interface EntityRepository extends ElasticsearchRepository<Entity,Long>{

  7.  
  8. }

 

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

 

 
  1. @Service

  2. public class CityESServiceImpl implements CityESService{

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

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

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

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

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

  13.  
  14. @Autowired

  15. EntityRepository entityRepository;

  16.  
  17. /**

  18. * 保存内容到ES

  19. */

  20. @Override

  21. public Long saveEntity(Entity entity) {

  22. Entity entityResult = entityRepository.save(entity);

  23. return entityResult.getId();

  24. }

  25.  
  26. /**

  27. * 在ES中搜索内容

  28. */

  29. @Override

  30. public List<Entity> searchEntity(int pageNumber, int pageSize, String searchContent){

  31. if(pageSize==0) {

  32. pageSize = PAGE_SIZE;

  33. }

  34. if(pageNumber<0) {

  35. pageNumber = PAGE_NUMBER;

  36. }

  37.  
  38. SearchQuery searchQuery = getEntitySearchQuery(pageNumber,pageSize,searchContent);

  39.  
  40. LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n "

  41. + searchQuery.getQuery().toString());

  42.  
  43.  
  44. Page<Entity> cityPage = entityRepository.search(searchQuery);

  45. return cityPage.getContent();

  46. }

  47.  
  48. /**

  49. * 组装搜索Query对象

  50. * @param pageNumber

  51. * @param pageSize

  52. * @param searchContent

  53. * @return

  54. */

  55. private SearchQuery getEntitySearchQuery(int pageNumber, int pageSize, String searchContent) {

  56. FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()

  57. .add(QueryBuilders.matchPhraseQuery("name", searchContent),

  58. ScoreFunctionBuilders.weightFactorFunction(1000))

  59. //.add(QueryBuilders.matchPhraseQuery("other", searchContent),

  60. //ScoreFunctionBuilders.weightFactorFunction(1000))

  61. .scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);

  62. //设置分页,否则只能按照ES默认的分页给

  63. Pageable pageable = new PageRequest(pageNumber, pageSize);

  64. return new NativeSearchQueryBuilder().withPageable(pageable).withQuery(functionScoreQueryBuilder).build();

  65. }

  66.  
  67. }

 

测试方式同Jest。

 

这两种方式,从设计上来讲属于两种思路,Spring Data的思路就是将ElasticSearch当自家的数据仓库来管理,直接通过Java客户端代码操作ES;Jest的思路是将ElasticSearch当为独立的服务端,自己作为客户端用兼容性最强的RestFul格式来与之交互。

个人比较倾向于Jest方式,第一兼容性好,不需要考虑版本的问题。第二,从ElasticSearch本身的设计上来分析,9200是对外服务端口,9300是内部管理和集群通信端口,请求9200获取搜索服务更符合ES的设计初衷,不会影响集群内部的通信。

以上比较分析仅代表个人观点,欢迎大神么交流批评。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值