elasticsearch通用工具类

这几天写了一个关于es的工具类,主要封装了业务中常用es的常用方法。

  本文中使用到的elasticsearch版本6.7,但实际上也支持es7.x以上版本,因为主要是对springboot提供的:ElasticsearchRestTemplate 提供的API做的二次封装。目的是:让不懂es的开发人员新手也能轻松上手。

一、概述

整个工程分为es-api与es-server。

es-api为对外公共jar,包含了es映射实体;

es-server包含了具体的es工具类与Repository接口(下文会提到)。并通过necos配置中心统一管理es配置参数。

外部业务模块可引入es-api jar maven依赖,由Jar提供的入口,通过httpClient或feign调用(springcloud分布式项目)到es-server服务上的es工具类,得到需要的数据。

二、使用

这里仅以springcloud分布式项目简单为例

业务方用户服务user 引入es-api maven

/**
 * @author: shf
 * description: es-server feign接口
 */
public interface EsServerClient {
    @PostMapping(value = "/queryList", produces = {"application/json"})
    public <T> List<T> queryList(@RequestBody T t);
}

在user服务中创建feignClient继承自es-api中的EsServerClient

@FeignClient(contextId = "esFeignClient", name = "es-server")
public interface EsFeignClient extends EsServerClient {
}

在user服务的代码中即可调用

@AutoWired
public EsFeignClient esFeignClient;
 
 
public void test() {
   //.......如业务方Dto与es映射实体转换 等省略
  //....
  EmployeeEs employee = new EmployeeEs();
  List queryList = Stream.of(employee.new QueryRelation<String>("张三", EntityEs.SHOULD, 5F), employee.new QueryRelation<String>("李四", EntityEs.SHOULD, 20F)).collect(Collectors.toList());
  employee.setFieldQueryMap(new EsMapUtil().put(EmployeeEs::getUserName, queryList).put(EmployeeEs::getUserAge, employee.new RangeRelation(20, EntityEs.GTE, null, null, EntityEs.MUST)));
  //排序查询
  employee.setOrderMap(new EsMapUtil().put(EmployeeEs::getUserId, SortOrder.DESC));
  List<EmployeeEs> employeeEs = esFeignClient.queryList(employee);
  //.....employeeEs与业务方Dto转换
}

三、具体实现

es-api 引入es依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>transport</artifactId>
    <version>6.7.0</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.7.0</version>
</dependency>

排除后重新引入对应的Es版本6.7,避免因版本不一致导致的一些坑。

es-server 服务 application.yml配置es

spring:
 elasticsearch:
  rest:
    #ES的连接地址,多个地址用逗号分隔
    uris: localhost:9200
    username: kibana
    password: pass
    #连接超时时间
    connection-timeout: 1000
    #读取超时时间
    read-timeout: 1000

一、映射实体

1、与ES mapping结构对应的映射实体:EmployeeEs

说明:

①设置的字段与该es对应的该索引完全对应,不存在多余字段。

②项目中引入了spring-boot-starter-data-elasticsearch,所以可直接使用注解形式设置索引信息与mapping结构信息,详见示例

③@JsonIgnoreProperties({"orderMap","pageNumber","pageSize","highlightFields","preTags","postTags","fieldQueryMap","scrollId","aggregationMap","multiLayerQueryList"})

作用:在保存文档到es时忽略父类EntityEs中的功能性字段,下文会提到

④@EsRepository(EmployeeEsRepository.class)

作用:通过注解过去该映射对应的Repository接口

/**
 * 员工对象
 * <p>
 * 注解:@Document用来声明Java对象与ElasticSearch索引的关系 indexName 索引名称 type 索引类型 shards 主分区数量,默认5
 * replicas 副本分区数量,默认1 createIndex 索引不存在时,是否自动创建索引,默认true
 */
@Setter
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EsRepository(EmployeeEsRepository.class)
@JsonIgnoreProperties({"orderMap","pageNumber","pageSize","highlightFields","preTags","postTags","fieldQueryMap","scrollId","aggregationMap","multiLayerQueryList"})
@Document(indexName = "employee_index", type = "employee_type", shards = 1, replicas = 0, createIndex = true)
public class EmployeeEs extends EntityEs {
 
 @Id
 @Field(type = FieldType.Keyword)
    private Long userId;
 
