前言
在上一篇學習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的配置如下:
1
2
3
4
5
6
7
8
9
|
<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的配置
1
2
|
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。)
代碼編寫
實體類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@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都必須是小寫!!!
dao層
1
2
|
public interface userdao extends elasticsearchrepository<user, long >{ } |
dao層這里就比較簡單了,只需繼承elasticsearchrepository該類就行了。其中主要的方法就是 save、delete和search。其中save方法相當如insert和update,沒有就新增,有就覆蓋。delete方法主要就是刪除數據以及索引庫。至于search就是查詢了,包括一些常用的查詢,如分頁、權重之類的。
service層
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
@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即可。
代碼測試
調用接口進行添加數據
新增數據:
1
2
3
4
|
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" } |
進行全文查詢
請求
1
|
http: //localhost:8086/api/user?searchcontent=工程師 |
返回
1
2
3
|
[{ "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" }] |
進行分頁查詢
請求
1
|
http: //localhost:8086/api/user?pagenumber=0&pagesize=2&searchcontent=工程師 |
返回
1
|
[{ "id" : 2 , "name" : "李四" , "age" : 14 , "description" : "李四是個測試工程師" },{ "id" : 1 , "name" : "張三" , "age" : 20 , "description" : "張三是個java開發工程師" }] |
進行權重查詢
請求
1
|
http: //localhost:8086/api/user2?searchcontent=李四 |
返回
1
|
[{ "id" : 2 , "name" : "李四" , "age" : 24 , "description" : "李四是個測試工程師" , "createtm" : "1980-2-15 19:01:32" }] |
權重查詢打印的語句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
查詢的語句:{{ "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即可。
示例:
1
2
|
@autowired elasticsearchtemplate elasticsearchtemplate; |
但是,上述方法中都有其局限性,也就是隨著elasticsearch的版本變更,相關的java api也在做不斷的調整,就是elasticsearch服務端版本進行更改之后,客戶端的代碼可能需要重新編寫。
因此介紹一個相當好用的第三方工具jestclient,它對elasticsearch進行封裝,填補了 elasticsearch httprest接口 客戶端的空白,它適用于elasticsearch2.x以上的版本,無需因為elasticsearch服務端版本更改而對代碼進行更改!
jestclient
首先在maven中添加如下依賴:
1
2
3
4
5
|
<dependency> <groupid>io.searchbox</groupid> <artifactid>jest</artifactid> <version> 5.3 . 3 </version> </dependency> |
然后編寫相關的測試代碼。
代碼中的注釋應該很完整,所以這里就不再對代碼過多的講述了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
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。
測試結果
全文搜索
1
2
3
4
5
6
7
8
9
10
11
|
全文搜索查詢語句:{ "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" } |
匹配搜索
1
2
3
4
5
6
7
8
9
|
精確搜索查詢語句:{ "query" : { "term" : { "age" : 24 } } } 精確搜索返回結果:{ "id" : 2 , "name" : "李四" , "age" : 24 , "description" : "李四是個測試工程師" , "createtm" : "1980-2-15 19:01:32" } |
時間區間搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
|
區間搜索語句:{ "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界面
依次輸入:
1
2
|
service.bat install service.bat start |
成功之后,再輸入
1
|
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
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:http://www.cnblogs.com/xuwujing/p/8998168.html