Spring Boot 2.3.2 集成elasticsearch 7.6.2实战

前言

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

其中

  1. spring-boot-starter-data-elasticsearch 需要2.3以上;
  2. 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

原文地址:https://www.cnblogs.com/it-deepinmind/p/14273024.html