SpringBoot 3.x集成ES 8.X 工具类整理

  近期用到了spring boot3.0,当然jdk也一起跟着升级到了17,后面有需求要用到ES,前后捣鼓了一整子发现,大部分的人都还在用7.X及以下的ES,所以找了很久的资料,主要是ES的语法有点不太好用,一大堆的lambda,索性就自己封装了一个工具类,语法类似于mybatis-plus,这样的语法我想大家应该都比较熟悉,话不多说了,直接上代码

项目结构

TestController
package com.xxx.xxx.es.test.controller;

import co.elastic.clients.elasticsearch._types.query_dsl.RegexpQuery;
import com.alibaba.fastjson2.JSONObject;
import com.xxx.xxx.es.test.entity.User;
import com.xxx.xxx.es.test.utils.ElasticsearchUtils;
import com.xxx.xxx.es.test.utils.ElasticsearchUtils.*;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;


@RestController
@RequestMapping("/es")
@RequiredArgsConstructor
public class TestController {

    private final ElasticsearchUtils elasticsearchUtils;

    @GetMapping("/test")
    public Object test() throws IOException {
        String index = "user-index";
        //删除索引测试
        deleteIndexTest(index);
        //创建索引测试
        createIndexTest(index);
        //新增数据测试
        addTest(index);
        //删除数据测试
        deleteTest(index);
        //修改数据测试
        updateTest(index);
        //条件查询测试
        List<?> listResult = getListTest(index);
        if (false){
            return getResultData(listResult);
        }
        //分页查询测试
        List<?> pageResult = getPageTest(index);
        if (false){
            return getResultData(pageResult);
        }
        //count
        long count = elasticsearchUtils.count(new EsSearchWrapper(index));
        //ES未封装方法
        List<?> esFunResult = getFunResultTest(index);
        if (true){
            return getResultData(esFunResult);
        }
        //最后我还做了分组的聚合,es虽然提供了分组的功能,但是没有帮我们把数据聚合,这里我按mysql的逻辑聚合了,用法也与之相似
        //分组查询测试
        List<Object> groupList = getGroupListTest(index);
        return getResultData(groupList);
    }

    private List<Object> getGroupListTest(String index) throws IOException {
        //加点数据
        List<User> addBatchList = new ArrayList<>();
        User user = new User();
        user.setId(9);
        user.setName("赵六小儿");
        user.setSex(1);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        user = new User();
        user.setId(10);
        user.setName("孙七小儿");
        user.setSex(2);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        elasticsearchUtils.addBatch(index, addBatchList);
        //方法1
//        EsSearchWrapper groupWrapper = new EsSearchWrapper<>(index);
//        groupWrapper.groupBy("sex", "name.keyword");
        //当然这里可以不select,我就没有让它报错(和mysql不同,mysql会报错), 但是如果只是要这两个字分组,就没必要查其他字段了
        //groupWrapper.select("sex","name");
        //lambda也支持,基本一直
        //EsSearchWrapper<User> groupWrapper = new EsSearchWrapper<>();
        //List<User> groupList = elasticsearchUtils.list(groupWrapper, User.class);
        //相当于 select sex,name from user group by sex,name
//        List<Object> groupList = elasticsearchUtils.list(groupWrapper);
        //方法2
        //分组如果要使用到函数的话 比如:max,min等 就要调用另一个重载方法
        EsSearchWrapper groupWrapper = new EsSearchWrapper<>(index);
        groupWrapper.groupBy(EsFunction.of("sex")
                , EsFunction.of("name.keyword")
                , EsFunction.of("id", EsFunctionEnum.SUM));
        //相当于 select sex,name,sum(id) from user group by sex,name
        List<Object> groupList = elasticsearchUtils.list(groupWrapper);
        return groupList;
    }

