当前位置: 首页 > 图灵资讯 > 技术篇> 基于注解式的分布式Elasticsearch的封装

基于注解式的分布式Elasticsearch的封装

来源:图灵教育
时间:2023-06-06 09:34:56

原生Rest Level 许多重复操作,如Client不易使用、构建检索等。

部分增强了bbosselasticsearch:通过注释和实体类自动构建索引,自动刷入文档,复杂的业务检索需要在xml中写Dsl。用法和mybatisplus一样。

依赖

<dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId></dependency><dependency><groupId>com.bbossgroups.plugins</groupId><artifactId>bboss-elasticsearch-spring-boot-starter</artifactId><version>5.9.5</version><exclusions><exclusion><artifactId>slf4j-log4j12</artifactId><groupId>org.slf4j</groupId></exclusion></exclusions></dependency><!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version><scope>provided</scope></dependency>

配置:

import com.rz.config.ElsConfig;import org.frameworkset.elasticsearch.boot.ElasticSearchBoot;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.ApplicationArguments;import org.springframework.boot.ApplicationRunner;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import java.util.HashMap;import java.util.Map;/** * 开始时,BBOSss初始化 * * @author sunziwen * @version 1.0 * @date 2019/12/12 16:54 **/@Component@Order(value = 1)public class StartElastic implements ApplicationRunner {    @Autowired    private ElsConfig config;    @Override    public void run(ApplicationArguments args) throws Exception {        Map properties = new HashMap();        properties.put("elasticsearch.rest.hostNames", config.getElsClusterNodes());        ElasticSearchBoot.boot(properties);    }}

注解和枚举:

package com.rz.szwes.annotations;import java.lang.annotation.*;/** * 标识实体对应的索引信息 * * @author sunziwen * 2019/12/13 10:14 * @version 1.0 **/@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ESDsl {    /**     * xml的位置     */    String value();    String indexName();    /**     * elasticsearch7.x版本删除了属性     */    String indexType() default "";}

