在上一篇學習SpringBoot中,整合了Mybatis、Druid和PageHelper並實現了多數據源的操做。本篇主要是介紹和使用目前最火的搜索引擎ElastiSearch,並和SpringBoot進行結合使用。html
ElasticSearch是一個基於Lucene的搜索服務器,其實就是對Lucene進行封裝,提供了 REST API 的操做接口 ElasticSearch做爲一個高度可拓展的開源全文搜索和分析引擎,可用於快速地對大數據進行存儲,搜索和分析。
ElasticSearch主要特色:分佈式、高可用、異步寫入、多API、面向文檔 。
ElasticSearch核心概念:近實時,集羣,節點(保存數據),索引,分片(將索引分片),副本(分片可設置多個副本) 。它能夠快速地儲存、搜索和分析海量數據。
ElasticSearch使用案例:維基百科、Stack Overflow、Github 等等。java
在使用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
首先,在使用以前,先作好相關的準備。github
<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>
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的時候,它須要在實體類中設置indexName 和type ,若是和傳統型數據庫比較的話,就至關於庫和表。須要注意的是indexName和type都必須是小寫!!!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服務端版本更改而對代碼進行更改!
首先在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的官方文檔。
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。 到此,本文結束,謝謝閱讀。