前言
此前雖然有嘗試過集成elasticsearch,不過技術棧並非spring boot。本次嘗試在springboot項目中集成elasticsearch,不過由於spring boot、es、rest的版本問題,折騰了好久,寫這篇文章的目的一是分享一下,也是為了紀念當時的摸爬滾打。
注:集成過程中有參考部分網友的文章,在此表示感謝。
版本和環境
JDK: 1.8
spring boot: 2.3.2
elasticsearch: 7.6.2
IDE: 宇宙第一的IDEA
Elasticsearch、IK分詞器的安裝和配置
我安裝的elasticsearch版本為7.6.2,網上教程很多,這里不再贅述
分詞器:IK
測試:
http://127.0.0.1:9200/alarm_reason_index/_search
{ "query": { "match": { "planTitle": "那時候" } }, "sort": [ "_score", { "timestamp": { "order": "desc", "unmapped_type": "long" } } ], "from": 0, "size": 4 }
結果
{ "took": 5, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 5, "relation": "eq" }, "max_score": null, "hits": [ { "_index": "alarm_reason_index", "_type": "_doc", "_id": "1315844356900499451", "_score": 4.377079, "_source": { "_class": "com.knowswift.stnl.bean.plan.po.EmergencyPlanES", "emergencyPlanId": "1315844356900499451", "planCode": "37090476851", "planTitle": "那時的卡卡是", "planLevel": "一級", "createTime": "2020-10-24 10:20:18", "timestamp": 1603506018 }, "sort": [ 4.377079, 1603506018 ] } ... ] } }
集成過程
1. pom文件
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> <version>2.3.4.RELEASE</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>7.6.2</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.6.2</version> <exclusions> <exclusion> <artifactId>elasticsearch-rest-client</artifactId> <groupId>org.elasticsearch.client</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>7.6.2</version> </dependency>
其中
- spring-boot-starter-data-elasticsearch 需要2.3以上;
- org.elasticsearch和org.elasticsearch.client的版本需要和elasticsearch一致。
2. application.yml
spring:
elasticsearch:
rest:
uris: 127.0.0.1:9200
3. 實體類
@Data @Document(indexName = "pl_index") public class PlES { @Id private String id; @Field(analyzer = "ik_smart") private String code; @Field(analyzer = "ik_max_word") private String title; @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "uuuu-MM-dd HH:mm:ss") private LocalDateTime createTime; @Field private Long timestamp; public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; timestamp = createTime.toEpochSecond(ZoneOffset.of("+8")); } }
4.ElasticRepository
用於調用ElasticsearchRepository的方法
public interface PlElasticRepository extends ElasticsearchRepository<PlES, String> { }
5.業務層接口IElasticService
基礎的接口方法
public interface IElasticService<T, ID> { boolean createIndex(Class<T> tClass); boolean deleteIndex(String index); void save(T entity); void saveBatch(List<T> list); List<T> findAll(); Page<T> query(String key); void deleteById(ID id); @Resource ElasticsearchRepository getRepository(); }
6.業務層實現類
public class ElasticServiceImpl<M extends ElasticsearchRepository<T, ID>, T, ID> implements IElasticService<T, ID> { @Resource private ElasticsearchRestTemplate elasticsearchRestTemplate; @Resource RestHighLevelClient restHighLevelClient; @Resource private M getRepository; @SneakyThrows @Override public boolean createIndex(Class<T> clazz) { Document document = clazz.getAnnotation(Document.class); GetIndexRequest request = new GetIndexRequest(document.indexName()); boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT); if (exists){ return true; } CreateIndexRequest createIndexRequest = new CreateIndexRequest(document.indexName()); restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT); return true; } @Override public boolean deleteIndex(String index) { return elasticsearchRestTemplate.deleteIndex(index); } @Override public void save(T entity) { T save = getRepository.save(entity); } @Override public void saveBatch(List<T> list) { Iterable<T> ts = getRepository.saveAll(list); } @Override public List<T> findAll() { // FieldSortBuilder timestamp = SortBuilders.fieldSort("timestamp").order(SortOrder.DESC); return (List<T>) getRepository.findAll(Sort.by("timestamp").descending()); } @Override public Page<T> query(String key) { return null; } @Override public M getRepository() { return this.getRepository; } public void deleteById(ID id) { getRepository.deleteById(id); } }
6. 業務實現方法
@Service public class PlElasticService extends ElasticServiceImpl<PlElasticRepository, PlES, String> { @SneakyThrows public SearchHits list(String key, int pageNo, int pageSize) { SearchRequest request = new SearchRequest(); SearchSourceBuilder builder = new SearchSourceBuilder(); if (StringUtils.isNotBlank(key)) { builder.query(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("title", key))) .sort(SortBuilders.scoreSort()); } else { builder.query(QueryBuilders.boolQuery().must(QueryBuilders.matchAllQuery())).sort(SortBuilders.scoreSort());; } // 排序 FieldSortBuilder order = SortBuilders.fieldSort("timestamp").unmappedType("long").order(SortOrder.DESC); // 分頁 應注意,pageNo在此處並非是頁碼,而是當前頁的第一行,也就是SQL語句的 offset builder.from(pageNo).size(pageSize).sort(order); builder.timeout(new TimeValue(2, TimeUnit.SECONDS)); // 高亮 builder.highlighter(new HighlightBuilder().field("title").preTags("<span style=\"color:red\">").postTags("</span>")); request.source(builder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); // long value = hits.getTotalHits().value; SearchHits searchHits = response.getHits(); SearchHit[] hits1 = searchHits.getHits(); for (SearchHit documentFields : hits1) { Map<String, HighlightField> map = documentFields.getHighlightFields(); HighlightField title = map.get("title"); if (title == null) { continue; } Text[] fragments = title.fragments(); if (fragments.length == 0) { continue; } // 將es結果集轉成實體類 String sourceAsString = documentFields.getSourceAsString(); PlES plES = JSONObject.parseObject(sourceAsString, PlES.class); // 高亮替換 plES.setTitle(fragments[0].toString()); } return searchHits; } }
至此,一個簡單的分頁查詢方法就已經完成。
存在問題
所使用的elasticsearch客戶端是 restHighLevelClient,在長時間不請求之后,再次請求會出現異常:遠程主機強迫關閉了一個現有的連接,再次請求又正常。
暫時了解到的,這可能是elasticsearch存在——會殺死長時間空閑連接——的問題,或者是我不知道如何解決的問題。
目前我使用的解決方法是添加一個定時器,持續請求,保證連接是活躍的
@Scheduled(cron = "0 0/2 * * * *") public void keepESAlive() { try { restHighLevelClient.info(RequestOptions.DEFAULT); } catch (IOException ignored) { } }
如果有更好的解決辦法,請在評論分享。
END
轉載於:https://blog.csdn.net/Hades_iphone/article/details/109459191