使用ElasticSearch 7.10.1(搜索引擎)

1,393 阅读5分钟

前言

这边没有提供安装es的流程,网上可以找一下。在我的csdn上早就有这篇文章了,我现在搬过来做了一些修改,这边的文章较为完全,其中贴的代码,大部分不是项目中的,是我练习时的代码,少部分是贴的项目中的。如果要看此文章,还是要建议要达到自己练习过原生语法,自己可以搭建一个kibana去练习。新手的话,可以去看看狂神的视频,个人觉得可以的。公司项目中需要用到es,当时没用过,直接上手了es 的最新版。然后发现项目中springboot版本比较低,经常报错,然后又小小的学习了一下,新版的es相比较旧版(目前用的包 6.4的clint)的话,有些参数必须要。其中_type 这个参数必须要的,但是我感觉用处也不是很大。欢迎指定,一起学习!

这里只贴了我在项目中封装的crud,这是最开始封装的,并没有很完美,但足以拿去直接使用。后续想改,但要动很多,就暂时没改。

@Slf4j
public class EsSearchUtils {
    private static RestHighLevelClient restHighLevelClient = SpringUtils.getBean(RestHighLevelClient.class);

    // 用于查询所有根节点
    @Getter
    private static List<Integer> proTypeIdList = new ArrayList<>();

    /**
     * es添加的方法
     *
     * @return
     */
    public static <T> boolean add(String indexName, T entity) {
        IndexRequest indexRequest = new IndexRequest(indexName);
        indexRequest.type("_doc");
        indexRequest.source(JSON.toJSONString(entity), XContentType.JSON);
        // indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        // indexRequest.setRefreshPolicy("1s");
        // WriteRequest.RefreshPolicy refreshPolicy = indexRequest.getRefreshPolicy();
        // String value = refreshPolicy.getValue();
        // System.out.println("添加是否开启了不延迟:"+value);
        try {
            restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * es更新的方法
     *
     * @return
     */
    public static <T> boolean update(String indexName, T entity, String _id) {
        UpdateRequest updateRequest = new UpdateRequest(indexName, "_doc", _id);
        updateRequest.doc(JSON.toJSONString(entity), XContentType.JSON);
        // updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        try {
            restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * es删除的方法
     *
     * @return
     */
    public static boolean delete(String indexName, String _id) {
        DeleteRequest deleteRequest = new DeleteRequest(indexName, "_doc", _id);
        // deleteRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        try {
            restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 封装es查询 操作
     *
     * @param queryBuilder
     * @param searchRequest
     * @param sortName      需要排序的字段名  默认降序
     * @param page          第几页
     * @param pageSize      每页的条数
     */
    public static List<Map<String, Object>> searchList(QueryBuilder queryBuilder, String[] searchArray, SearchRequest searchRequest, String sortName, Integer page, Integer pageSize) {
        //构建构造者
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.timeout(TimeValue.timeValueMillis(2000));
        // 限制字段(前者是你想要什么字段,后者是排除什么字段)
        if (null != searchArray) {
            // searchSourceBuilder.fetchSource(searchArray, new String[]{});
            searchSourceBuilder.fetchSource(searchArray, null);
        }
        // 分页条件
        if (!"".equals(page) && null != page) {
            if (page < 0) {
                page = 0;
            }
            searchSourceBuilder.from(page);
        }
        // 分页条件
        if (!"".equals(pageSize) && null != pageSize) {
            if (pageSize <= 0) {
                pageSize = 1;
            }
            searchSourceBuilder.size(pageSize);
        } else {
            // 目前设置1万条
            searchSourceBuilder.size(10000);
        }
        // 排序条件
        if (!"".equals(sortName) && null != sortName) {
            searchSourceBuilder.sort(sortName, SortOrder.DESC);
        }
        // 查询条件
        if (null != queryBuilder) {
            searchSourceBuilder.query(queryBuilder);
        }
        //这里是针对关键字高亮。其实就是将前端代码放在了这里
        //searchSourceBuilder.highlighter().preTags("<p class='one' style='color:red'>").postTags("</p>").field("content");  
        SearchRequest source = searchRequest.source(searchSourceBuilder);
        SearchResponse search = null;
        try {
            search = restHighLevelClient.search(source, RequestOptions.DEFAULT);
        } catch (Exception e) {
            e.printStackTrace();
            search = null;
            log.info("============查询出现异常=========");
        }
        List<Map<String, Object>> list = new ArrayList<>();
        if (null != search) {
            for (SearchHit hit : search.getHits().getHits()) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                sourceAsMap.put("_id", hit.getId());
                sourceAsMap.put("_score", hit.getScore());
                list.add(sourceAsMap);
            }
        }
        return list;
    }
}

一、索引的操作

直接上手api吧,ElasticSearchCommon 是自己将所有的索引名字封装出来的,这里是只需要一个字符串的! 提前准备的话:导入springboot封装过后的es(也可以不用这样)依赖。然后写一下配置类,配置类我贴出来,再注入一下

依赖

      <!--    elasticsearchs    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
        </dependency>

配置类

@Configuration
public class ElasticSearchConfig {
    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost(ip,端口,"http")
                )
        );
        return client;
    }
}

