1. 目錄

2. SpringBoot集成
開(kāi)發(fā)工具,這里選擇的是IDEA 2021.1.2,構(gòu)建 Gradle 工程等一堆通用操作,不清楚的自行百度 或者 參看 90分鐘玩轉(zhuǎn)Gradle
2.1. 依賴配置
我這邊選擇 spring-boot-starter-data-elasticsearch 方式來(lái)集成 spring-boot 中集成的版本號(hào)與實(shí)際安裝版本號(hào)的差異,盡量選擇一致的版本,否則在集成過(guò)程中,會(huì)有莫名的問(wèn)題。讀者在選擇的時(shí)候多加留意。
api("org.springframework.boot:spring-boot-starter-data-elasticsearch")
我在此基礎(chǔ)上封裝一層 persistence-elasticsearch,更貼近一般項(xiàng)目使用。
- 中央倉(cāng)庫(kù)下載

- 阿里云的倉(cāng)庫(kù)下載

2.2. 核心操作類
為了規(guī)范索引管理,這里將所有的操作都封裝成一個(gè)基類,實(shí)現(xiàn)對(duì)索引的增刪改查。同時(shí)還集成了對(duì)數(shù)據(jù)的單個(gè)以及批量的插入以及刪除。避免針對(duì)每個(gè)索引都自己寫一套實(shí)現(xiàn),杜絕代碼的冗余,同時(shí)這樣的集成對(duì)代碼的結(jié)構(gòu)本身也是低侵入性。
- AbstractElasticIndexManger
public abstract class AbstractElasticIndexManger {
protected ElasticsearchRestTemplate elasticsearchRestTemplate;
protected RestHighLevelClient restHighLevelClient;
@Autowired
public void setRestHighLevelClient(RestHighLevelClient restHighLevelClient) {
this.restHighLevelClient = restHighLevelClient;
}
@Autowired
public void setElasticsearchRestTemplate(ElasticsearchRestTemplate elasticsearchRestTemplate) {
this.elasticsearchRestTemplate = elasticsearchRestTemplate;
}
/**
* 設(shè)置分片 和 副本
* 副本作用主要為了保證數(shù)據(jù)安全
*
* @param request 請(qǐng)求
* @author <a >Sam</a>
* @date 2019/10/17 19:27
*/
protected void buildSetting(CreateIndexRequest request, int replicas, int shards) {
request.settings(Settings.builder().put("index.number_of_shards", shards)
.put("index.number_of_replicas", replicas));
}
/**
* 查詢匹配條件的數(shù)據(jù)量,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
*
* @param builder BoolQueryBuilder類型查詢實(shí)例
* @param indexNames 索引名,可以一次性查詢多個(gè)
* @return long 最終數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-9:26
**/
protected long count(BoolQueryBuilder builder, String... indexNames) {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withQuery(builder);
return elasticsearchRestTemplate.count(nativeSearchQueryBuilder.build(), IndexCoordinates.of(indexNames));
}
/**
* 查詢匹配條件,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
*
* @param builder BoolQueryBuilder類型查詢實(shí)例
* @param clazz Class對(duì)象
* @param indexNames 索引名,可以一次性查詢多個(gè)
* @return long 最終數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-9:26
**/
protected SearchHits search(BoolQueryBuilder builder, Class<? extends BasePo> clazz, String... indexNames) {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withQuery(builder);
Pageable pageable = PageRequest.of(1, 20);
nativeSearchQueryBuilder.withPageable(pageable);
return elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), clazz, IndexCoordinates.of(indexNames));
}
/**
* 查詢匹配條件,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
*
* @param page 當(dāng)前頁(yè)
* @param size 每頁(yè)大小
* @param builder BoolQueryBuilder類型查詢實(shí)例
* @param clazz Class對(duì)象
* @param indexNames 索引名,可以一次性查詢多個(gè)
* @return SearchHits 命中結(jié)果的數(shù)據(jù)集
* @author <a >Sam</a>
* @date 2021/11/1-9:26
**/
protected SearchHits<? extends BasePo> searchPage(int page, int size, BoolQueryBuilder builder, Class<? extends BasePo> clazz, String... indexNames) {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withQuery(builder);
Pageable pageable = PageRequest.of(page, size);
nativeSearchQueryBuilder.withPageable(pageable);
return elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), clazz, IndexCoordinates.of(indexNames));
}
protected DeleteByQueryRequest builderDeleteRequest(QueryBuilder builder, String... indexNames) {
DeleteByQueryRequest request = new DeleteByQueryRequest(indexNames);
request.setQuery(builder);
request.setBatchSize(0X5F5E0FF);
request.setConflicts("proceed");
return request;
}
/**
* 查詢匹配條件,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
*
* @param params Map形式的 字段名 和 字段內(nèi)容 組成的條件
* @param builder BoolQueryBuilder類型查詢實(shí)例
* @param indexNames 索引名,可以一次性查詢多個(gè)
* @return long 最終數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-9:26
**/
protected BulkByScrollResponse update(Map<String, Object> params, BoolQueryBuilder builder, String... indexNames) {
UpdateByQueryRequest request = buildUpdateByQueryReq(params, builder, indexNames);
try {
return restHighLevelClient.updateByQuery(request, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 構(gòu)建更新 QueryRequest
*
* @param params 參數(shù)
* @param builder 布爾構(gòu)建
* @param indexNames 索引
* @return UpdateByQueryRequest
* @author <a >Sam</a>
* @date 2021/11/28-15:50
**/
protected UpdateByQueryRequest buildUpdateByQueryReq(Map<String, Object> params, BoolQueryBuilder builder, String... indexNames) {
Script script = buildScriptType(params);
UpdateByQueryRequest request = new UpdateByQueryRequest(indexNames);
request.setQuery(builder);
request.setScript(script);
request.setConflicts("proceed");
request.setRefresh(true);
request.setTimeout(TimeValue.timeValueMinutes(3));
return request;
}
/**
* 以 K-V鍵值對(duì) 方式構(gòu)建條件 Script
*
* @param params Map形式的 字段名 和 字段內(nèi)容 組成的條件
* @return Script
* @author <a >Sam</a>
* @date 2021/11/28-13:19
**/
protected Script buildScriptType(Map<String, Object> params) {
Set<String> keys = params.keySet();
StringBuffer idOrCodeStb = new StringBuffer();
for (String key : keys) {
idOrCodeStb.append("ctx._source.").append(key).append("=params.").append(key).append(";");
}
ScriptType type = ScriptType.INLINE;
return new Script(type, Script.DEFAULT_SCRIPT_LANG, idOrCodeStb.toString(), params);
}
/**
* @param builder BoolQueryBuilder
* @param bool 布爾類條件
* @author <a >Sam</a>
* @date 2021/11/28-14:45
**/
protected void setBuilders(BoolQueryBuilder builder, BoolCondition bool) {
mustBuilders(builder, bool);
mustNotBuilders(builder, bool);
shouldBuilders(builder, bool);
filterBuilders(builder, bool);
}
/**
* 構(gòu)建滿足 必須 條件 的方法
*
* @param builder BoolQueryBuilder
* @param bool 布爾類條件
* @author <a >Sam</a>
* @date 2021/11/28-14:45
**/
protected void mustBuilders(BoolQueryBuilder builder, BoolCondition bool) {
List<AtomicCondition> must = bool.getMust();
if (must.isEmpty()) {
return;
}
for (AtomicCondition cds : must) {
builder.must(getQueryBuilder(cds));
}
}
/**
* 構(gòu)建滿足 非必須 條件 的方法
*
* @param builder BoolQueryBuilder
* @param bool 布爾類條件
* @author <a >Sam</a>
* @date 2021/11/28-14:45
**/
protected void mustNotBuilders(BoolQueryBuilder builder, BoolCondition bool) {
List<AtomicCondition> mustNot = bool.getMustNot();
if (mustNot.isEmpty()) {
return;
}
for (AtomicCondition cds : mustNot) {
builder.mustNot(getQueryBuilder(cds));
}
}
/**
* 構(gòu)建滿足 可選 條件 的方法
*
* @param builder BoolQueryBuilder
* @param bool 布爾類條件
* @author <a >Sam</a>
* @date 2021/11/28-14:45
**/
protected void shouldBuilders(BoolQueryBuilder builder, BoolCondition bool) {
List<AtomicCondition> should = bool.getShould();
if (should.isEmpty()) {
return;
}
for (AtomicCondition cds : should) {
builder.should(getQueryBuilder(cds));
}
}
/**
* 構(gòu)建滿足 必須 條件 的方法,推薦使用
*
* @param builder BoolQueryBuilder
* @param bool 布爾類條件
* @author <a >Sam</a>
* @date 2021/11/28-14:45
**/
protected void filterBuilders(BoolQueryBuilder builder, BoolCondition bool) {
List<AtomicCondition> filter = bool.getFilter();
if (filter.isEmpty()) {
return;
}
for (AtomicCondition cds : filter) {
builder.filter(getQueryBuilder(cds));
}
}
public QueryBuilder getQueryBuilder(AtomicCondition cds) {
QueryBuilder queryBuilder;
Tuple tuple = cds.getTuple();
switch (cds.getStatus()) {
case (Constants.SUFFIX_QUERY):
queryBuilder = QueryBuilders.wildcardQuery(cds.getField(), Constants.MULTI_CHARACTER + tuple.v1());
break;
case (Constants.SUFFIX_SINGLE_QUERY):
queryBuilder = QueryBuilders.wildcardQuery(cds.getField(), Constants.SINGLE_CHARACTER + tuple.v1());
break;
case (Constants.RANGE_QUERY):
queryBuilder = QueryBuilders.rangeQuery(cds.getField()).from(tuple.v1()).to(tuple.v2());
break;
case (Constants.PREFIX_QUERY):
queryBuilder = QueryBuilders.prefixQuery(cds.getField(), tuple.v1().toString());
break;
case (Constants.REG_QUERY):
queryBuilder = QueryBuilders.regexpQuery(cds.getField(), tuple.v1().toString());
break;
default:
queryBuilder = QueryBuilders.termQuery(cds.getField(), tuple.v1().toString());
break;
}
return queryBuilder;
}
}
- ElasticIndexManger
public class ElasticIndexManger extends AbstractElasticIndexManger {
/**
* 創(chuàng)建索引,默認(rèn)分片數(shù)量為 1,即一個(gè)主片,副本數(shù)量為 0
*
* @param indexName 索引名稱
* @param mapping 索引定義,JSON形式的字符串
* @author <a >Sam</a>
* @date 2019/10/17 17:30
*/
public void createIndex(String indexName, String mapping) {
createIndex(indexName, mapping, 0, 1);
}
/**
* 指定索引結(jié)構(gòu)創(chuàng)建索引
*
* @param indexName 索引名稱
* @param mapping 索引定義,JSON形式的字符串
* @param replicas 副本的數(shù)量
* @param shards 分片數(shù)量
* @author <a >Sam</a>
* @date 2019/10/17 17:30
*/
public void createIndex(String indexName, String mapping, int replicas, int shards) {
try {
if (!this.existIndex(indexName)) {
log.error(" indexName={} 已經(jīng)存在,mapping={}", indexName, mapping);
return;
}
CreateIndexRequest request = new CreateIndexRequest(indexName);
buildSetting(request, replicas, shards);
request.mapping(mapping, XContentType.JSON);
CreateIndexResponse res = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
if (!res.isAcknowledged()) {
throw new RuntimeException("初始化失敗");
}
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
/**
* 獲取所有索引,默認(rèn)為所有索引
*
* @return List
* @author <a >Sam</a>
* @date 2021/10/30-11:54
**/
public List getAllIndex() {
return getAllIndex(Constants.MULTI_CHARACTER);
}
/**
* 獲取所有索引,按照正則表達(dá)式方式過(guò)濾 索引名稱,并返回符合條件的索引名字
*
* @param inPattern 正則表達(dá)式
* @return List
* @author <a >Sam</a>
* @date 2021/10/30-11:54
**/
public List<String> getAllIndex(String inPattern) {
GetIndexRequest getIndexRequest = new GetIndexRequest(inPattern);
try {
GetIndexResponse getIndexResponse = restHighLevelClient.indices().get(getIndexRequest, RequestOptions.DEFAULT);
String[] indices = getIndexResponse.getIndices();
return Arrays.asList(indices);
} catch (IOException e) {
log.error("獲取索引失敗 {} 已經(jīng)存在", e.getMessage());
} catch (ElasticsearchStatusException e) {
log.error("獲取索引失敗 {} 索引本身不存在", e.getMessage());
}
return Collections.EMPTY_LIST;
}
/**
* 制定配置項(xiàng)的判斷索引是否存在,注意與 isExistsIndex 區(qū)別
* <ul>
* <li>1、可以指定 用本地檢索 還是用 主動(dòng)節(jié)點(diǎn)方式檢索</li>
* <li>2、是否適應(yīng)被人讀的方式</li>
* <li>3、返回默認(rèn)設(shè)置</li>
* </ul>
*
* @param indexName index名
* @return boolean
* @author <a >Sam</a>
* @date 2019/10/17 17:27
*/
public boolean existIndex(String indexName) throws IOException {
GetIndexRequest request = new GetIndexRequest(indexName);
//TRUE-返回本地信息檢索狀態(tài),F(xiàn)ALSE-還是從主節(jié)點(diǎn)檢索狀態(tài)
request.local(false);
//是否適應(yīng)被人可讀的格式返回
request.humanReadable(true);
//是否為每個(gè)索引返回所有默認(rèn)設(shè)置
request.includeDefaults(false);
//控制如何解決不可用的索引以及如何擴(kuò)展通配符表達(dá)式,忽略不可用索引的索引選項(xiàng),僅將通配符擴(kuò)展為開(kāi)放索引,并且不允許從通配符表達(dá)式解析任何索引
request.indicesOptions(IndicesOptions.lenientExpandOpen());
return restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
}
/**
* 單純斷某個(gè)索引是否存在
*
* @param indexName index名
* @return boolean 存在為True,不存在則 False
* @author <a >Sam</a>
* @date 2019/10/17 17:27
*/
public boolean isIndexExists(String indexName) throws Exception {
return restHighLevelClient.indices().exists(new GetIndexRequest(indexName), RequestOptions.DEFAULT);
}
/**
* 批量插入數(shù)據(jù),通過(guò) {@link List} 的對(duì)象集合進(jìn)行插入,此處對(duì)失敗的提交進(jìn)行二次提交,并覆蓋原有數(shù)據(jù),這一層面是 ElasticSearch自行控制
*
* @param indexName index
* @param list 列表
* @author <a >Sam</a>
* @date 2019/10/17 17:26
*/
public void batch(String indexName, List<? extends BasePo> list) throws IOException {
int sleep = 15;
BulkRequest request = new BulkRequest();
list.forEach(item -> request.add(new IndexRequest(indexName)
.id(item.getId().toString())
.source(JSON.toJSONString(item), XContentType.JSON)));
try {
BulkResponse bulkResponse = bulk(request);
log.error("[Verification BulkResponse bulk 操作結(jié)果] {}, 文件大小 {} ", bulkResponse.status(), list.size());
if (bulkResponse.hasFailures()) {
log.error(bulkResponse.buildFailureMessage());
for (BulkItemResponse bulkItemResponse : bulkResponse) {
if (bulkItemResponse.isFailed()) {
log.error(bulkItemResponse.getFailureMessage());
}
}
log.error("批量操作失敗,重新再提交一次,間隔時(shí)間{}, 文件大小 {} ", sleep, list.size());
TimeUnit.SECONDS.sleep(sleep);
bulkResponse = bulk(request);
if (bulkResponse.hasFailures()) {
log.error("再次提交失敗,需要寫入MQ , 文件大小 {} ", list.size());
}
}
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
}
/**
* bulk 方式批量提交
*
* @param request {@link BulkRequest} 請(qǐng)求
* @return BulkResponse
* @author <a >Sam</a>
* @date 2021/10/24-15:50
**/
private BulkResponse bulk(BulkRequest request) throws IOException {
return restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
}
/**
* <p>
* 批量插入數(shù)據(jù),通過(guò) {@link List} 的對(duì)象集合進(jìn)行插入,提交前,判斷 該索引是否存在不存在則直接創(chuàng)建 該索引
* 并對(duì)失敗的提交進(jìn)行二次提交,并覆蓋原有數(shù)據(jù),這一層面是 ElasticSearch自行控制
* </p>
*
* @param indexName index
* @param list 列表
* @param created 當(dāng)索引不存在,則創(chuàng)建索引,默認(rèn)為 true,即索引不存在,創(chuàng)建該索引,此時(shí) mapping 應(yīng)該不為空
* @param mapping 索引定義,JSON形式的字符串
* @author <a >Sam</a>
* @date 2019/10/17 17:26
*/
public void batch(List<? extends BasePo> list, String indexName, boolean created, String mapping) throws Exception {
try {
if (!isIndexExists(indexName)) {
log.error("[Index does not exist] Rebuilding index. IndexName ={}", indexName);
if (created && StringUtils.isNotBlank(mapping)) {
createIndex(indexName, mapping);
} else {
log.error("[Index does not exist , No index creation] IndexName ={}", indexName);
return;
}
}
batch(indexName, list);
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
}
/**
* 批量刪除,根據(jù)索引名稱,刪除索引下數(shù)據(jù)
*
* @param indexName index
* @param idList 待刪除列表
* @author <a >Sam</a>
* @date 2019/10/17 17:14
*/
public <T> void deleteBatch(String indexName, Collection<T> idList) {
BulkRequest request = new BulkRequest();
idList.forEach(item -> request.add(new DeleteRequest(indexName, item.toString())));
try {
restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 根據(jù)索引名稱,和 {@link SearchSourceBuilder} 條件,以及返回對(duì)象實(shí)體類,返回列表
*
* @param indexName index
* @param builder 查詢參數(shù)
* @param clazz 結(jié)果類對(duì)象
* @return java.util.List<T>
* @author <a >Sam</a>
* @date 2019/10/17 17:14
*/
public <T> List<T> search(String indexName, SearchSourceBuilder builder, Class<T> clazz) {
List res = Collections.EMPTY_LIST;
try {
SearchRequest request = new SearchRequest(indexName);
request.source(builder);
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
SearchHit[] hits = response.getHits().getHits();
res = new ArrayList<>(hits.length);
for (SearchHit hit : hits) {
res.add(JSON.parseObject(hit.getSourceAsString(), clazz));
}
} catch (IOException e) {
log.error("[ElasticSearch] connect err ,err-msg {}", e.getMessage());
} catch (Exception e) {
throw new RuntimeException(e);
}
return res;
}
/**
* 刪除 index,以及索引下數(shù)據(jù)
*
* @param indexName 索引名字
* @author <a >Sam</a>
* @date 2019/10/17 17:13
*/
public void deleteIndex(String indexName) {
try {
restHighLevelClient.indices().delete(new DeleteIndexRequest(indexName), RequestOptions.DEFAULT);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 刪除索引下數(shù)據(jù),但是不刪除索引結(jié)構(gòu)
*
* @param builder 條件構(gòu)建模式
* @param indexNames 索引名稱列表
* @author <a >Sam</a>
* @date 2019/10/17 17:13
*/
public void deleteByQuery(QueryBuilder builder, String... indexNames) {
try {
DeleteByQueryRequest request = builderDeleteRequest(builder, indexNames);
BulkByScrollResponse response = restHighLevelClient.deleteByQuery(request, RequestOptions.DEFAULT);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 不推薦使用,原因?yàn)椴粔蜢`活,獲取該索引下可以匹配的數(shù)量,支持 模糊查詢和精確查詢,
* 用法 在 方法 <b>field</b> 的處理上。
* <ul>
* <li>模糊匹配模式:字段</li>
* <li>精確匹配模式:字段.類型</li>
* </ul>
*
* @param indexName 文檔索引名
* @param field 字段
* @param text 內(nèi)容
* @return long 數(shù)量
* @author <a >Sam</a>
* @date 2018/07/20-20:47
**/
@Deprecated
public long countMatchPhrasePrefixQuery(String indexName, String field, String text) {
CountRequest countRequest = new CountRequest(indexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchPhrasePrefixQuery(field, text));
countRequest.source(searchSourceBuilder);
CountResponse countResponse = null;
try {
countResponse = restHighLevelClient.count(countRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
return countResponse == null ? 0L : countResponse.getCount();
}
/**
* 按照字段 內(nèi)容進(jìn)行精確匹配,返回匹配的數(shù)量
*
* @param field 字段名
* @param content 內(nèi)容
* @param indexNames 索引名
* @return long 數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-10:49
**/
public long exactCondition(String field, String content, String... indexNames) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
builder.must(QueryBuilders.termQuery(field, content));
return count(builder, indexNames);
}
/**
* 按照字段的前綴內(nèi)容進(jìn)行匹配,返回匹配的數(shù)量
*
* @param field 字段名
* @param prefix 前綴
* @param indexNames 索引名
* @return long 數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-10:49
**/
public long prefix(String field, String prefix, String... indexNames) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
builder.must(QueryBuilders.prefixQuery(field, prefix));
return count(builder, indexNames);
}
/**
* 按照字段對(duì) 內(nèi)容進(jìn)行后綴匹配,返回匹配的數(shù)量
*
* @param suffix 后綴
* @param indexNames 索引名
* @return long 數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-10:56
**/
public long suffix(String field, String suffix, String... indexNames) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
builder.must(QueryBuilders.wildcardQuery(field, Constants.MULTI_CHARACTER + suffix));
return count(builder, indexNames);
}
/**
* 字段的前綴和后綴都必須滿足條件
*
* @param field 字段
* @param prefix 前綴
* @param suffix 后綴
* @param indexNames 索引名
* @return long 數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-10:59
**/
public long prefixAndSuffix(String field, String prefix, String suffix, String... indexNames) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
builder.must(QueryBuilders.prefixQuery(field, prefix));
builder.must(QueryBuilders.wildcardQuery(field, Constants.MULTI_CHARACTER + suffix));
return count(builder, indexNames);
}
/**
* 字段的前綴和后綴都滿足一個(gè)條件按即可
*
* @param field 字段
* @param prefix 前綴
* @param suffix 后綴
* @param indexNames 索引名
* @return long 數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-10:59
**/
public long prefixOrSuffix(String field, String prefix, String suffix, String... indexNames) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
builder.should(QueryBuilders.prefixQuery(field, prefix));
builder.should(QueryBuilders.wildcardQuery(field, Constants.MULTI_CHARACTER + suffix));
return count(builder, indexNames);
}
/**
* 字段的前綴必須滿足,而 后綴則不要求 不一定滿足
*
* @param field 字段
* @param prefix 前綴
* @param suffix 后綴
* @param indexNames 索引名
* @return long 數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-10:59
**/
public long prefixMustSuffixShould(String field, String prefix, String suffix, String... indexNames) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
builder.must(QueryBuilders.prefixQuery(field, prefix));
builder.should(QueryBuilders.wildcardQuery(field, Constants.MULTI_CHARACTER + suffix));
return count(builder, indexNames);
}
/**
* 字段的前綴選擇性滿足,而 后綴則一定要滿足
*
* @param field 字段
* @param prefix 前綴
* @param suffix 后綴
* @param indexNames 索引名
* @return long 數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-10:59
**/
public long prefixShouldSuffixMust(String field, String prefix, String suffix, String... indexNames) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
builder.should(QueryBuilders.prefixQuery(field, prefix));
builder.must(QueryBuilders.wildcardQuery(field, Constants.MULTI_CHARACTER + suffix));
return count(builder, indexNames);
}
/**
* 查詢總數(shù)
*
* @param indexNames 索引文檔名稱,可以是多個(gè)
* @return long 匹配的數(shù)量
* @author <a >Sam</a>
* @date 2021/10/29-21:11
**/
public long total(String... indexNames) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
return count(builder, indexNames);
}
/**
* 查詢匹配條件,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
*
* @param params Map形式的 字段名 和 字段內(nèi)容 組成的條件
* @param bool 復(fù)合條件封裝
* @param indexNames 索引名,可以一次性查詢多個(gè)
* @return long 最終數(shù)量
* @author <a >Sam</a>
* @date 2021/11/1-9:26
**/
public BulkByScrollResponse update(Map<String, Object> params, BoolCondition bool, String... indexNames) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
setBuilders(builder,bool);
return update(params,builder,indexNames);
}
/**
* 查詢匹配條件,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
*
* @param page 當(dāng)前頁(yè)
* @param size 每頁(yè)大小
* @param clazz Class對(duì)象
* @param indexNames 索引名,可以一次性查詢多個(gè)
* @return SearchHits 命中結(jié)果的數(shù)據(jù)集
* @author <a >Sam</a>
* @date 2021/11/1-9:26
**/
protected SearchHits<? extends BasePo> searchPage(int page, int size, BoolCondition bool,Class<? extends BasePo> clazz, String... indexNames) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
setBuilders(builder,bool);
return searchPage(page,size,builder, clazz, indexNames);
}
}
3. 項(xiàng)目代碼
通過(guò)以上的集成,我們看到完成在項(xiàng)目中對(duì) elasticsearch 的集成,同時(shí)也用基類,將所有可能的操作都封裝起來(lái)。下來(lái)我們通過(guò)對(duì)基類的講解,來(lái)逐個(gè)說(shuō)明!
3.1. 索引管理
由于在ElasticIndexManger類定義了所有方法,直接調(diào)用即可。
3.1.1. 創(chuàng)建索引
我們?cè)趧?chuàng)建索引過(guò)程中需要先判斷是否有這個(gè)索引,否則不允許創(chuàng)建,由于我案例采用的是手動(dòng)指定 indexName 和 Settings ,大家看的過(guò)程中要特別注意下,而且還有一點(diǎn) indexName 必須是小寫,如果是大寫在創(chuàng)建過(guò)程中會(huì)有錯(cuò)誤


。詳細(xì)的代碼實(shí)現(xiàn)見(jiàn)如下:
/**
* 創(chuàng)建索引,默認(rèn)分片數(shù)量為 1,即一個(gè)主片,副本數(shù)量為 0
*
* @param indexName 索引名稱
* @param mapping 索引定義,JSON形式的字符串
* @author <a >Sam</a>
* @date 2019/10/17 17:30
*/
public void createIndex(String indexName, String mapping) {
createIndex(indexName, mapping, 0, 1);
}
/**
* 指定索引結(jié)構(gòu)創(chuàng)建索引
*
* @param indexName 索引名稱
* @param mapping 索引定義,JSON形式的字符串
* @param replicas 副本的數(shù)量
* @param shards 分片數(shù)量
* @author <a >Sam</a>
* @date 2019/10/17 17:30
*/
public void createIndex(String indexName, String mapping, int replicas, int shards) {
try {
if (!this.existIndex(indexName)) {
log.error(" indexName={} 已經(jīng)存在,mapping={}", indexName, mapping);
return;
}
CreateIndexRequest request = new CreateIndexRequest(indexName);
buildSetting(request, replicas, shards);
request.mapping(mapping, XContentType.JSON);
CreateIndexResponse res = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
if (!res.isAcknowledged()) {
throw new RuntimeException("初始化失敗");
}
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
創(chuàng)建索引需要設(shè)置分片,這里采用Settings.Builder方式,當(dāng)然也可以 JSON 自定義方式,本文篇幅有限,不做演示。
index.number_of_shards:分片數(shù)
number_of_replicas:副本數(shù)
/**
* 設(shè)置分片 和 副本
* 副本作用主要為了保證數(shù)據(jù)安全
*
* @param request 請(qǐng)求
* @author <a >Sam</a>
* @date 2019/10/17 19:27
*/
protected void buildSetting(CreateIndexRequest request, int replicas, int shards) {
request.settings(Settings.builder().put("index.number_of_shards", shards)
.put("index.number_of_replicas", replicas));
}
[elastic@localhost elastic]$ curl -H "Content-Type: application/json" -X GET "http://localhost:9200/_cat/indices?v"
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open twitter scSSD1SfRCio4F77Hh8aqQ 3 2 2 0 8.3kb 8.3kb
yellow open idx_location _BJ_pOv0SkS4tv-EC3xDig 3 2 1 0 4kb 4kb
yellow open wongs uT13XiyjSW-VOS3GCqao8w 3 2 1 0 3.4kb 3.4kb
yellow open idx_locat Kr3wGU7JT_OUrRJkyFSGDw 3 2 3 0 13.2kb 13.2kb
yellow open idx_copy_to HouC9s6LSjiwrJtDicgY3Q 3 2 1 0 4kb 4kb
說(shuō)明創(chuàng)建成功,這里總是通過(guò)命令行來(lái)驗(yàn)證,有點(diǎn)繁瑣,既然我都有WEB服務(wù),為什么不直接通過(guò)HTTP驗(yàn)證了?
3.1.2. 查看索引
查看索引這個(gè)操作支持模糊操作,即以通配符 * 作為一個(gè)或者多個(gè)字符匹配,這個(gè)操作在實(shí)際應(yīng)用非常好用,將來(lái)有機(jī)會(huì)說(shuō)到 Index 設(shè)計(jì)過(guò)程中就顯得尤為重要。
/**
* 獲取所有索引,默認(rèn)為所有索引
*
* @return List
* @author <a >Sam</a>
* @date 2021/10/30-11:54
**/
public List getAllIndex() {
return getAllIndex(Constants.MULTI_CHARACTER);
}
/**
* 獲取所有索引,按照正則表達(dá)式方式過(guò)濾 索引名稱,并返回符合條件的索引名字
*
* @param inPattern 正則表達(dá)式
* @return List
* @author <a >Sam</a>
* @date 2021/10/30-11:54
**/
public List<String> getAllIndex(String inPattern) {
GetIndexRequest getIndexRequest = new GetIndexRequest(inPattern);
try {
GetIndexResponse getIndexResponse = restHighLevelClient.indices().get(getIndexRequest, RequestOptions.DEFAULT);
String[] indices = getIndexResponse.getIndices();
return Arrays.asList(indices);
} catch (IOException e) {
log.error("獲取索引失敗 {} 已經(jīng)存在", e.getMessage());
} catch (ElasticsearchStatusException e) {
log.error("獲取索引失敗 {} 索引本身不存在", e.getMessage());
}
return Collections.EMPTY_LIST;
}
3.1.3. 刪除索引
刪除的邏輯就比較簡(jiǎn)單,這里就不多說(shuō)。
/**
* 刪除 index,以及索引下數(shù)據(jù)
*
* @param indexName 索引名字
* @author <a >Sam</a>
* @date 2019/10/17 17:13
*/
public void deleteIndex(String indexName) {
try {
restHighLevelClient.indices().delete(new DeleteIndexRequest(indexName), RequestOptions.DEFAULT);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
3.2. 引用依賴
構(gòu)建一個(gè)工程,我這里依然用 Gralde 工程作為樣例說(shuō)明, Maven 項(xiàng)目類似。
3.2.1. 依賴管理
implementation("io.github.rothschil:persistence-elasticsearch:1.2.3.RELEASE")
3.2.2. 依賴注入
在工程項(xiàng)目中直接使用 ElasticIndexManger 作為實(shí)例注入進(jìn)來(lái),后面我們可以直接使用它提供的各種方法。樣例中我是定義一個(gè)精確查詢作為說(shuō)明,TermQueryBuilder("sysCode","crm") 中參數(shù)分別代表匹配條件的列名和列的值; 在索引列名中我這里用的是 通配符,即可以在多個(gè)索引之間查詢; AccLog.class 這是我自定義的類,用以接收查詢出來(lái)的結(jié)果進(jìn)行實(shí)例化映射。
@Component
public class LogIndexManager{
private ElasticIndexManger elasticIndexManger;
@Autowired
public void setElasticIndexManger(ElasticIndexManger elasticIndexManger) {
this.elasticIndexManger = elasticIndexManger;
}
public List<AccLog> query(){
QueryBuilder queryBuilder = new TermQueryBuilder("sysCode","crm");
SearchSourceBuilder sb = new SearchSourceBuilder();
sb.query(queryBuilder);
return elasticIndexManger.search("hnqymh_hpg*",sb,AccLog.class);
}
}
![查詢結(jié)果]](https://upload-images.jianshu.io/upload_images/7232803-c3509fce823f59ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
4. 源碼
Github演示源碼 ,記得給Star
Gitee演示源碼,記得給Star