    private List<?> getFunResultTest(String index) throws IOException {
        //当然ES和mysql还是有点区别的,我所以保留了一些方法
        List<Object> funRestulList = elasticsearchUtils.list(new EsSearchWrapper<>(index)
                //分词,模糊查询 相当于 select * from user where (name like '%赵%' or name like '%小%')
                //当然ES 用的是倒排索引,效率会比mysql高  这里只是说查询出来的效果相似
//                .match(MatchQuery.of(f -> f.field("name").query("赵小")))
                //精确查询
//                .term(TermQuery.of(f -> f.field("name").value("赵六小儿")))
                //keyword text whidcard 等String类型 不支持
//                .range(RangeQuery.of(f -> f.field("id").gte(JsonData.of("7"))))
                //就是match的缩减版 通过slop 这个去控制范围
                //slop == 1 相当于 select * from user where name like '%赵_小%'
                //slop == 2 相当于 select * from user where (name like '%赵_小%' or name like '%赵__小%')
                //slop == 3 相当于 select * from user where (name like '%赵_小%' or name like '%赵__小%' or name like '%赵___小%')
                //以此类推
//                .matchPhrase(MatchPhraseQuery.of(f -> f.field("name").query("赵小").slop(1)))
                //类似 select * from user where name like '赵%'
//                .prefix(PrefixQuery.of(f -> f.field("name").value("赵")))
//                //*:通配符   ?:占位符   类似like      *相当于%  ?相当于_
//                .wildcard(WildcardQuery.of(f -> f.field("name.keyword").value("赵*??")))
                //就是允许你错别字,非特殊情况还是别用,非常消耗性能不说,还很容易和产品扯皮
//                .fuzzy(FuzzyQuery.of(f -> f.field("name.keyword").value("赵漏小儿")))
                //正则匹配 value 正则就是表达式
                .regexp(RegexpQuery.of(f -> f.field("name.keyword").value(".*")))
            );
        return funRestulList;
    }

    private List<?> getPageTest(String index) throws IOException {
        //分页查询
        EsPage<User> esPage = new EsPage<>(1, 10);
        EsSearchWrapper<User> pageWrapper = new EsSearchWrapper<>(index);
        pageWrapper.like(User::getName, "小儿");
        //注意:是and里需要拼接or的条件则必须先调用.or(),调用一次后,后面的拼接条件都是or
        pageWrapper.and(wrapper -> wrapper.or().eq(User::getId, "6").eq(User::getId, "7"));
        elasticsearchUtils.page(esPage, pageWrapper, User.class);
        return esPage.getRecords();
    }

    private List<?> getListTest(String index) throws IOException {
        //条件查询(全量滚动查询)
        EsSearchWrapper<User> listWrapper = new EsSearchWrapper<>(index);
        //如果AUTO或Keyword类型,会自动拼接.keyword 默认精确匹配
        listWrapper.like(User::getName, "小儿");
//        listWrapper.like("name.keyword", "小儿");
        List<User> userList = elasticsearchUtils.list(listWrapper, User.class);
        return userList;
    }

    private void updateTest(String index) throws IOException {
        //修改方法1
        User user = new User();
        user.setId(6);
        user.setName("赵六小儿");
        elasticsearchUtils.updateById(index, user);
        //修改方法2
//        EsUpdateWrapper<User> esUpdateWrapper = new EsUpdateWrapper<>(index);
//        esUpdateWrapper.doc(user);
//        //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
//        esUpdateWrapper.refresh(true);
        List<User> updateBatchList = new ArrayList<>();
        user = new User();
        user.setId(7);
        user.setName("孙七小儿");
        updateBatchList.add(user);
        user = new User();
        user.setId(8);
        user.setName("周八小儿");
        updateBatchList.add(user);
        //批量修改1
        elasticsearchUtils.updateBatch(index, updateBatchList);
        //批量修改2
//        EsBulkWrapper<User> esBulkWrapper = new EsBulkWrapper<>(index);
//        //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
//        esBulkWrapper.refresh(true);
//        esBulkWrapper.update(updateBatchList);
//        elasticsearchUtils.exec(esBulkWrapper);
    }

    private void deleteTest(String index) throws IOException {
        //删除方法1
        elasticsearchUtils.deleteById(index, "3");
        //删除方法2
//        EsDeleteWrapper esDeleteWrapper = new EsDeleteWrapper();
//        esDeleteWrapper.id("1");
//        //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
//        esDeleteWrapper.refresh(true);
//        elasticsearchUtils.delete(esDeleteWrapper);
        //批量删除方法1
        elasticsearchUtils.deleteByIds(index, List.of("4", "5"));
        //批量删除方法2
//        EsBulkWrapper<User> esBulkWrapper = new EsBulkWrapper<>(index);
//        //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
//        esBulkWrapper.refresh(true);
//        esBulkWrapper.delete(List.of("2","3"));
//        elasticsearchUtils.exec(esBulkWrapper);
    }