哪些地方要使用就把RestHighLevelClient注入过来

    @Autowired
    // 1.指定方法名
    @Qualifier("restHighLevelClient")
    private RestHighLevelClient restHighLevelClient;

1.新增索引

        // 创建索引请求
        CreateIndexRequest createIndex = new CreateIndexRequest(ElasticSearchCommon.LG_SESSION);
        // 客户端执行请求
        CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndex, RequestOptions.DEFAULT);

2.删除索引

        DeleteIndexRequest deletRequest = new DeleteIndexRequest(ElasticSearchCommon.LG_SESSION);
        AcknowledgedResponse delete = restHighLevelClient.indices().delete(deletRequest, RequestOptions.DEFAULT);

3.查询索引

        GetIndexRequest getIndexRequest = new GetIndexRequest(ElasticSearchCommon.LG_SESSION);
        boolean exists = restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);

二、文档操作(重点是查询吧)

2.1新增文档

2.1.1 单独添加

        IndexRequest indexRequest = new IndexRequest(ElasticSearchCommon.LG_SESSION);
        // 版本低一点的话需要加上type类型
        indexRequest.type("自己定义的类型");
        SessionEntity sessionEntity = new SessionEntity();
        sessionEntity.setId(1);
        sessionEntity.setConversationTime(getNowTime());
        sessionEntity.setMsgState(1);
        sessionEntity.setAddrImages("/addr/images");
        sessionEntity.setVipId("VIP545465");
        sessionEntity.setCustomerServerName("弟弟1号");
        IndexRequest source = indexRequest.source(JSON.toJSONString(sessionEntity), XContentType.JSON);
        IndexResponse index = restHighLevelClient.index(source, RequestOptions.DEFAULT);
        System.out.println("预留内容添加状态:"+index.status());

2.1.2 批量添加

 		// 创建请求
        BulkRequest bulkRequest = new BulkRequest(ElasticSearchCommon.LG_SESSION);
        // 创建批量数据
        List<ProblemTypeEntity> list = new ArrayList<ProblemTypeEntity>();
        list.add(new ProblemTypeEntity(1,"111"));
        list.add(new ProblemTypeEntity(2,"222"));
        list.add(new ProblemTypeEntity(3,"333"));
        list.add(new ProblemTypeEntity(4,"444"));
        list.add(new ProblemTypeEntity(5,"555"));
        list.add(new ProblemTypeEntity(6,"666"));
        list.add(new ProblemTypeEntity(7,"777"));
        list.add(new ProblemTypeEntity(8,"888"));
        // 循环添加
        for (int i = 0;i<list.size();i++){
            bulkRequest.add(
                    new IndexRequest(ElasticSearchCommon.LG_SESSION)
							//.id(""+(i+1)) // 设置es 当中_id,不设置的话是一串字符
                            .source(JSON.toJSONString(list.get(i)),XContentType.JSON)
            );
        }

        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println("循环添加的数据状态:"+bulk.status());

2.2删除文档

7.10版可以这样写,根据其他字段条件去查询删除,较新版本的删除这我觉得是最方便的

        DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(ElasticSearchCommon.LG_PROBLEM);
        DeleteByQueryRequest deleteByQueryRequest1 = deleteByQueryRequest.setQuery(QueryBuilders.termQuery("id", 12));
        BulkByScrollResponse bulkByScrollResponse = restHighLevelClient.deleteByQuery(deleteByQueryRequest1, RequestOptions.DEFAULT); 
        System.out.println("删除了几条:"+bulkByScrollResponse.getStatus().getDeleted());

老一点的版本,就必须带上_type,我所有的都叫_type,目前没感觉到用处,目前探索到,删除只能更具_id 去查询

DeleteRequest deleteRequest = new DeleteRequest(ElasticSearchCommon.LG_PROBLEM,"_doc","_id的值");
DeleteResponse delete = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);

2.3更改文档

        //第一种写法
        // 创建请求
        UpdateRequest updateRequest = new UpdateRequest(ElasticSearchCommon.LG_SESSION,"这里就是_id了");
        // 创建需要更改的数据
        UserEntity userEntity = new UserEntity("更改后的数据",28, CommonUtils.getTimeFormat());
        // 修改的数据放入请求中
        updateRequest.doc(JSON.toJSONString(userEntity),XContentType.JSON);
        // 客户端发送请求
        UpdateResponse update = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        // 打印状态
        System.out.println("更新状态:"+update.status());

