SpringBoot整合ElasticSearch實現多版本的兼容

前言

上一篇學習SpringBoot中,整合了Mybatis、Druid和PageHelper並實現了多數據源的操做。本篇主要是介紹和使用目前最火的搜索引擎ElastiSearch,並和SpringBoot進行結合使用。html

ElasticSearch介紹

ElasticSearch是一個基於Lucene的搜索服務器,其實就是對Lucene進行封裝,提供了 REST API 的操做接口 ElasticSearch做爲一個高度可拓展的開源全文搜索和分析引擎,可用於快速地對大數據進行存儲,搜索和分析。 ElasticSearch主要特色:分佈式、高可用、異步寫入、多API、面向文檔 。 ElasticSearch核心概念:近實時,集羣,節點(保存數據),索引,分片(將索引分片),副本(分片可設置多個副本) 。它能夠快速地儲存、搜索和分析海量數據。 ElasticSearch使用案例:維基百科、Stack Overflow、Github 等等。java

SpringBoot整合Elasticsearch

在使用SpringBoot整合Elasticsearch 以前,咱們應該瞭解下它們之間對應版本的關係。node

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。linux

使用SpringBoot整合Elasticsearch,通常都是使用 SpringData 進行封裝的,而後再dao層接口繼承ElasticsearchRepository 類,該類實現了不少的方法,好比經常使用的CRUD方法。git

SpringData的使用

首先,在使用以前,先作好相關的準備。github

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 的接口。web

更多的配置:spring

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都必須是小寫!!!windows

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

若是以爲不錯,但願順便給個star。 到此,本文結束,謝謝閱讀。

相關文章
相關標籤/搜索