 //@Field(type = FieldType.Text, analyzer = "ik_max_word")
 @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "ik_max_word"), otherFields = @InnerField(suffix = "trueName", type = FieldType.Keyword))
    private String userName;
 
 @Field(type = FieldType.Keyword)
    private String userCode;
 
 @Field(type = FieldType.Integer)
    private Integer userAge;
 
 @Field(type = FieldType.Keyword)
    private String userMobile;
 
 @Field(type = FieldType.Date)
    private Date birthDay;
 
 
@Field(type = FieldType.Keyword)
 private String userSex;
 
 
 @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String remarks;
}

2、Repository接口:EmployeeEsRepository

/**
 * @author: shf
 * description: 可根据映射实体设置自动生成mapping结构;支持bean的增删改查操作
 * date: 2022/2/23 10:47
 */
@Component
public interface EmployeeEsRepository extends CrudRepository<EmployeeEs,Long> {
}

二、功能性实体类:EntityEs

说明:

①所有字段非es索引中的mapping属性字段,均为功能性字段,如排序、高亮、分页设置和一些常量设置等,详见贴码

②所有es映射实体类均需继承该实体

/**
 * @author: shf description: 功能性字段(非mapping结构字段)
 * date: 2022/3/1 15:07
 */
@Data
public class EntityEs {

    /**
     * 组合多查询常量
     */
    /**
     * 文档 必须 匹配这些条件才能被查询到。相当于sql中的and
     */
    public static String MUST = "must";

    /**
     * 文档 必须不 匹配这些条件才能被查询到。相当于sql中的 not
     */
    public static String MUST_NOT = "must_not";

    /**
     * 如果满足这些语句中的任意语句,将增加 _score ,否则,无任何影响。它们主要用于修正每个文档的相关性得分。相当于sql中的or
     */
    public static String SHOULD = "should";

    /**
     * 必须 匹配,但它以不评分、过滤模式来进行。这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档
     */
    public static String FILTER = "filter";

    /**
     * 至少匹配一项should子句
     */
    public static String MINIMUM_SHOULD_MATCH = "minimum_should_match";

    /**
     * 多字段排序查询
     */
    public EsMapUtil orderMap;

    /**
     * 分页查询
     */
    public Integer pageNumber;

    public Integer pageSize;

    /**
     * 游标分页ID
     */
    public String scrollId;

    /**
     * 游标分页ID有效期 单位:毫秒
     */
    public static Long scrollIdExpireTime = 1000 * 60 * 2L;

    /**
     * 游标分页ID最小有效期 单位:毫秒
     */
    public static Long scrollIdMinExpireTime = 1000L;

    /**
     * 高亮查询
     */
    public List<String> highlightFields;

    public String preTags;

    public String postTags;

    /**
     * 字段查询
     */
    public EsMapUtil fieldQueryMap;

    /**
     * 聚合查询,当前只支持单个字段分组聚合count与sum,只针对keyword类型字段有效
     */
    public EsMapUtil aggregationMap;

    public static String COUNT = "count";
    public static String SUM = "sum";

    /**
     * 多层(bool)查询
     */
    public List multiLayerQueryList;

    /**
     * 范围查询常量
     */
    public static String GT = "gt";
    public static String GTE = "gte";
    public static String LT = "lt";
    public static String LTE = "lte";

    @Data
    public class RangeRelation<T> {

        //String fieldKey;

        T fieldMinValue;

        String fieldMinMode;

        T fieldMaxValue;

        String fieldMaxMode;

        String queryMode;

        public RangeRelation(T fieldMinValue, String fieldMinMode, T fieldMaxValue, String fieldMaxMode, String queryMode) {
            this.fieldMinValue = fieldMinValue;
            this.fieldMinMode = fieldMinMode;
            this.fieldMaxValue = fieldMaxValue;
            this.fieldMaxMode = fieldMaxMode;
            this.queryMode = queryMode;
        }
    }

    @Data
    public class QueryRelation<T> {

        T fieldValue;

        String queryMode;

        Float boostValue;

        public QueryRelation(T fieldValue, String queryMode) {
            this.fieldValue = fieldValue;
            this.queryMode = queryMode;
        }