    private void addTest(String index) throws IOException {
        User user = new User();
        user.setId(3);
        user.setName("张三");
        user.setSex(1);
        user.setCreateTime(new Date());
        //方法1
        elasticsearchUtils.add(index, user);
        //方法2
//        EsCreateWrapper<User> esCreateWrapper = new EsCreateWrapper<>(index);
//        esCreateWrapper.document(user);
//        elasticsearchUtils.add(esCreateWrapper);
        List<User> addBatchList = new ArrayList<>();
        user = new User();
        user.setId(4);
        user.setName("李四");
        user.setSex(1);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        user = new User();
        user.setId(5);
        user.setName("王五");
        user.setSex(2);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        user = new User();
        user.setId(6);
        user.setName("赵六");
        user.setSex(2);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        user = new User();
        user.setId(7);
        user.setName("孙七");
        user.setSex(2);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        user = new User();
        user.setId(8);
        user.setName("周八");
        user.setSex(2);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        //批量新增方法1
        elasticsearchUtils.addBatch(index, addBatchList);
        //批量新增方法2
//        EsBulkWrapper<User> esBulkWrapper = new EsBulkWrapper<>(index);
//        //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
//        esBulkWrapper.refresh(true);
//        esBulkWrapper.create(addBatchList);
//        elasticsearchUtils.exec(esBulkWrapper);
    }

    private void createIndexTest(String index) throws IOException {
        EsCreateIndexWrapper esCreateIndexWrapper = new EsCreateIndexWrapper(index);
        //如果不设置,每次查询最多返回10000条,当然这个要根据情况而定设置太大会影响性能
        esCreateIndexWrapper.setMaxResultWindow(100000);
        /**
         * 无论新增还是编辑,目前es是自动支持动态字段,所以加索引直接加实体字段即可,不需要主动去设置
         * 当然如果自定义的话可以调用下面setDocClass方法
         * 取@Field注解上的值  可以不设置,那么字段都是自动生成类型
         */
        esCreateIndexWrapper.setDocClass(User.class);
        elasticsearchUtils.createIndex(esCreateIndexWrapper);
    }

    private void deleteIndexTest(String index) throws IOException {
        //删除索引方法1
        elasticsearchUtils.deleteIndex(index);
        //删除索引方法2
//        EsDeleteIndexWrapper esDeleteIndexWrapper = new EsDeleteIndexWrapper(index);
//        elasticsearchUtils.deleteIndex(esDeleteIndexWrapper);
    }

    private String getResultData(List<?> result) {
        StringBuilder html = new StringBuilder();
        html.append("<!DOCTYPE html>");
        html.append("<html>");
        html.append("<head>");
        html.append("<meta charset=\"utf-8\" />");
        html.append("<title></title>");
        html.append("</head>");
        html.append("<body>");
        html.append("<table border=\"1\" width=\"50%\" align=\"left\">");
        boolean title = true;
        for (Object p : result) {
            JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(p));
            if (title) {
                html.append("<tr>");
                for (String key : jsonObject.keySet()) {
                    html.append("<td>").append(key).append("</td>");
                }
                title = false;
            }
            html.append("</tr>");
            html.append("<tr>");
            for (String key : jsonObject.keySet()) {
                html.append("<td>").append(Objects.nonNull(jsonObject.get(key)) ? String.valueOf(jsonObject.get(key)) : "").append("</td>");
            }
            html.append("</tr>");
        }
        html.append("</table>");
        html.append("</body>");
        html.append("</html>");
        return html.toString();
    }


}
User
package com.xxx.xxx.es.test.entity;

import lombok.Data;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.io.Serializable;
import java.util.Date;


