前言
此前虽然有尝试过集成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