在 “Springboot 系列 (19) - Springboot+ElasticSearch 实现全文搜索(一)” 里我们演示了安装配置 ElasticSearch 和使用 curl 调用 ElasticSearch API,本文将演示在 Springboot 项目里如何调用 ElasticSearch API。
1. 配置 ElasticSearch
1) 运行环境
操作系统:Ubuntu 20.04
内网 IP:192.168.0.10
端口: 9200
Java 版本: 1.8.0_341
ElasticSearch 版本:7.10.2
ElasticSearch 所在路径:~/apps/elasticsearch-7.10.2/
2) 启动
$ cd ~/apps/elasticsearch-7.10.2/bin
$ ./elasticsearch
... ERROR: [2] bootstrap checks failed [1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] [2]: the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured ...
注:启动失败,两个问题:1. jvm 内存不够大;2. 集群节点最低配置要求;
# 增加 jvm 内存到 262144
$ sudo sysctl -w vm.max_map_count=262144
vm.max_map_count = 262144
# 修改 elasticsearch.yml 文件,添加如下内容
$ vim ~/apps/elasticsearch-7.10.2/config/elasticsearch.yml
# 单机模式
discovery.type: single-node
# 再次启动
$ ./elasticsearch
2. Springboot 开发环境
Windows版本:Windows 10 Home (20H2)
IntelliJ IDEA (https://www.jetbrains.com/idea/download/):Community Edition for Windows 2020.1.4
Apache Maven (https://maven.apache.org/):3.8.1
注:Spring 开发环境的搭建,可以参考 “ Spring基础知识(1)- Spring简介、Spring体系结构和开发环境配置 ”。
3. 创建 Springboot 基础项目
项目实例名称:SpringbootExample19
Spring Boot 版本:2.6.6
创建步骤:
(1) 创建 Maven 项目实例 SpringbootExample19;
(2) Spring Boot Web 配置;
(3) 导入 Thymeleaf 依赖包;
(4) 配置 jQuery;
具体操作请参考 “Spring 系列 (2) - 在 Spring Boot 项目里使用 Thymeleaf、JQuery+Bootstrap 和国际化” 里的项目实例 SpringbootExample02,文末包含如何使用 spring-boot-maven-plugin 插件运行打包的内容。
SpringbootExample19 和 SpringbootExample02 相比,SpringbootExample19 不配置 Bootstrap、模版文件(templates/*.html)和国际化。
4. 添加 Elasticsearch 依赖
1) 修改 pom.xml,导入 Elasticsearch 依赖包
<project ... > ... <dependencies> ... <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> ... </dependencies> ... </project>
在IDE中项目列表 -> SpringbootExample19 -> 点击鼠标右键 -> Maven -> Reload Project
2) 修改 src/main/resources/application.properties 文件,添加如下配置
# ElasticSearch spring.elasticsearch.rest.uris=192.168.0.10:9200 spring.elasticsearch.rest.username=elastic spring.elasticsearch.rest.password=123456
5. 测试实例 (Web 模式)
1) 创建 src/main/java/com/example/config/ElasticSearchRestClientConfig.java 文件
1 package com.example.config; 2 3 import org.springframework.beans.factory.annotation.Value; 4 import org.springframework.context.annotation.Bean; 5 import org.springframework.context.annotation.Configuration; 6 import org.springframework.data.elasticsearch.client.ClientConfiguration; 7 import org.springframework.data.elasticsearch.client.RestClients; 8 import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration; 9 10 import org.elasticsearch.client.RestHighLevelClient; 11 12 @Configuration 13 public class ElasticSearchRestClientConfig extends AbstractElasticsearchConfiguration { 14 @Value("${spring.elasticsearch.rest.uris}") 15 private String uris; 16 @Value("${spring.elasticsearch.rest.username}") 17 private String username; 18 @Value("${spring.elasticsearch.rest.password}") 19 private String password; 20 21 @Override 22 @Bean 23 public RestHighLevelClient elasticsearchClient() { 24 25 final ClientConfiguration clientConfiguration = ClientConfiguration.builder() 26 .connectedTo(uris) 27 .withBasicAuth(username, password) 28 .build(); 29 30 return RestClients.create(clientConfiguration).rest(); 31 } 32 33 }
2) 创建 src/main/resources/templates/client.html 文件
1 <html lang="en" xmlns:th="http://www.thymeleaf.org"> 2 <head> 3 <meta charset="UTF-8"> 4 <title th:text="${var}">Title</title> 5 <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script> 6 </head> 7 <body> 8 9 <h3>ElasticSearch Client</h3> 10 <hr> 11 <p> 12 Operation:<br> 13 <select id="operation" name="operation" style="width: 50%;height: 32px;"> 14 <option value=""></option> 15 <option value="create_springboot">1. 创建一个索引 "springboot"</option> 16 <option value="get_springboot">2. 查看 "springboot"</option> 17 <option value="delete_springboot">3. 删除 "springboot"</option> 18 <option value="create_springboot_settings">4. 创建一个带 settings 的索引 "springboot"</option> 19 <option value="get_springboot_settings">5. 查看带 settings 的 "springboot"</option> 20 <option value="create_doc1_auto">6. 在 "springboot/doc1" 下创建一个文档(自动生成 _id)</option> 21 <option value="create_doc1_2">7. 在 "springboot/doc1/2" 下创建一个文档(指定 _id 为 2)</option> 22 <option value="create_doc1_batch">8. 在 "springboot/doc1" 下批量创建文档(自动生成 _id)</option> 23 <option value="get_doc1_2">9. 查看 "springboot/doc1/2"</option> 24 <option value="modify_doc1_2">10. 修改 "springboot/doc1/2"</option> 25 <option value="delete_doc1_2">11. 删除 "springboot/doc1/2"</option> 26 <option value="query_springboot_docs">12. 查询 "springboot" 下的全部文档 </option> 27 <option value="term_query_springboot_docs">13. 精准查询 "springboot" 下 name="Elastic Search" (_id=2) 的文档 </option> 28 <option value="match_query_springboot_docs">14. 模糊查询 "springboot" 下 name 包含 "Elastic Search" 的文档 </option> 29 </select> 30 </p> 31 <p id="raw_area" style="display:none;"> 32 Raw (JSON):<br> 33 <textarea name="raw" id="raw" rows="3" style="width: 50%;"></textarea> 34 </p> 35 <p id="raw_area_2" style="display:none;"> 36 Raw 2 (JSON):<br> 37 <textarea name="raw2" id="raw2" rows="3" style="width: 50%;"></textarea> 38 </p> 39 <p> 40 <button type="button" id="btn_submit">Submit</button> 41 </p> 42 43 <div id="result" style="width: 98%; height: 420px; background-color: #f2f2f2; padding: 5px;"></div> 44 45 <p> </p> 46 47 <script type="text/javascript"> 48 49 $(document).ready(function(){ 50 51 $('#btn_submit').click(function() { 52 53 var operation = $('#operation').val(); 54 if (operation == '') { 55 alert('Please select operation'); 56 $('#operation').focus(); 57 return; 58 } 59 var raw = $('#raw').val(); 60 var raw2 = $('#raw2').val(); 61 62 $.ajax({ 63 64 type: 'POST', 65 url: '/operation', 66 data: { 67 operation: operation, 68 raw: raw, 69 raw2: raw2 70 }, 71 success: function(response) { 72 console.log(response); 73 if (response.ret == "OK") { 74 $('#result').html("SUCCESS, (" + operation + "): " + response.description + "<br>"); 75 } else { 76 $('#result').html("FAILED, (" + operation + "): " + response.description + "<br>"); 77 } 78 }, 79 error: function(err) { 80 console.log(err); 81 $('#result').append("Error: " + err.responseText + "<br>"); 82 } 83 }); 84 }); 85 86 $('#operation').change(function() { 87 88 var operation = $('#operation').val(); 89 90 $('#raw_area').css('display', 'none'); 91 $('#raw_area_2').css('display', 'none'); 92 $('#raw').val(''); 93 $('#raw2').val(''); 94 95 if (operation == "create_springboot_settings") { 96 var strRaw = '{"number_of_shards":5,"number_of_replicas":1}'; 97 $('#raw').val(strRaw); 98 $('#raw_area').css('display', ''); 99 } else if (operation == "create_doc1_auto") { 100 var d = Date.now(); 101 var strRaw = '{"name":"Elastic Search - ' + d + '","author":"Elastic Company","count":3}'; 102 $('#raw').val(strRaw); 103 $('#raw_area').css('display', ''); 104 } else if (operation == "create_doc1_2") { 105 var strRaw = '{"name":"Elastic Search","author":"Elastic Company","count":8}'; 106 $('#raw').val(strRaw); 107 $('#raw_area').css('display', ''); 108 } else if (operation == "create_doc1_batch") { 109 var strRaw = '{"name":"Elastic Search 2","author":"Elastic Company 2","count":11}'; 110 var strRaw2 = '{"name":"Elastic Search 3","author":"Elastic Company 3","count":22}'; 111 $('#raw').val(strRaw); 112 $('#raw2').val(strRaw2); 113 $('#raw_area').css('display', ''); 114 $('#raw_area_2').css('display', ''); 115 } else if (operation == "modify_doc1_2") { 116 var strRaw = '{"count": 99}'; 117 $('#raw').val(strRaw); 118 $('#raw_area').css('display', ''); 119 } 120 }); 121 122 }); 123 124 </script> 125 126 </body> 127 </html>
3) 修改 src/main/java/com/example/controller/IndexController.java 文件
1 package com.example.controller; 2 3 import java.io.IOException; 4 import java.util.Map; 5 import java.util.HashMap; 6 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.stereotype.Controller; 9 import org.springframework.web.bind.annotation.RequestMapping; 10 import org.springframework.web.bind.annotation.ResponseBody; 11 import org.springframework.web.bind.annotation.RequestParam; 12 13 import org.elasticsearch.ElasticsearchStatusException; 14 import org.elasticsearch.common.xcontent.XContentType; 15 import org.elasticsearch.common.settings.Settings; 16 import org.elasticsearch.client.RestHighLevelClient; 17 import org.elasticsearch.client.RequestOptions; 18 import org.elasticsearch.client.indices.CreateIndexRequest; 19 import org.elasticsearch.client.indices.CreateIndexResponse; 20 import org.elasticsearch.client.indices.GetIndexRequest; 21 import org.elasticsearch.client.indices.GetIndexResponse; 22 23 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; 24 import org.elasticsearch.action.support.master.AcknowledgedResponse; 25 import org.elasticsearch.action.index.IndexRequest; 26 import org.elasticsearch.action.index.IndexResponse; 27 import org.elasticsearch.action.get.GetRequest; 28 import org.elasticsearch.action.get.GetResponse; 29 import org.elasticsearch.action.bulk.BulkRequest; 30 import org.elasticsearch.action.bulk.BulkResponse; 31 import org.elasticsearch.action.update.UpdateRequest; 32 import org.elasticsearch.action.update.UpdateResponse; 33 import org.elasticsearch.action.delete.DeleteRequest; 34 import org.elasticsearch.action.delete.DeleteResponse; 35 36 import org.elasticsearch.action.search.SearchRequest; 37 import org.elasticsearch.action.search.SearchResponse; 38 import org.elasticsearch.index.query.QueryBuilders; 39 import org.elasticsearch.search.builder.SearchSourceBuilder; 40 import org.elasticsearch.search.fetch.subphase.FetchSourceContext; 41 import org.elasticsearch.search.SearchHit; 42 43 @Controller 44 public class IndexController { 45 46 @Autowired 47 private RestHighLevelClient restHighLevelClient; 48 49 @ResponseBody 50 @RequestMapping("/test") 51 public String test() { 52 return "Test Page"; 53 } 54 55 @RequestMapping("/client") 56 public String client() { 57 return "client"; 58 } 59 60 @ResponseBody 61 @RequestMapping("/operation") 62 public Map<String, String> operationPost(@RequestParam String operation, 63 @RequestParam String raw, @RequestParam String raw2) { 64 65 Map<String, String> map = new HashMap(); 66 if ("".equals(operation)) { 67 map.put("ret", "ERROR"); 68 map.put("description", "Operation is empty"); 69 } else { 70 71 try { 72 73 if ("create_springboot".equals(operation)) { 74 75 CreateIndexRequest request = new CreateIndexRequest("springboot"); 76 CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT); 77 78 //System.out.println(response); 79 map.put("ret", "OK"); 80 map.put("description", "create_springboot -> " + response.index()); 81 82 } else if ("get_springboot".equals(operation)) { 83 84 GetIndexRequest request = new GetIndexRequest("springboot"); 85 GetIndexResponse response = restHighLevelClient.indices().get(request, RequestOptions.DEFAULT); 86 87 //System.out.println(response); 88 map.put("ret", "OK"); 89 map.put("description", "get_springboot -> " + response.getSettings().toString()); 90 91 } else if ("delete_springboot".equals(operation)) { 92 93 DeleteIndexRequest request = new DeleteIndexRequest("springboot"); 94 AcknowledgedResponse response = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT); 95 96 //System.out.println(response); 97 map.put("ret", "OK"); 98 map.put("description", "delete_springboot -> " + response.toString()); 99 100 } else if ("create_springboot_settings".equals(operation)) { 101 102 CreateIndexRequest request = new CreateIndexRequest("springboot"); 103 request.settings(raw, XContentType.JSON); 104 CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT); 105 106 System.out.println(response); 107 map.put("ret", "OK"); 108 map.put("description", "create_springboot_settings -> " + response.index()); 109 110 } else if ("get_springboot_settings".equals(operation)) { 111 112 GetIndexRequest request = new GetIndexRequest("springboot"); 113 GetIndexResponse response = restHighLevelClient.indices().get(request, RequestOptions.DEFAULT); 114 115 System.out.println(response); 116 map.put("ret", "OK"); 117 map.put("description", "get_springboot_settings -> settings: " + response.getSettings() 118 + ", mapping: " + response.getMappings()); 119 120 } else if ("create_doc1_auto".equals(operation)) { 121 122 IndexRequest indexRequest = new IndexRequest(); 123 indexRequest.index("springboot"); 124 indexRequest.type("doc1"); 125 indexRequest.source(raw, XContentType.JSON); 126 IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT); 127 128 System.out.println(response); 129 map.put("ret", "OK"); 130 map.put("description", "create_doc1_auto -> id = " + response.getId() + " (" + raw + ")"); 131 132 } else if ("create_doc1_2".equals(operation)) { 133 134 IndexRequest indexRequest = new IndexRequest(); 135 indexRequest.index("springboot"); 136 indexRequest.type("doc1"); 137 indexRequest.id("2"); 138 indexRequest.source(raw, XContentType.JSON); 139 IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT); 140 141 System.out.println(response); 142 map.put("ret", "OK"); 143 map.put("description", "create_doc1_2 -> id = " + response.getId() + " (" + raw + ")"); 144 145 } else if ("create_doc1_batch".equals(operation)) { 146 147 BulkRequest bulkRequest = new BulkRequest("springboot", "doc1"); 148 149 IndexRequest indexRequest1 = new IndexRequest(); 150 indexRequest1.source(raw, XContentType.JSON); 151 152 IndexRequest indexRequest2 = new IndexRequest(); 153 indexRequest2.source(raw2, XContentType.JSON); 154 155 bulkRequest.add(indexRequest1); 156 bulkRequest.add(indexRequest2); 157 158 BulkResponse response = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT); 159 160 System.out.println(response); 161 map.put("ret", "OK"); 162 map.put("description", "create_doc1_batch -> " + response.status()); 163 164 } else if ("get_doc1_2".equals(operation)) { 165 166 GetRequest getRequest = new GetRequest(); 167 getRequest.index("springboot"); 168 getRequest.type("doc1"); 169 getRequest.id("2"); 170 getRequest.fetchSourceContext(new FetchSourceContext(true)); 171 GetResponse response = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT); 172 173 System.out.println(response); 174 map.put("ret", "OK"); 175 map.put("description", "get_doc1_2 -> " + response.getSourceAsString()); 176 177 } else if ("modify_doc1_2".equals(operation)) { 178 179 UpdateRequest updateRequest = new UpdateRequest(); 180 updateRequest.index("springboot"); 181 updateRequest.type("doc1"); 182 updateRequest.id("2"); 183 184 updateRequest.doc(raw, XContentType.JSON); 185 UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT); 186 187 System.out.println(response); 188 map.put("ret", "OK"); 189 map.put("description", "modify_doc1_2 -> " + response.status()); 190 191 } else if ("delete_doc1_2".equals(operation)) { 192 193 DeleteRequest deleteRequest = new DeleteRequest(); 194 deleteRequest.index("springboot"); 195 deleteRequest.type("doc1"); 196 deleteRequest.id("2"); 197 DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT); 198 199 System.out.println(response); 200 map.put("ret", "OK"); 201 map.put("description", "delete_doc1_2 -> " + response.status()); 202 203 } else if ("query_springboot_docs".equals(operation)) { 204 205 SearchRequest searchRequest = new SearchRequest("springboot"); 206 207 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 208 sourceBuilder.query(QueryBuilders.matchAllQuery()); 209 searchRequest.source(sourceBuilder); 210 SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); 211 212 System.out.println(response); 213 214 String ret = response.status().toString(); 215 if ("OK".equals(ret)) { 216 ret = ""; 217 for (SearchHit hit : response.getHits()) { 218 if (ret.isEmpty()) { 219 ret = hit.getSourceAsString(); 220 } else { 221 ret += " | " + hit.getSourceAsString(); 222 } 223 } 224 } 225 226 map.put("ret", "OK"); 227 map.put("description", "query_springboot_docs -> " + ret); 228 229 } else if ("term_query_springboot_docs".equals(operation)) { 230 231 SearchRequest searchRequest = new SearchRequest("springboot"); 232 233 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 234 sourceBuilder.query(QueryBuilders.termQuery("name.keyword","Elastic Search")); 235 searchRequest.source(sourceBuilder); 236 SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); 237 238 System.out.println(response); 239 240 String ret = response.status().toString(); 241 if ("OK".equals(ret)) { 242 ret = ""; 243 for (SearchHit hit : response.getHits()) { 244 if (ret.isEmpty()) { 245 ret = hit.getSourceAsString(); 246 } else { 247 ret += " | " + hit.getSourceAsString(); 248 } 249 } 250 } 251 252 map.put("ret", "OK"); 253 map.put("description", "term_query_springboot_docs -> " + ret); 254 255 } else if ("match_query_springboot_docs".equals(operation)) { 256 257 SearchRequest searchRequest = new SearchRequest("springboot"); 258 259 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 260 sourceBuilder.query(QueryBuilders.matchQuery("name","Elastic Search")); 261 searchRequest.source(sourceBuilder); 262 SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); 263 264 System.out.println(response); 265 266 String ret = response.status().toString(); 267 if ("OK".equals(ret)) { 268 ret = ""; 269 for (SearchHit hit : response.getHits()) { 270 if (ret.isEmpty()) { 271 ret = hit.getSourceAsString(); 272 } else { 273 ret += " | " + hit.getSourceAsString(); 274 } 275 } 276 } 277 278 map.put("ret", "OK"); 279 map.put("description", "match_query_springboot_docs -> " + ret); 280 281 } else { 282 map.put("ret", "ERROR"); 283 map.put("description", "Invalid operation"); 284 } 285 286 } catch (ElasticsearchStatusException e) { 287 map.put("ret", "ERROR"); 288 map.put("description", e.getMessage()); 289 } catch (IOException e) { 290 map.put("ret", "ERROR"); 291 map.put("description", e.getMessage()); 292 } 293 294 } 295 296 return map; 297 } 298 299 }
确保 ElasticSearch 服务已正常运行,运行 SpringBoot 项目,浏览器访问 http://localhost:9090/client。