2.4查询文档(重点)

你要去了解QueryBuilders里面的一些方法。

 *QueryBuilders.termQuery("key", obj) 完全匹配,输入的查询内容是什么,就会按照什么去查询,并不会解析查询内容,对它分词。
 *QueryBuilders.termsQuery("key", obj1, obj2..)   一次匹配多个值
 *QueryBuilders. matchQuery("key", Obj) 单个匹配,match查询,会将搜索词分词,再与目标查询字段进行匹配,若分词中的任意一个词与目标字段匹配上,则可查询到。
 *QueryBuilders. multiMatchQuery("text", "field1", "field2"..);  匹配多个字段, field有通配符忒行
 *QueryBuilders. matchAllQuery();         匹配所有文件
 *QueryBuilders.matchPhraseQuery(“supplierName”,param);默认使用 match_phrase 时会精确匹配查询的短语,需要全部单词和顺序要完全一样,标点符号除外
 *QueryBuilders.wildcardQuery(“supplierName”,"*"+param+"*") ;条件wildcard不分词查询,加*(相当于sql中的%)表示模糊查询,加keyword表示查不分词数据 

2.4.1

builderList 我自己写的操作es库的一个方法;这里和上面在项目中使用的封装大致一样,这里名字换了一下,这个就做参考就行了,要在项目中使用的话,就用上面的 随机一个方法:

 		SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.LG_PROBLEM);
        MatchQueryBuilder should = QueryBuilders.matchQuery("content", content).operator(Operator.OR);
        List<Map<String, Object>> list = builderList(should, searchRequest,null,0,5);
        HashMap<String,Object> hashMap = new HashMap<String, Object>();
        // 分词未搜到则 返回热度最高的5个数据
        if (list.size() <= 0){
            List<Map<String, Object>> heatList = MostHeat();
            hashMap.put("heat","未查询到关键词,返回热度最高的五个问题!");
            heatList.add(0,hashMap);
            return heatList;
        }

builderList方法:封装的查询 //构建构造者

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.timeout(TimeValue.timeValueMillis(1));
        // 分页条件
        if (!"".equals(page) && null != page){
            if (page < 0 ){
                page = 0;
            }
            searchSourceBuilder.from(page);
        }
        // 分页条件
        if (!"".equals(pageSize) && null != pageSize){
            if (pageSize <= 0 ){
                pageSize = 1;
            }
            searchSourceBuilder.size(pageSize);
        }
        // 排序条件
        if (!"".equals(sortName) && null != sortName){
            searchSourceBuilder.sort(sortName, SortOrder.DESC);
        }
        // 查询条件
        if (null != queryBuilder ){
            searchSourceBuilder.query(queryBuilder);
        }
        //searchSourceBuilder.highlighter().preTags("<p class='one' style='color:red'>").postTags("</p>").field("content");
        SearchRequest source = searchRequest.source(searchSourceBuilder);
        SearchResponse search = restHighLevelClient.search(source, RequestOptions.DEFAULT);
        List<Map<String,Object>> list = new ArrayList<>();
        for (SearchHit hit : search.getHits().getHits()) {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            sourceAsMap.put("_id",hit.getId());
            //sourceAsMap.remove("addrImages");
            list.add(sourceAsMap);
        }

三、附带上kibana 中间的一些语法,可以根据这个去尝试一下其他Java查询方法

3.1多条件查询

customerName.keyword 代表不分词搜索,整体搜索,完全匹配 query.term,之前网上也是说不分词搜索,欢迎大佬指定

在这里插入图片描述

3.2must(必须),must_not(不必须),shuold(相当于or)

在这里插入图片描述

3.3过滤条件(gte,gt,lte,lt相信你们都知道)

在这里插入图片描述

3.4限制搜索条件(这里表示从第0条开始查询5条)、sort(numbers是字段,order不要变,desc降序,asc升序)排序

在这里插入图片描述

3.5高亮显示(其实就是放了前端代码过来)注意一下hightlight的用法,目前项目中还没用到,你们可以去探索

在这里插入图片描述

三、总结与注意

还是得自己去摸索着玩吧,这样踩几个坑就知道了,后续会更新其他的。最重要就多在查询上面花点心思,还有很多查询未摸索到。这里没写到ik,但项目中是使用到了的。es注意的地方,他的所有增删改都不是及时的去刷新索引中的信息的,网上有很多说在3s 有的说在1s,我这边感觉大致是1s。可以通过参数去把延迟刷新去除,如果频繁的操作容易破坏索引和其他原因,暂时还不太清楚。