        public QueryRelation(T fieldValue, String queryMode, Float boostValue) {
            this.fieldValue = fieldValue;
            this.queryMode = queryMode;
            this.boostValue = boostValue;
        }
    }

    @Data
    public class MultiLayerRelation {

        String queryMode;

        EsMapUtil map;

        List<EntityEs.MultiLayerRelation> multiLayerList;

        public MultiLayerRelation(String queryMode, EsMapUtil map) {
            this.queryMode = queryMode;
            this.map = map;
        }

        public MultiLayerRelation(String queryMode, EsMapUtil map, List<MultiLayerRelation> multiLayerList) {
            this.queryMode = queryMode;
            this.map = map;
            this.multiLayerList = multiLayerList;
        }
    }
}

三、小工具:EsMapUtil

说明:封装了一个map工具,编码简洁链式调用,应用了方法引用特性避免了字符串硬编码造成单词拼错的情况。

/**
 * @author: shf description: 函数式接口 便于方法引用获取实体字段名称
 * date: 2022/3/4 13:41
 */
@FunctionalInterface
public interface IGetterFunction<T> extends Serializable{
    Object get(T source);
}

/**
 * @author: shf
 * description
 * date: 2019/11/13 18:30
 */
public class EsMapUtil extends LinkedHashMap<String, Object> {

    public <T> EsMapUtil put(IGetterFunction<T> fn, Object value) {
        String key = getFieldName(fn);
        super.put(key, value);
        return this;
    }

    public <T> EsMapUtil putStr(String key, Object value) {
        super.put(key, value);
        return this;
    }

    private static Map<Class, SerializedLambda> CLASS_LAMDBA_CACHE = new ConcurrentHashMap<>();

    /***
     * 转换方法引用为属性名
     * @param fn
     * @return
     */
    public <T> String getFieldName(IGetterFunction<T> fn) {
        SerializedLambda lambda = getSerializedLambda(fn);
        String methodName = lambda.getImplMethodName();
        String prefix = null;
        if (methodName.startsWith("get")) {
            prefix = "get";
        }
        // 截取get之后的字符串并转换首字母为小写
        return toLowerCaseFirstOne(methodName.replace(prefix, ""));
    }

    /**
     * 首字母转小写
     *
     * @param s
     */
    public String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }
    }

    public static SerializedLambda getSerializedLambda(Serializable fn) {
        SerializedLambda lambda = CLASS_LAMDBA_CACHE.get(fn.getClass());
        if (lambda == null) {
            try {
                Method method = fn.getClass().getDeclaredMethod("writeReplace");
                method.setAccessible(Boolean.TRUE);
                lambda = (SerializedLambda) method.invoke(fn);
                CLASS_LAMDBA_CACHE.put(fn.getClass(), lambda);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return lambda;
    }
}

四、Es通用工具类EsService

/**
 * @author: shf description: es工具类,支持:分页(支持游标分页)、高亮(支持自定义标签)、范围查找、bool组合查询、多层bool套bool、多字段排序、加权重、聚合、二级字段查询
 * date: 2022/2/23 10:54
 */
@Component
@Slf4j
public class EsService {
    @Autowired
    private ElasticsearchRestTemplate restTemplate;
 
    @Autowired
    private ApplicationContext context;
 
 
    /**
     * 判断索引是否存在
     *
     * @return boolean
     */
    public <T> boolean indexExists(Class<T> clazz) {
        return restTemplate.indexExists(clazz);
    }
 
    /**
     * 判断索引是否存在
     *
     * @param indexName 索引名称
     * @return boolean
     */
    public boolean indexExists(String indexName) {
        return restTemplate.indexExists(indexName);
    }
 
    /**
     * 创建索引(推荐使用:因为Java对象已经通过注解描述了Setting和Mapping)
     *
     * @return boolean
     */
    public <T> boolean indexCreate(Class<T> clazz) {
        boolean createFlag = restTemplate.createIndex(clazz);
        boolean mappingFlag = restTemplate.putMapping(clazz);
        return createFlag && mappingFlag;
 
    }
 
    /**
     * 索引删除
     *
     * @param indexName 索引名称
     * @return boolean
     */
    public boolean indexDelete(String indexName) {
        return restTemplate.deleteIndex(indexName);
    }
 
    /**
     * 新增数据
     *
     * @param bean 数据对象
     */
    publ
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值