前面瞭解了使用 HTTP 請求操作 ES,但是實際項目中操作,我們主要還是使用Java代碼操作,所以嘗試一下Java客戶端連接ES
基礎
- 已安裝ES,且自己能夠正常操作API,瞭解ES基本語法
- 本地已安裝IDE
新建springboot工程
- pom.xml,主要是加入ES的依賴就好,版本號與ES服務器版本號一致,我的都是6.5.1
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.elastic</groupId> <artifactId>es-1</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>es-1</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.elasticsearch.client/transport --> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>6.5.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- 客戶端連接服務器,通過TransportClient,這裏直接獲取連接然後注入容器
package com.elastic.es1; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.transport.client.PreBuiltTransportClient; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import java.net.InetAddress; import java.net.UnknownHostException; @Configuration @SpringBootApplication @ComponentScan("com.elastic.es1") public class Es1Application { public static void main(String[] args) { SpringApplication.run(Es1Application.class, args); } @Bean public TransportClient client() throws UnknownHostException { String hostName = "47.105.159.23"; TransportAddress transportAddress = new TransportAddress(InetAddress.getByName(hostName), 9300); String clusterName = "cluster.name"; String nodeName = "gaojie"; Settings settings = Settings.builder().put(clusterName, nodeName).build(); TransportClient client = new PreBuiltTransportClient(settings); client.addTransportAddress(transportAddress); return client; } }
- 編寫實體類,此處操作的是一個 employee 對象
public class EmployeeVo { private String id; // ID private String name; // 姓名 private String phone; // 電話 private Integer sex; // 性別 private Integer salary; // 工資 private String post; // 崗位 private String desc; // 描述 private Date joinTime; // 入職時間 private String keyword; // 關鍵字 private Integer minSalary; // 最小工資 private Integer maxSalary; // 最大工資 private Date startTime; // 開始時間 private Date endTime; // 結束時間 private Set<String> ids; // ID集合 private Object data; // 查詢結果 private Long total; // 查詢記錄總數 private Integer from; // 起始位置 private Integer size; // 查詢條數 public EmployeeVo(Object data, Long total) { this.data = data; this.total = total; } }
- 編寫接口類,定義方法以及常量,避免便於維護和可讀
package com.elastic.es1.service.interfaces; import com.elastic.es1.entity.vo.EmployeeVo; import java.io.IOException; public interface EmployeeService { void save(EmployeeVo vo) throws IOException; void delete(EmployeeVo vo); EmployeeVo search(EmployeeVo vo); String INDEX = "manage"; String TYPE = "employee"; String NAME = "name"; String PHONE = "phone"; String SEX = "sex"; String SALARY = "salary"; String POST = "post"; String DESC = "desc"; String JOIN_TIME = "joinTime"; }
- 方法實現(save方法,當傳入ID時爲修改,不傳ID時爲新增)
package com.elastic.es1.service.impl; import com.elastic.es1.entity.vo.EmployeeVo; import com.elastic.es1.service.interfaces.EmployeeService; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.RangeQueryBuilder; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; @Service public class EmployeeServiceImpl implements EmployeeService { @Resource private TransportClient client; @Override public void save(EmployeeVo vo) throws IOException { XContentBuilder builder = this.getBuilder(vo); if (vo.getId() == null) { // 若不傳入 ID 則表示新增 client.prepareIndex(INDEX, TYPE).setSource(builder).get(); } else { // 若傳入 ID 則表示修改 client.prepareUpdate(INDEX, TYPE, vo.getId()).setDoc(builder).get(); } } /** * 將請求對象 EmployeeVo 轉換成 builder 對象 */ private XContentBuilder getBuilder(EmployeeVo vo) throws IOException { XContentBuilder builder = jsonBuilder().startObject(); if (vo.getName() != null) { builder.field(NAME, vo.getName()); } if (vo.getPhone() != null) { builder.field(PHONE, vo.getPhone()); } if (vo.getPost() != null) { builder.field(POST, vo.getPost()); } if (vo.getDesc() != null) { builder.field(DESC, vo.getDesc()); } if (vo.getSex() != null) { builder.field(SEX, vo.getSex()); } if (vo.getSalary() != null) { builder.field(SALARY, vo.getSalary()); } return builder.endObject(); } @Override public void delete(EmployeeVo vo) { if (vo.getIds() != null) { vo.getIds().forEach(id -> client.prepareDelete(INDEX, TYPE, id).get()); } } @Override public EmployeeVo search(EmployeeVo vo) { // boolQuery :可以疊加多個查詢條件,相當於SQL的 AND BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); // multiMatchQuery :多字段搜索,輸入關鍵字,可以從多個字段匹配 if (vo.getKeyword() != null) { boolQueryBuilder.must(QueryBuilders.multiMatchQuery(vo.getKeyword(), NAME, PHONE, POST, DESC)); } // matchQuery :單字段搜索 if (vo.getName() != null) { boolQueryBuilder.must(QueryBuilders.matchQuery(NAME, vo.getName())); } // wildcardQuery :通配符匹配:類似於SQL裏面的 LIKE if (vo.getPhone() != null) { boolQueryBuilder.must(QueryBuilders.wildcardQuery(PHONE, "*" + vo.getPhone() + "*")); } if (vo.getPost() != null) { boolQueryBuilder.must(QueryBuilders.matchQuery(POST, vo.getPost())); } if (vo.getDesc() != null) { boolQueryBuilder.must(QueryBuilders.matchQuery(DESC, vo.getDesc())); } // termQuery : 通常用於非字符串類型的比較,比如性別爲男的或者女的,狀態爲1或者2什麼的。。總之比較固定值 if (vo.getSex() != null) { boolQueryBuilder.must(QueryBuilders.termQuery(SEX, vo.getSex())); } if (vo.getSalary() != null) { boolQueryBuilder.must(QueryBuilders.termQuery(SALARY, vo.getSalary())); } // rangeQuery :範圍查詢,判斷一個值是否在範圍內,比如工資大於多少,年齡多少之間,是否在有效期內。。。 if (vo.getMinSalary() != null || vo.getMaxSalary() != null) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(SALARY); if (vo.getMinSalary() != null) { // from :範圍查詢裏面的下限值,包含該值 rangeQueryBuilder.from(vo.getMinSalary()); } if (vo.getMaxSalary() != null) { // to :範圍查詢裏面的上限值,包含該值 rangeQueryBuilder.to(vo.getMaxSalary()); } boolQueryBuilder.filter(rangeQueryBuilder); } if (vo.getStartTime() != null || vo.getEndTime() != null) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(JOIN_TIME); if (vo.getStartTime() != null) { rangeQueryBuilder.from(vo.getStartTime()); } if (vo.getEndTime() != null) { rangeQueryBuilder.to(vo.getEndTime()); } boolQueryBuilder.filter(rangeQueryBuilder); } SearchRequestBuilder searchRequestBuilder = client.prepareSearch(INDEX).setSearchType(SearchType.DFS_QUERY_THEN_FETCH).setQuery(boolQueryBuilder); // from :翻頁參數,從第幾條記錄開始 if (vo.getFrom() != null) { searchRequestBuilder.setFrom(vo.getFrom()); } // size :翻頁參數,查詢的記錄數 if (vo.getSize() != null) { searchRequestBuilder.setSize(vo.getSize()); } // sort : 排序規則,默認規則爲ASC升序,可以修改排序規則也可以用多個字段 searchRequestBuilder.addSort(SortBuilders.fieldSort(SEX)) .addSort(SortBuilders.fieldSort(SALARY).order(SortOrder.DESC)); System.out.println("查詢條件爲|" + searchRequestBuilder); // 查詢結果,由於數據都在 sourceAsMap 參數下,所以下面會再獲取 sourceAsMap 的值 SearchHits hits = searchRequestBuilder.get().getHits(); List<Map<String, Object>> data = new ArrayList<>(); hits.forEach(hit -> { Map<String, Object> sourceAsMap = hit.getSourceAsMap(); sourceAsMap.put("_id", hit.getId()); data.add(sourceAsMap); }); return new EmployeeVo(data, hits.totalHits); } }
- 編寫 Controller
package com.elastic.es1.controller; import com.elastic.es1.entity.vo.EmployeeVo; import com.elastic.es1.service.interfaces.EmployeeService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.io.IOException; @RestController @RequestMapping("employee") public class EmployeeController { @Resource private EmployeeService employeeService; @PostMapping("save") public Object save(@RequestBody EmployeeVo vo) throws IOException { employeeService.save(vo); return "保存成功"; } @DeleteMapping("delete") public Object delete(@RequestBody EmployeeVo vo) { employeeService.delete(vo); return "刪除成功"; } @GetMapping("search") public Object search(EmployeeVo vo) { return employeeService.search(vo); } }
- 修改 application.properties 端口號,因爲默認端口爲8080,防止衝突
server.port=9090
項目啓動成功後,去Postman測試,都能夠正常使用,這裏只記錄一個 search 請求
GET http://localhost:9090/employee/search?from=0&size=10
================================================
{
"id": null,
"name": null,
"phone": null,
"sex": null,
"salary": null,
"post": null,
"desc": null,
"joinTime": null,
"keyword": null,
"minSalary": null,
"maxSalary": null,
"startTime": null,
"endTime": null,
"ids": null,
"data": [
{
"post": "設計前端",
"phone": "18874797777",
"sex": 1,
"name": "桂祿",
"dept": {
"name": "設計中心",
"id": "1003"
},
"_id": "o1ZWmGcBof4Qu3GrG81P",
"salary": 50000,
"join_time": "2018-05-03",
"desc": "a front boy"
},
{
"post": "架構師",
"phone": "18874792222",
"sex": 1,
"name": "春哥",
"dept": {
"name": "技術中心",
"id": "1001"
},
"_id": "nlb-l2cBof4Qu3Gr8M1Y",
"salary": 35000,
"join_time": "2018-03-03",
"desc": "a framework boy"
},
{
"post": "設計UI",
"phone": "18874796666",
"sex": 1,
"name": "銅川",
"dept": {
"name": "設計中心",
"id": "1003"
},
"_id": "olYDmGcBof4Qu3Grw837",
"salary": 16000,
"join_time": "2018-04-03",
"desc": "a sunny boy"
},
{
"post": "Java工程師",
"phone": "18874796310",
"sex": 1,
"name": "高節",
"dept": {
"name": "技術中心",
"id": "1001"
},
"_id": "nFZul2cBof4Qu3GrDc0v",
"salary": 10000,
"join_time": "2018-12-03",
"desc": "a love of work boy"
},
{
"post": "IOS工程師",
"phone": "18874791111",
"sex": 2,
"name": "周益",
"dept": {
"name": "技術中心",
"id": "1001"
},
"_id": "nVZ-l2cBof4Qu3GrFc1L",
"salary": 15000,
"join_time": "2018-10-03",
"desc": "a IOS boy"
},
{
"post": "測試工程師",
"phone": "18874793333",
"sex": 2,
"name": "麗燕",
"dept": {
"name": "技術中心",
"id": "1001"
},
"_id": "n1YAmGcBof4Qu3GrB83o",
"salary": 12000,
"join_time": "2018-05-03",
"desc": "a beautiful girl"
},
{
"post": "Android工程師",
"phone": "18874794444",
"sex": 2,
"name": "李嶽",
"dept": {
"name": "技術中心",
"id": "1001"
},
"_id": "oFYAmGcBof4Qu3Gr880L",
"salary": 12000,
"join_time": "2018-04-03",
"desc": "a rude man"
},
{
"post": "會計",
"phone": "18874795555",
"sex": 2,
"name": "青萍",
"dept": {
"name": "財務部門",
"id": "1002"
},
"_id": "oVYCmGcBof4Qu3Gr8c33",
"salary": 6000,
"join_time": "2018-08-03",
"desc": "a sunny girl"
}
],
"total": 8,
"from": null,
"size": null
}