@Data
public class User implements Serializable {
	//必须有一个id主键字段
	@Field(type= FieldType.Auto)
	private Integer id;
	//自动生成text+keyword,既支持分词又支持全匹配,当然如果数据量过大且需要模糊查询的话,会影响性能,可以考虑用Wildcard
	@Field(type= FieldType.Auto)
	private String name;
	/**
	 * 1:男 0:女
	 */
	@Field(type= FieldType.Integer)
	private Integer sex;
	@Field(type= FieldType.Date)
	private Date createTime;
}
ElasticsearchUtils
package com.xxx.xxx.es.test.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.Refresh;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.SourceFilter;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.util.ObjectBuilder;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * @author: chr
 * @since: 1.0.0
 * @date: 2024/2/28 11:21
 * @des 基于8.7.1版本客户端代码封装
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ElasticsearchUtils {

    private final ElasticsearchClient elasticsearchClient;

    private static final String ID_FIELD = "id";

    private static final String FLAG_KEYWORD = ".keyword";
    //这个建议放到缓存中,如redis等,这个定义了静态是不会被回收的,是一种内存泄漏,这里只是案例,当然如果你的系统小型,接口不多,内存也足够,影响忽略不计
    private static ConcurrentHashMap<Class<?>, String> lambdaCach = new ConcurrentHashMap<>(16);

    /**
     * 创建索引
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public boolean createIndex(EsCreateIndexWrapper wrapper) throws IOException {
        wrapper.initIndexSetting();
        log.info("索引:{} 创建-dsl json:{}", wrapper.getIndex(), wrapper.getCreateIndexRequestDsl().build().toString());
        CreateIndexResponse createIndexResponse = elasticsearchClient.indices().create(c -> wrapper.getCreateIndexRequest());
        log.info("索引:{} 创建成功{}", wrapper.getIndex(), createIndexResponse.toString());
        return Boolean.TRUE;
    }

    /**
     * 删除索引
     *
     * @param index 索引名称
     * @return
     * @throws IOException
     */
    public boolean deleteIndex(String index) throws IOException {
        return deleteIndex(new EsDeleteIndexWrapper(index));
    }

    /**
     * 删除索引
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public boolean deleteIndex(EsDeleteIndexWrapper wrapper) throws IOException {
        if (elasticsearchClient.indices().exists(request -> request.index(wrapper.getIndex())).value()) {
            log.info("索引:{} 创建-dsl json:{}", wrapper.getIndex(), wrapper.getDeleteIndexRequestDsl().build().toString());
            DeleteIndexResponse deleteIndexResponse = elasticsearchClient.indices().delete(s -> wrapper.getDeleteIndexRequest());
            log.info("索引:{} 删除成功{}", wrapper.getIndex(), deleteIndexResponse.toString());
            return Boolean.TRUE;
        }
        log.info("{}索引不存在", wrapper.getIndex());
        return Boolean.FALSE;
    }

    /**
     * 新增
     *
     * @param index 索引名称
     * @param t     doc
     * @return
     * @throws IOException
     */
    public <T> boolean add(String index, T t) throws IOException {
        return add(new EsCreateWrapper<T>(index).document(t).refresh(Boolean.TRUE));
    }

    /**
     * 新增
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public <T> boolean add(EsCreateWrapper<T> wrapper) throws IOException {
        log.info("索引:{} 新增-dsl json:{}", wrapper.getIndex(), wrapper.getCreateRequestDsl().build().toString());
        CreateResponse createResponse = elasticsearchClient.create(c -> (ObjectBuilder) wrapper.getCreateRequest());
        log.info("索引:{} 新增成功 {}", wrapper.getIndex(), createResponse.toString());
        return Boolean.TRUE;
    }

    /**
     * 删除
     *
     * @param index 索引名称
     * @param id    实体id
     * @return
     * @throws IOException
     */
    public boolean deleteById(String index, String id) throws IOException {
        return delete(new EsDeleteWrapper(index).id(id).refresh(Boolean.TRUE));
    }

    /**
     * 删除
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public boolean delete(EsDeleteWrapper wrapper) throws IOException {
        log.info("索引:{} 删除-dsl json:{}", wrapper.getIndex(), wrapper.getDeleteRequestDsl().build().toString());
        DeleteResponse deleteResponse = elasticsearchClient.delete(i -> wrapper.getDeleteRequest());
        log.info("索引:{} 删除成功:{}", wrapper.getIndex(), deleteResponse.toString());
        return Boolean.TRUE;
    }

    /**
     * 更新
     *
     * @param index 索引名称
     * @param t     doc实体
     * @return
     * @throws IOException
     */
    public <T> boolean updateById(String index, T t) throws IOException {
        return updateById(new EsUpdateWrapper<T>(index).doc(t).refresh(Boolean.TRUE), t.getClass());
    }

    /**
     * 更新
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public <T> 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值