SpringBoot整合Elasticsearch詳細步驟以及程式碼示例(附原始碼)
阿新 • • 發佈:2019-09-20
## 準備工作
### 環境準備
JAVA版本
```bash
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
```
ES版本
```json
{
"name": "pYaFJhZ",
"cluster_name": "my-cluster",
"cluster_uuid": "oC28y-cNQduGItC7qq5W8w",
"version": {
"number": "6.8.2",
"build_flavor": "oss",
"build_type": "tar",
"build_hash": "b506955",
"build_date": "2019-07-24T15:24:41.545295Z",
"build_snapshot": false,
"lucene_version": "7.7.0",
"minimum_wire_compatibility_version": "5.6.0",
"minimum_index_compatibility_version": "5.0.0"
},
"tagline": "You Know, for Search"
}
```
SpringBoot版本
```xml
2.1.7.RELEASE
```
開發工具使用的是`IDEA`
### 安裝ES
Elasticsearch介紹以及安裝:[ElasticSearch入門-基本概念介紹以及安裝](https://www.lifengdi.com/archives/article/tech/869)
## 開始
### 建立SpringBoot專案
1. 開啟IDEA,在選單中點選
`File` > `New` > `Project...`
在彈框中選擇`Spring Initializr`
![圖1](https://www.lifengdi.com/wp-content/uploads/2019/09/2019091810411464.jpg '圖1')
然後`Next`
2. 填寫專案名等,然後`Next`,
![圖2](https://www.lifengdi.com/wp-content/uploads/2019/09/2019091810433646.jpg '圖2')
3. 選擇依賴的jar包(一般我只選Lombok,其他的自己手動加),然後`Next`。
![圖3](https://www.lifengdi.com/2019091810541514 '圖3')
4. 最後選擇專案所在路徑,點選`Finish`。
![圖3](https://www.lifengdi.com/2019091810515396 '圖3')
搞定收工。至此,一個新的SpringBoot專案就新鮮出爐了。
#### POM檔案
當然,具體依賴的jar包肯定不止第2步選擇的那些,其中SpringBoot提供的操作ES的jar包`spring-boot-starter-data-elasticsearch`當然也是必不可少的。
這裡貼出最終的pom檔案:
```xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.7.RELEASE
com.lifengdi
search
0.0.1-SNAPSHOT
search
elasticsearch
1.8
6.14.2
Greenwich.RELEASE
1.2.4
1.2.47
1.0.15-SNAPSHOT
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud-dependencies.version}
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-elasticsearch
org.springframework.boot
spring-boot-configuration-processor
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.testng
testng
${testng.version}
test
joda-time
joda-time
com.alibaba
fastjson
${fastjson.version}
org.springframework.cloud
spring-cloud-starter-openfeign
org.apache.commons
commons-lang3
org.springframework.boot
spring-boot-maven-plugin
```
#### application.yml檔案
application.yml檔案配置如下:
```yaml
server:
port: 8080
servlet:
context-path: /search
spring:
application:
name: search
data:
elasticsearch:
cluster-name: my-cluster
cluster-nodes: localhost:9300
jackson:
default-property-inclusion: non_null
logging:
file: application.log
path: .
level:
root: info
com.lifengdi.store.client: DEBUG
index-entity:
configs:
- docCode: store
indexName: store
type: base
documentPath: com.lifengdi.document.StoreDocument
```
`spring.data.elasticsearch.cluster-name`:叢集名稱
`spring.data.elasticsearch.cluster-nodes`:叢集節點地址列表,多個節點用英文逗號(,)分隔
### 建立ES文件和對映
首先建立一個JAVA物件,然後通過註解來宣告欄位的對映屬性。
spring提供的註解有`@Document`、`@Id`、`@Field`,其中`@Document`作用在類,`@Id`、`@Field`作用在成員變數,`@Id`標記一個欄位作為id主鍵。
```java
package com.lifengdi.document;
import com.lifengdi.document.store.*;
import com.lifengdi.search.annotation.DefinitionQuery;
import com.lifengdi.search.enums.QueryTypeEnum;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.List;
/**
* 門店Document
*
* @author 李鋒鏑
* @date Create at 19:31 2019/8/22
*/
@Document(indexName = "store", type = "base")
@Data
@DefinitionQuery(key = "page", type = QueryTypeEnum.IGNORE)
@DefinitionQuery(key = "size", type = QueryTypeEnum.IGNORE)
@DefinitionQuery(key = "q", type = QueryTypeEnum.FULLTEXT)
public class StoreDocument {
@Id
@DefinitionQuery(type = QueryTypeEnum.IN)
@DefinitionQuery(key = "id", type = QueryTypeEnum.IN)
@Field(type = FieldType.Keyword)
private String id;
/**
* 基礎資訊
*/
@Field(type = FieldType.Object)
private StoreBaseInfo baseInfo;
/**
* 標籤
*/
@Field(type = FieldType.Nested)
@DefinitionQuery(key = "tagCode", mapped = "tags.key", type = QueryTypeEnum.IN)
@DefinitionQuery(key = "tagValue", mapped = "tags.value", type = QueryTypeEnum.AND)
@DefinitionQuery(key = "_tagValue", mapped = "tags.value", type = QueryTypeEnum.IN)
private List tags;
}
```
### 建立索引
`ElasticsearchTemplate`提供了四個`createIndex()`方法來建立索引,可以根據類的資訊自動生成,也可以手動指定indexName和Settings
```java
@Override
public boolean createIndex(Class clazz) {
return createIndexIfNotCreated(clazz);
}
@Override
public boolean createIndex(String indexName) {
Assert.notNull(indexName, "No index defined for Query");
return client.admin().indices().create(Requests.createIndexRequest(indexName)).actionGet().isAcknowledged();
}
@Override
public boolean createIndex(String indexName, Object settings) {
CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(indexName);
if (settings instanceof String) {
createIndexRequestBuilder.setSettings(String.valueOf(settings), Requests.INDEX_CONTENT_TYPE);
} else if (settings instanceof Map) {
createIndexRequestBuilder.setSettings((Map) settings);
} else if (settings instanceof XContentBuilder) {
createIndexRequestBuilder.setSettings((XContentBuilder) settings);
}
return createIndexRequestBuilder.execute().actionGet().isAcknowledged();
}
@Override
public boolean createIndex(Class clazz, Object settings) {
return createIndex(getPersistentEntityFor(clazz).getIndexName(), settings);
}
```
### 建立對映
`ElasticsearchTemplate`提供了三個`putMapping()`方法來建立對映
```java
@Override
public boolean putMapping(Class clazz) {
if (clazz.isAnnotationPresent(Mapping.class)) {
String mappingPath = clazz.getAnnotation(Mapping.class).mappingPath();
if (!StringUtils.isEmpty(mappingPath)) {
String mappings = readFileFromClasspath(mappingPath);
if (!StringUtils.isEmpty(mappings)) {
return putMapping(clazz, mappings);
}
} else {
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
}
}
ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz);
XContentBuilder xContentBuilder = null;
try {
ElasticsearchPersistentProperty property = persistentEntity.getRequiredIdProperty();
xContentBuilder = buildMapping(clazz, persistentEntity.getIndexType(),
property.getFieldName(), persistentEntity.getParentType());
} catch (Exception e) {
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
}
return putMapping(clazz, xContentBuilder);
}
@Override
public boolean putMapping(Class clazz, Object mapping) {
return putMapping(getPersistentEntityFor(clazz).getIndexName(), getPersistentEntityFor(clazz).getIndexType(),
mapping);
}
@Override
public boolean putMapping(String indexName, String type, Object mapping) {
Assert.notNull(indexName, "No index defined for putMapping()");
Assert.notNull(type, "No type defined for putMapping()");
PutMappingRequestBuilder requestBuilder = client.admin().indices().preparePutMapping(indexName).setType(type);
if (mapping instanceof String) {
requestBuilder.setSource(String.valueOf(mapping), XContentType.JSON);
} else if (mapping instanceof Map) {
requestBuilder.setSource((Map) mapping);
} else if (mapping instanceof XContentBuilder) {
requestBuilder.setSource((XContentBuilder) mapping);
}
return requestBuilder.execute().actionGet().isAcknowledged();
}
```
測試程式碼如下
```java
@Test
public void testCreate() {
System.out.println(elasticsearchTemplate.createIndex(StoreDocument.class));
System.out.println(elasticsearchTemplate.putMapping(StoreDocument.class));
}
```
### 刪除索引
`ElasticsearchTemplate`提供了2個`deleteIndex()`方法來刪除索引
```java
@Override
public boolean deleteIndex(Class clazz) {
return deleteIndex(getPersistentEntityFor(clazz).getIndexName());
}
@Override
public boolean deleteIndex(String indexName) {
Assert.notNull(indexName, "No index defined for delete operation");
if (indexExists(indexName)) {
return client.admin().indices().delete(new DeleteIndexRequest(indexName)).actionGet().isAcknowledged();
}
return false;
}
```
### 新增&修改文件
在Elasticsearch中文件是不可改變的,不能修改它們。相反,如果想要更新現有的文件,需要重建索引或者進行替換。
所以可以使用和新增同樣的介面來對文件進行修改操作。區分的依據就是id。
下面提供新增&修改文件的其中兩種方法,一種是通過`ElasticsearchTemplate`提供的`index()`方法:
```java
@Override
public String index(IndexQuery query) {
String documentId = prepareIndex(query).execute().actionGet().getId();
// We should call this because we are not going through a mapper.
if (query.getObject() != null) {
setPersistentEntityId(query.getObject(), documentId);
}
return documentId;
}
```
示例程式碼如下:
```java
/**
* 更新索引
* @param indexName 索引名稱
* @param type 索引型別
* @param id ID
* @param jsonDoc JSON格式的文件
* @param refresh 是否重新整理索引
* @return ID
*/
public String index(String indexName, String type, String id, JsonNode jsonDoc, boolean refresh)
throws JsonProcessingException {
log.info("AbstractDocumentIndexService更新索引.indexName:{},type:{},id:{},jsonDoc:{}", indexName, type, id, jsonDoc);
IndexQuery indexQuery = new IndexQueryBuilder()
.withIndexName(indexName)
.withType(type)
.withId(id)
.withSource(objectMapper.writeValueAsString(jsonDoc))
.build();
try {
if (elasticsearchTemplate.indexExists(indexName)) {
String index = elasticsearchTemplate.index(indexQuery);
if (refresh) {
elasticsearchTemplate.refresh(indexName);
}
return index;
}
} catch (Exception e) {
log.error("更新索引失敗,重新整理ES重試", e);
elasticsearchTemplate.refresh(indexName);
return elasticsearchTemplate.index(indexQuery);
}
throw BaseException.INDEX_NOT_EXISTS_EXCEPTION.build();
}
```
另一種則是通過Repository介面。Spring提供的ES的Repository介面為`ElasticsearchCrudRepository`,所以我們就可以直接定義額新的介面,然後實現`ElasticsearchCrudRepository`即可:
```java
package com.taoche.docindex.repo;
import com.taoche.document.StoreDocument;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
* 門店Repository
* @author 李鋒鏑
* @date Create at 09:30 2019/8/23
*/
public interface StoreRepository extends ElasticsearchRepository { }
```
示例程式碼如下:
```java
@Test
public void testSave() {
StoreDocument storeDocument = new StoreDocument();
storeDocument.setId("1");
StoreBaseInfo baseInfo = new StoreBaseInfo();
baseInfo.setStoreId("1");
baseInfo.setCreatedTime(DateTime.now());
storeDocument.setBaseInfo(baseInfo);
storeRepository.save(storeDocument);
}
```
### 查詢
ES的主要功能就是查詢,`ElasticsearchRepository`也提供了基本的查詢介面,比如`findById()`、`findAll()`、`findAllById()`、`search()`等方法;當然也可以使用Spring Data提供的另外一個功能:Spring Data JPA——通過方法名建立查詢,當然需要遵循一定的規則,比如你的方法名叫做`findByTitle()`,那麼它就知道你是根據title查詢,然後自動幫你完成,這裡就不仔細說了。
上邊說的基本能滿足一般的查詢,複雜一點的查詢就無能為力了,這就需要用到自定義查詢,這裡可以檢視我的另一篇部落格[SpringBoot使用註解的方式構建Elasticsearch查詢語句,實現多條件的複雜查詢](https://www.lifengdi.com/archives/article/919),這裡邊有詳細的說明。
另外還有一個比較厲害的功能,Elasticsearch的聚合;聚合主要實現的是對資料的統計、分析。這個暫時沒有用到的,所以要看聚合功能的小夥伴們可能要失望了~ 哈哈哈~~~
聚合功能以後有時間會再單獨說的~都會有的。
至此,SpringBoot整合Elasticsearch基本結束,有什麼不明白的地方請留言~
### 原始碼
Git專案地址:[search](https://github.com/lifengdi/search)
如果覺得有幫助的話,請幫忙點贊、點星小小的支援一下~
謝謝~~
原文連結:[https://www.lifengdi.com/archives/article/945](https://www.lifengdi.com/archives/arti