• SpringBoot整合ElasticSearch实现多版本的兼容


    前言

    上一篇学习SpringBoot中,整合了Mybatis、Druid和PageHelper并实现了多数据源的操作。本篇主要是介绍和使用目前最火的搜索引擎ElastiSearch,并和SpringBoot进行结合使用。

    ElasticSearch介绍

    ElasticSearch是一个基于Lucene的搜索服务器,其实就是对Lucene进行封装,提供了 REST API 的操作接口 ElasticSearch作为一个高度可拓展的开源全文搜索和分析引擎,可用于快速地对大数据进行存储,搜索和分析。
    ElasticSearch主要特点:分布式、高可用、异步写入、多API、面向文档 。
    ElasticSearch核心概念:近实时,集群,节点(保存数据),索引,分片(将索引分片),副本(分片可设置多个副本) 。它可以快速地储存、搜索和分析海量数据。
    ElasticSearch使用案例:维基百科、Stack Overflow、Github 等等。

    SpringBoot整合Elasticsearch

    在使用SpringBoot整合Elasticsearch 之前,我们应该了解下它们之间对应版本的关系。

    Spring Boot Version (x) Spring Data Elasticsearch Version (y) Elasticsearch Version (z)
    x <= 1.3.5 y <= 1.3.4 z <= 1.7.2*
    x >= 1.4.x 2.0.0 <=y < 5.0.0** 2.0.0 <= z < 5.0.0**

    这里我们使用的SpringBoot的版本是1.5.9,Elasticsearch的版本是2.3.5。

    使用SpringBoot整合Elasticsearch,一般都是使用 SpringData 进行封装的,然后再dao层接口继承ElasticsearchRepository 类,该类实现了很多的方法,比如常用的CRUD方法。

    SpringData的使用

    首先,在使用之前,先做好相关的准备。

    Maven的配置如下:

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                 <version>1.5.9.RELEASE</version>
            </dependency>
      <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
                 <version>1.5.9.RELEASE</version>
            </dependency>
    

    application.properties的配置

    spring.data.elasticsearch.repositories.enabled = true
    spring.data.elasticsearch.cluster-nodes =127.0.0.1:9300
    

    注: 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。

    更多的配置:

    spring.data.elasticsearch.cluster-name Elasticsearch 集群名。(默认值: elasticsearch)
    spring.data.elasticsearch.cluster-nodes 集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
    spring.data.elasticsearch.propertie 用来配置客户端的额外属性。
    spring.data.elasticsearch.repositories.enabled 开启 Elasticsearch 仓库。(默认值:true。)
    

    代码编写

    实体类

    @Document(indexName = "userindex", type = "user")
    public class User implements Serializable{
    	 /**
    	 * 
    	 */
    	private static final long serialVersionUID = 1L;
    	/** 编号 */
    	 private Long id;
    	 /** 姓名 */
    	 private String name;
    	 
    	 /** 年龄 */
    	 private Integer age;
    	 
    	 /** 描述 */  
    	 private String description;
    	 
    	 /** 创建时间 */
    	 private String createtm;
    
    	// getter和setter 略
    }	 
    
    

    使用SpringData的时候,它需要在实体类中设置indexNametype ,如果和传统型数据库比较的话,就相当于。需要注意的是indexNametype都必须是小写!!!

    dao层

    public interface UserDao extends ElasticsearchRepository<User, Long>{
    }
    

    dao层这里就比较简单了,只需继承ElasticsearchRepository该类就行了。其中主要的方法就是 save、delete和search。其中save方法相当如insert和update,没有就新增,有就覆盖。delete方法主要就是删除数据以及索引库。至于search就是查询了,包括一些常用的查询,如分页、权重之类的。

    Service层

    @Service
    public class UserServiceImpl implements UserService {
    	@Autowired
        private UserDao userDao;
    	@Override
    	public boolean insert(User user) {
    		boolean falg=false;
    		try{
    			userDao.save(user);
    			falg=true;
    		}catch(Exception e){
    			e.printStackTrace();
    		}
    		return falg;
    	}
    
    	@Override
    	public List<User> search(String searchContent) {
    		  QueryStringQueryBuilder builder = new QueryStringQueryBuilder(searchContent);
    		  System.out.println("查询的语句:"+builder);
              Iterable<User> searchResult = userDao.search(builder);
              Iterator<User> iterator = searchResult.iterator();
              List<User> list=new ArrayList<User>();
              while (iterator.hasNext()) {
           	   	list.add(iterator.next());
              }
           return list;
    	}
    	
    	
    	
    	@Override
    	public List<User> searchUser(Integer pageNumber, Integer pageSize,String searchContent) {
    		 // 分页参数
            Pageable pageable = new PageRequest(pageNumber, pageSize);
            QueryStringQueryBuilder builder = new QueryStringQueryBuilder(searchContent);
            SearchQuery searchQuery = new NativeSearchQueryBuilder().withPageable(pageable).withQuery(builder).build();
            System.out.println("查询的语句:" + searchQuery.getQuery().toString());
            Page<User> searchPageResults = userDao.search(searchQuery);
            return searchPageResults.getContent();
    	}
    	
    
    	@Override
    	public List<User> searchUserByWeight(String searchContent) {
    	 // 根据权重进行查询
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
                    .add(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("name", searchContent)),
                        ScoreFunctionBuilders.weightFactorFunction(10))
                    .add(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("description", searchContent)),
                            ScoreFunctionBuilders.weightFactorFunction(100)).setMinScore(2);
            System.out.println("查询的语句:" + functionScoreQueryBuilder.toString());
            Iterable<User> searchResult = userDao.search(functionScoreQueryBuilder);
            Iterator<User> iterator = searchResult.iterator();
            List<User> list=new ArrayList<User>();
            while (iterator.hasNext()) {
         	   	list.add(iterator.next());
            }
            return list;
    	}
    }
    

    这里我就简单的写了几个方法,其中主要的方法是查询。查询包括全文搜索,分页查询和权重查询。其中需要说明的是权重查询这块,权重的分值越高,查询的结果也越靠前,如果没有对其它的数据设置分值,它们默认的分值就是1,如果不想查询这些语句,只需使用setMinScore将其设为大于1即可。

    代码测试

    调用接口进行添加数据

    新增数据:

    POST http://localhost:8086/api/user
    
    {"id":1,"name":"张三","age":20,"description":"张三是个Java开发工程师","createtm":"2018-4-25 11:07:42"}
    {"id":2,"name":"李四","age":24,"description":"李四是个测试工程师","createtm":"1980-2-15 19:01:32"}
    {"id":3,"name":"王五","age":25,"description":"王五是个运维工程师","createtm":"2016-8-21 06:11:32"}
    

    进行全文查询
    请求

    http://localhost:8086/api/user?searchContent=工程师
    

    返回

    [{"id":2,"name":"李四","age":14,"description":"李四是个测试工程师","createtm": "1980-2-15 19:01:32"},
    {"id":1,"name":"张三","age":20,"description":"张三是个Java开发工程师", "createtm": "2018-4-25 11:07:42"},
    {"id":3,"name":"王五","age":25,"description":"王五是个运维工程师","createtm": "2016-8-21 06:11:32"}]
    

    进行分页查询
    请求

    http://localhost:8086/api/user?pageNumber=0&pageSize=2&searchContent=工程师
    

    返回

    [{"id":2,"name":"李四","age":14,"description":"李四是个测试工程师"},{"id":1,"name":"张三","age":20,"description":"张三是个Java开发工程师"}]
    

    进行权重查询
    请求

    http://localhost:8086/api/user2?searchContent=李四
    

    返回

    [{"id":2,"name":"李四","age":24,"description":"李四是个测试工程师","createtm":"1980-2-15 19:01:32"}]
    

    权重查询打印的语句:

    查询的语句:{{
      "function_score" : {
        "functions" : [ {
          "filter" : {
            "bool" : {
              "should" : {
                "match" : {
                  "name" : {
                    "query" : "李四",
                    "type" : "boolean"
                  }
                }
              }
            }
          },
          "weight" : 10.0
        }, {
          "filter" : {
            "bool" : {
              "should" : {
                "match" : {
                  "description" : {
                    "query" : "李四",
                    "type" : "boolean"
                  }
                }
              }
            }
          },
          "weight" : 100.0
        } ],
        "min_score" : 2.0
      }
    }
    
    

    注:测试中,因为设置了setMinScore最小权重分为2的,所以无关的数据是不会显示出来的。如果想显示的话,在代码中去掉即可。

    新增完数据之后,可以在浏览器输入:http://localhost:9200/_plugin/head/
    然后点击基本查询,便可以查看添加的数据。如果想用语句查询,可以将程序中控制台打印的查询语句粘贴到查询界面上进行查询!

    注:这里的ElasticSearch是我在windows上安装的,并安装了ES插件head,具体安装步骤在文章末尾。

    除了SpringData之外,其实还有其它的方法操作ElasticSearch的。
    比如使用原生ElasticSearch的Api,使用TransportClient类实现。
    或者使用由Spring封装,只需在Service层,进行注入Bean即可。
    示例:

    @Autowired
     ElasticsearchTemplate elasticsearchTemplate; 
    

    但是,上述方法中都有其局限性,也就是随着ElasticSearch的版本变更,相关的Java API也在做不断的调整,就是ElasticSearch服务端版本进行更改之后,客户端的代码可能需要重新编写。
    因此介绍一个相当好用的第三方工具JestClient,它对ElasticSearch进行封装,填补了 ElasticSearch HttpRest接口 客户端的空白,它适用于ElasticSearch2.x以上的版本,无需因为ElasticSearch服务端版本更改而对代码进行更改!

    JestClient

    首先在Maven中添加如下依赖:

    	<dependency>
        	<groupId>io.searchbox</groupId> 
       		 <artifactId>jest</artifactId>
        	<version>5.3.3</version>
    	</dependency>
    

    然后编写相关的测试代码。
    代码中的注释应该很完整,所以这里就不再对代码过多的讲述了。

    import java.util.ArrayList;
    import java.util.List;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import com.pancm.pojo.User;
    import io.searchbox.client.JestClient;
    import io.searchbox.client.JestClientFactory;
    import io.searchbox.client.JestResult;
    import io.searchbox.client.config.HttpClientConfig;
    import io.searchbox.core.Bulk;
    import io.searchbox.core.BulkResult;
    import io.searchbox.core.Delete;
    import io.searchbox.core.DocumentResult;
    import io.searchbox.core.Index;
    import io.searchbox.core.Search;
    import io.searchbox.indices.CreateIndex;
    import io.searchbox.indices.DeleteIndex;
    import io.searchbox.indices.mapping.GetMapping;
    import io.searchbox.indices.mapping.PutMapping;
    
    public class JestTest {  
    	    private static JestClient jestClient;  
    	    private static String indexName = "userindex";  
    //	    private static String indexName = "userindex2";  
    	    private static String typeName = "user";  
    	    private static String elasticIps="http://192.169.2.98:9200";
    //	    private static String elasticIps="http://127.0.0.1:9200";
    		
    	    
    	    public static void main(String[] args) throws Exception {
    	        jestClient = getJestClient();  
    	        insertBatch();
    	        serach1();
    	        serach2();
    	        serach3();
    	        jestClient.close();  
    	        
    		}
    	    
    	    private static  JestClient getJestClient() {  
    	    	JestClientFactory factory = new JestClientFactory();  
    			factory.setHttpClientConfig(new HttpClientConfig.Builder(elasticIps).connTimeout(60000).readTimeout(60000).multiThreaded(true).build());  
    	        return factory.getObject();  
    	    }  
    	    
    	    public static void insertBatch() {
    			List<Object> objs = new ArrayList<Object>();
    			objs.add(new User(1L, "张三", 20, "张三是个Java开发工程师","2018-4-25 11:07:42"));
    			objs.add(new User(2L, "李四", 24, "李四是个测试工程师","1980-2-15 19:01:32"));
    			objs.add(new User(3L, "王五", 25, "王五是个运维工程师","2016-8-21 06:11:32"));
    			boolean result = false;
    			try {
    				result = insertBatch(jestClient,indexName, typeName,objs);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    			System.out.println("批量新增:"+result);
    		}
    	    
    	    
    	    /**
    	     * 全文搜索
    	     */
    	    public static void serach1() {
    			String query ="工程师";
    			try {
    				SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); 
    		    	 searchSourceBuilder.query(QueryBuilders.queryStringQuery(query)); 
    		    	 //分页设置
    		    	 searchSourceBuilder.from(0).size(2); 
    		        System.out.println("全文搜索查询语句:"+searchSourceBuilder.toString());
    				System.out.println("全文搜索返回结果:"+search(jestClient,indexName, typeName, searchSourceBuilder.toString()));
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	    
    	    /**
    	     * 精确搜索
    	     */
    	    public static void serach2() {
    			try {
    				SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); 
    		    	searchSourceBuilder.query(QueryBuilders.termQuery("age", 24)); 
    		    	System.out.println("精确搜索查询语句:"+searchSourceBuilder.toString());
    				System.out.println("精确搜索返回结果:"+search(jestClient,indexName, typeName, searchSourceBuilder.toString()));
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	    
    	    
    	    /**
    	     * 区间搜索
    	     */
    	    public static void serach3() {
    			String createtm="createtm";
    			String from="2016-8-21 06:11:32";
    			String to="2018-8-21 06:11:32";
    			
    			try {
    				SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); 
    		    	searchSourceBuilder.query(QueryBuilders.rangeQuery(createtm).gte(from).lte(to)); 
    		    	System.out.println("区间搜索语句:"+searchSourceBuilder.toString());
    				System.out.println("区间搜索返回结果:"+search(jestClient,indexName, typeName, searchSourceBuilder.toString()));
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	    
    	    
    	    /**
    	     * 创建索引
    	     * @param indexName
    	     * @return
    	     * @throws Exception
    	     */
    	    public boolean createIndex(JestClient jestClient,String indexName) throws Exception {  
    	        JestResult jr = jestClient.execute(new CreateIndex.Builder(indexName).build());  
    	        return jr.isSucceeded();  
    	    }  
    	      
    	    /**
    	     * 新增数据
    	     * @param indexName
    	     * @param typeName
    	     * @param source
    	     * @return
    	     * @throws Exception
    	     */
    	    public boolean insert(JestClient jestClient,String indexName, String typeName, String source) throws Exception {  
    	        PutMapping putMapping = new PutMapping.Builder(indexName, typeName, source).build();  
    	        JestResult jr = jestClient.execute(putMapping);  
    	        return jr.isSucceeded();  
    	    }  
    	      
    	    
    	     /**
    	      * 查询数据
    	      * @param indexName
    	      * @param typeName
    	      * @return
    	      * @throws Exception
    	      */
    	    public static String getIndexMapping(JestClient jestClient,String indexName, String typeName) throws Exception {  
    	        GetMapping getMapping = new GetMapping.Builder().addIndex(indexName).addType(typeName).build();  
    	        JestResult jr =jestClient.execute(getMapping);  
    	        return jr.getJsonString();  
    	     }  
    	      
    	    
    	    
    	   /**
    	    * 批量新增数据
    	    * @param indexName
    	    * @param typeName
    	    * @param objs
    	    * @return
    	    * @throws Exception
    	    */
    	    public static boolean insertBatch(JestClient jestClient,String indexName, String typeName, List<Object> objs) throws Exception {  
    	        Bulk.Builder bulk = new Bulk.Builder().defaultIndex(indexName).defaultType(typeName);  
    	        for (Object obj : objs) {  
    	            Index index = new Index.Builder(obj).build();  
    	             bulk.addAction(index);  
    	        }  
    	        BulkResult br = jestClient.execute(bulk.build());  
    	        return br.isSucceeded();  
    	       }  
    	      
    	    /**
    	     * 全文搜索
    	     * @param indexName
    	     * @param typeName
    	     * @param query
    	     * @return
    	     * @throws Exception
    	     */
    	    public static String search(JestClient jestClient,String indexName, String typeName, String query) throws Exception {  
    	    	 Search search = new Search.Builder(query)
    	    	 .addIndex(indexName)
    	    	 .addType(typeName)  
    	    	 .build(); 
    	        JestResult jr = jestClient.execute(search);  
    //	        System.out.println("--"+jr.getJsonString());
    //	        System.out.println("--"+jr.getSourceAsObject(User.class));
    	        return jr.getSourceAsString();  
    	     }  
    	      
    	      
    	    
    	   
    	      
    	   /**
    	    * 删除索引
    	    * @param indexName
    	    * @return
    	    * @throws Exception
    	    */
    	    public boolean delete(JestClient jestClient,String indexName) throws Exception {  
    	        JestResult jr = jestClient.execute(new DeleteIndex.Builder(indexName).build());  
    	        return jr.isSucceeded();  
    	    }  
    	      
    	   /**
    	    * 删除数据
    	    * @param indexName
    	    * @param typeName
    	    * @param id
    	    * @return
    	    * @throws Exception
    	    */
    	    public boolean delete(JestClient jestClient,String indexName, String typeName, String id) throws Exception {  
    	        DocumentResult dr = jestClient.execute(new Delete.Builder(id).index(indexName).type(typeName).build());  
    	        return dr.isSucceeded();  
    	    }  
    

    注:测试之前先说明下,本地windows系统安装的是ElasticSearch版本是2.3.5,linux服务器上安装的ElasticSearch版本是6.2。

    测试结果

    全文搜索

    全文搜索查询语句:{
      "from" : 0,
      "size" : 2,
      "query" : {
        "query_string" : {
          "query" : "工程师"
        }
      }
    }
    
    全文搜索返回结果:{"id":1,"name":"张三","age":20,"description":"张三是个Java开发工程师","createtm":"2018-4-25 11:07:42"},{"id":2,"name":"李四","age":24,"description":"李四是个测试工程师","createtm":"1980-2-15 19:01:32"}
    

    匹配搜索

    精确搜索查询语句:{
      "query" : {
        "term" : {
          "age" : 24
        }
      }
    }
    
    精确搜索返回结果:{"id":2,"name":"李四","age":24,"description":"李四是个测试工程师","createtm":"1980-2-15 19:01:32"}
    

    时间区间搜索

    区间搜索语句:{
      "query" : {
        "range" : {
          "createtm" : {
            "from" : "2016-8-21 06:11:32",
            "to" : "2018-8-21 06:11:32",
            "include_lower" : true,
            "include_upper" : true
          }
        }
      }
    }
    区间搜索返回结果:{"id":1,"name":"张三","age":20,"description":"张三是个Java开发工程师","createtm":"2018-4-25 11:07:42"}
    

    新增完数据之后,我们可以上linux的 Kibana中进行相关的查询,查询结果如下:

    注:Kibana 是属于ELK中一个开源软件。Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。

    上述代码中测试返回的结果符合我们的预期。其中关于JestClient只是用到了很少的一部分,更多的使用可以查看JestClient的官方文档。

    Windows安装ElasticSearch

    1,文件准备
    下载地址:
    https://www.elastic.co/downloads
    选择ElasticSearch相关版本, 然后选择后缀名为ZIP文件进行下载,下载之后进行解压。

    2,启动Elasticsearch
    进入bin目录下,运行 elasticsearch.bat
    然后在浏览上输入: localhost:9200
    成功显示一下界面表示成功!

    3,安装ES插件
    web管理界面head 安装
    进入bin目录下,打开cmd,进入dos界面
    输入:plugin install mobz/elasticsearch-head
    进行下载
    成功下载之后,在浏览器输入:http://localhost:9200/_plugin/head/
    若显示一下界面,则安装成功!

    4,注册服务
    进入bin目录下,打开cmd,进入dos界面
    依次输入:
    service.bat install
    service.bat start
    成功之后,再输入
    services.msc
    跳转到Service服务界面,可以直接查看es的运行状态!

    其它

    ElasticSearch官网API地址:
    https://www.elastic.co/guide/en/elasticsearch/client/java-api/2.3/index.html

    JestClientGithub地址:
    https://github.com/searchbox-io/Jest

    项目我放到github上面去了。
    https://github.com/xuwujing/springBoot

    ------------- 分割线 2019-06-19---------------------

    最新看到评论说SpringBoot官方整合的Elasticsearch版本太低了,查看官网发现,如果想在SpringBoot中想使用更高的Elasticsearch版本,可以使用 spring-data-elasticsearch 这个spring的架包,它们版本的对应关系如下:

    Spring Data Elasticsearch Elasticsearch Version (z)
    3.2.x 6.7.2
    3.1.x 6.2.2
    3.0.x 5.5.0

    官网地址: https://github.com/spring-projects/spring-data-elasticsearch
    对应的SpringBoot2.x整合ElasticSearch项目地址: https://github.com/xuwujing/springBoot-study/tree/master/springboot2-elasticsearch

    如果觉得不错,希望顺便给个star。
    到此,本文结束,谢谢阅读。

    版权声明:
    作者:虚无境
    博客园出处:http://www.cnblogs.com/xuwujing
    CSDN出处:http://blog.csdn.net/qazwsxpcm    
    个人博客出处:http://www.panchengming.com
    原创不易,转载请标明出处,谢谢!

  • 相关阅读:
    34. 在排序数组中查找元素的第一个和最后一个位置
    153. 寻找旋转排序数组中的最小值
    278. 第一个错误的版本
    540. 有序数组中的单一元素
    744. 寻找比目标字母大的最小字母
    69. x 的平方根
    763. 划分字母区间
    53. 最大子序和
    665. 非递减数列
    Zabbix探索:Agent配置中Hostname错误引起的Agent.Ping报错
  • 原文地址:https://www.cnblogs.com/xuwujing/p/8998168.html
Copyright © 2020-2023  润新知