package com.rz.szwes.annotations;import java.lang.annotation.*;/** * 指定字段的映射类型 * * @author sunziwen * 2019/12/14 10:06 * @version 1.0 **/@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ESMapping {    ///映射类型    ESMappingType value();    //加权    int boost() default 1;    ///分词标识analyzed、not_analyzed    String index() default "analyzed";    //分词器ik__max_word、standard    String analyzer() default "ik_max_word";    //当String作为分组聚合字段时,需要设置为true    boolean fildData() default false;}

package com.rz.szwes.annotations;/** * Es映射类型枚举(定义了大部分,如有缺失,请用户补充。当前版本基于elasticsearch 6.8 * * @author sunziwen * 2019/12/14 10:09 * @version 1.0 **/public enum ESMappingType {    /**     * 全文搜索。     */    text("text"),    /**     * keyword类型适用于索引结构化(排序、过滤、聚合),只能通过精确值搜索。     */    keyword("keyword"),    /    /**     * -128~127 尽量选择范围较小的数据类型,以满足需求。     */    _byte("byte"),    /**     * -32768~32767     */    _short("short"),    /**     * -2^31~2^31-1     */    _integer("integer"),    /**     * -2^63~2^63-1     */    _long("long"),    /    /**     * 64双精度IEEEEE 754浮点类型     */    _doule("doule"),    /**     * IEEEEEE32单位精度 754浮点类型     */    _float("float"),    /**     * IEEEEEE16半精度 754浮点类型     */    half_float("half_float"),    /**     * 缩放型浮点数     */    scaled_float("scaled_float"),    /    /**     * 时间类型     */    date("date"),    _boolean("boolean"),    /**     * 范围类型     */    range("range"),    /**     * 嵌套类型     */    nested("nested"),    /**     * 地理坐标     */    geo_point("geo_point"),    /**     * 地理地图     */    geo_shape("geo_shape"),    /**     * 二进制类型     */    binary("binary"),    /**     * ip 192.168.1.2     */    ip("ip");    private String value;    ESMappingType(String value) {        this.value = value;    }    public String getValue() {        return value;    }}

工具类:增强Hashmap

package com.rz.szwes.util;import java.util.HashMap;import java.util.function.Supplier;/** * 不支持Lambda表达式的原始Hashmap。特此包装一个 * * @author sunziwen * @version 1.0 * @date 2019/12/13 11:09 **/public class LambdaHashMap<K, V> extends HashMap<K, V> {    public static <K, V> LambdaHashMap<K, V> builder() {        return new LambdaHashMap<>();    }    public LambdaHashMap<K, V> put(K key, Supplier<V> supplier) {        super.put(key, supplier.get());        //流式        return this;    }}

两个核心类:

package com.rz.szwes.core;import cn.hutool.core.util.ClassUtil;import com.alibaba.fastjson.JSON;import com.frameworkset.orm.annotation.ESId;import com.rz.szwes.annotations.ESDsl;import com.rz.szwes.annotations.ESMapping;import com.rz.szwes.util.LambdaHashMap;import org.springframework.util.StringUtils;import java.lang.reflect.Field;import java.lang.reflect.ParameterizedType;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Date;/** * 抽象分析泛型 * * @author sunziwen * 2019/12/14 16:04 * @version 1.0 **/public abstract class AbstractElasticBase<T> {    {   ///初始化分析        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();        // 获取第一类参数的真实类型        Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];        parseMapping(clazz);    }    /**     * 索引名称     */    protected String indexName;    /**     * 索引类型     */    protected String indexType;    /**     * es写dsl的文件路径     */    protected String xmlPath;    /**     * 索引映射     */    protected String mapping;    ///将Class分析成映射JSONString    private void parseMapping(Class<T> clazz) {        if (clazz.isAnnotationPresent(ESDsl.class)) {            ESDsl esDsl = clazz.getAnnotation(ESDsl.class);            this.xmlPath = esDsl.value();            this.indexName = esDsl.indexName();            //如果类型是空的,以索引名为类型            this.indexType = StringUtils.isEmpty(esDsl.indexType()) ? esDsl.indexName() : esDsl.indexType();        } else {            throw new RuntimeException(clazz.getName() + "缺失注解[@ESDsl]");        }        //构建索引映射        LambdaHashMap<Object, Object> put = LambdaHashMap.builder()                                                         .put("mappings", () -> LambdaHashMap.builder()                                                                                             .put(indexType, () -> LambdaHashMap.builder()                                                                                                                                .put("properties", () -> {                                                                                                                                    Field[] fields = clazz.getDeclaredFields();                                                                                                                                    LambdaHashMap<Object, Object> builder = LambdaHashMap.builder();                                                                                                                                    for (Field field : fields) {                                                                                                                                        builder.put(field.getName(), () -> toEsjson(field));                                                                                                                                    }                                                                                                                                    return builder;                                                                                                                                })))        ;        this.mapping = JSON.toJSONString(put);    }    private LambdaHashMap<Object, Object> toEsjson(Field field) {        //基本数据类型        if (ClassUtil.isSimpleTypeOrArray(field.getType())) {            ///限制字符串的大小,设置分词            if (new ArrayList<Class>(Collections.singletonList(String.class)).contains(field.getType())) {                LambdaHashMap<Object, Object> put = LambdaHashMap.builder()                                                                 .put("type", () -> "text")                                                                 .put("fields", () -> LambdaHashMap.builder()                                                                                                   .put("keyword", () -> LambdaHashMap.builder()                                                                                                                                      .put("type", () -> "keyword")                                                                                                                                      .put("ignore_above", () -> 256)));                if (field.isAnnotationPresent(ESMapping.class)) {                    ESMapping esMapping = field.getAnnotation(ESMapping.class);                    //设置聚合分组                    if (esMapping.fildData()) {                        put.put("fildData", () -> true);                    }                    //设置加权                    if (esMapping.boost() != 1) {                        put.put("boost", esMapping::boost);                    }                    //设置是否进入行分词                    if (!"analyzed".equals(esMapping.index())) {                        put.put("analyzed", esMapping::analyzer);                    }                    //分词器                    put.put("analyzer", esMapping::analyzer);                }                return put;            }            //设置默认类型            return LambdaHashMap.builder().put("type", () -> {                if (field.isAnnotationPresent(ESMapping.class)) {                    ESMapping esMapping = field.getAnnotation(ESMapping.class);                    return esMapping.value().getValue();                }                if (new ArrayList<Class>(Arrays.asList(byte.class, Byte.class, short.class, Short.class, int.class, Integer.class, long.class, Long.class)).contains(field.getType())) {                    return "long";                } else if (new ArrayList<Class>(Arrays.asList(double.class, Double.class, float.class, Float.class)).contains(field.getType())) {                    return "double";                } else if (new ArrayList<Class>(Arrays.asList(Date.class, java.sql.Date.class, LocalDate.class, LocalDateTime.class, LocalTime.class)).contains(field.getType())) {                    return "date";                } else if (new ArrayList<Class>(Arrays.asList(boolean.class, Boolean.class)).contains(field.getType())) {                    return "boolean";                }                return "text";            });        } else {            //设置对象类型            LambdaHashMap<Object, Object> properties = LambdaHashMap.builder()                                                                    .put("properties", () -> {                                                                        Field[] fields = field.getType().getDeclaredFields();                                                                        LambdaHashMap<Object, Object> builder = LambdaHashMap.builder();                                                                        for (Field field01 : fields) {                                                                            builder.put(field01.getName(), toEsjson(field01);                                                                        }                                                                        return builder;                                                                    });            if (field.isAnnotationPresent(ESMapping.class)) {                ESMapping esMapping = field.getAnnotation(ESMapping.class);                properties.put("type", esMapping.value().getValue());            }            return properties;        }    }}

package com.rz.szwes.core;import lombok.extern.slf4j.Slf4j;import org.frameworkset.elasticsearch.boot.BBossESStarter;import org.frameworkset.elasticsearch.client.ClientInterface;import org.frameworkset.elasticsearch.client.ClientUtil;import org.springframework.beans.factory.annotation.Autowired;import java.util.*;/** * Elastic基本函数 * * @author sunziwen * @version 1.0 * @date 2019/12/13 9:56 **/@Slf4jpublic class ElasticBaseService<T> extends AbstractElasticBase<T> {    @Autowired    private BBossESStarter starter;    /**     * Xml创建索引     */    protected String createIndexByXml(String xmlName) {        ClientInterface restClient = starter.getConfigRestClient(xmlPath);        boolean existIndice = restClient.existIndice(this.indexName);        if (existIndice) {            restClient.dropIndice(indexName);        }        return restClient.createIndiceMapping(indexName, xmlName);    }    /**     * 索引是自动创建的     */    protected String createIndex() {        ClientInterface restClient = starter.getRestClient();        boolean existIndice = restClient.existIndice(this.indexName);        if (existIndice) {            restClient.dropIndice(indexName);        }        log.debug("创建索引:" + this.mapping);        return restClient.executeHttp(indexName, this.mapping, ClientUtil.HTTP_PUT);    }    /**     * 删除索引     */    protected String delIndex() {        return starter.getRestClient().dropIndice(this.indexName);    }    /**     * 添加文档     *     * @param t       实体类     * @param refresh 是否强制刷新     */    protected String addDocument(T t, Boolean refresh) {        return starter.getRestClient().addDocument(indexName, indexType, t, "refresh=" + refresh);    }    /**     * 添加文档     *     * @param ts      实体类集合     * @param refresh 是否强制刷新     */    protected String addDocuments(List<T> ts, Boolean refresh) {        return starter.getRestClient().addDocuments(indexName, indexType, ts, "refresh=" + refresh);    }    /**     * 页面-添加文档集合     *     * @param ts      实体类集合     * @param refresh 是否强制刷新     */    protected void addDocumentsOfPage(List<T> ts, Boolean refresh) {        this.delIndex();        this.createIndex();        int start = 0;        int rows = 100;        Integer size;        do {            List<T> list = pageDate(start, rows);            if (list.size() > 0) {                ///批量同步信息                starter.getRestClient().addDocuments(indexName, indexType, ts, "refresh=" + refresh);            }            size = list.size();            start += size;        } while (size > 0);    }    /**     * 使用分页添加文档必须重写此类文档     *     * @param start 起始     * @param rows  项数     * @return     */    protected List<T> pageDate(int start, int rows) {        return null;    }    /**     * 删除文档     *     * @param id      id     * @param refresh 是否强制刷新     * @return     */    protected String delDocument(String id, Boolean refresh) {        return starter.getRestClient().deleteDocument(indexName, indexType, id, "refresh=" + refresh);    }    /**     * 删除文档     *     * @param ids     id集合     * @param refresh 是否强制刷新     * @return     */    protected String delDocuments(String[] ids, Boolean refresh) {        return starter.getRestClient().deleteDocumentsWithrefreshOption(indexName, indexType, "refresh=" + refresh, ids);    }    /**     * Id获取文档     *     * @param id     * @return     */    protected T getDocument(String id, Class<T> clazz) {        return starter.getRestClient().getDocument(indexName, indexType, id, clazz);    }    /**     * ID更新文档     *     * @param t       实体     * @param refresh 是否强制刷新     * @return     */    protected String updateDocument(String id, T t, Boolean refresh) {        return starter.getRestClient().updateDocument(indexName, indexType, id, t, "refresh=" + refresh);    }}

xml写复杂Dsl:(如何写Dsl请参考bboss-elasticsearch文档,用法与mybatis标签相似)

<properties>    </properties>

框架集成后,以下是使用示例:

定义数据模型:

package com.rz.dto;import com.frameworkset.orm.annotation.ESId;import com.rz.szwes.annotations.ESDsl;import com.rz.szwes.annotations.ESMapping;import com.rz.szwes.annotations.ESMappingType;import lombok.Data;import java.util.List;/** * 对应elasticsearch服务器的数据模型 * * @author sunziwen * @version 1.0 * @date 2019/12/16 11:08 **/@ESDsl(value = "elasticsearch/zsInfo.xml", indexName = "zsInfo")@Datapublic class ElasticZsInfoDto {    @ESMapping(ESMappingType._byte)    private int least_hit;    private int is_must_zz;    private int zs_level;    private int cur_zs_ct;    private int least_score_yy;    private int least_score_yw;    private int area_id;    private String coll_name;    private String coll_code;    private long coll_pro_id;    private int is_must_wl;    private int cur_year;    private int is_two;    private long logo;    @ESId    private int id;    private String area;    private int college_id;    private String is_must_yy;    private int is_double;    private int least_score_zz;    private int least_score_wl;    private String grade;    private int is_nine;    private String pro_name;    private int least_score_sx;    private int relevanceSort;    private int pre_avg;    private String is_must_dl;    private String profession_code;    private int least_score_sw;    private String is_must_ls;    private int grade_zk;    private int least_score_wy;    private int is_must_hx;    private int profession_id;    private String is_grad;    private String is_must_yw;    private int is_must_sw;    private int least_score_ls;    private int least_score_dl;    private String zs_memo;    private String is_must_sx;    private String introduce;    private int is_must_wy;    private int grade_bk;    private String pre_name;    private int least_score_hx;    private String coll_domain;    private int pre_wch;    private List<String> courses;}

定义服务

package com.rz.service;import com.rz.dto.ElasticZsInfoDto;import com.rz.szwes.core.ElasticBaseService;/** * 招生索引操作服务 * * @author sunziwen * @version 1.0 * @date 2019/12/16 11:02 **/public class ElasticZsInfoService extends ElasticBaseService<ElasticZsInfoDto> {}

完毕。可以进行索引和文档的crud操作,需要在xml中定义复杂的检索操作。这里只介绍我的增强功能,大部分功能定义在老板,读者可以看到老板文档(作者认为他唯一的缺陷是不能通过物理注释实现自动索引,每次手动指定xml位置,手动写mapping是一件非常痛苦的事情,这里增强)。