关于注解式的分布式Elasticsearch的封装案例

网友投稿 290 2022-11-05


关于注解式的分布式Elasticsearch的封装案例

原生的Rest Level Client不好用,构建检索等很多重复操作。

对bboss-elasticsearch进行了部分增强:通过注解配合实体类进行自动构建索引和自动刷入文档,复杂的业务检索需要自己在xml中写Dsl。用法与mybatis-plus如出一辙。

依赖

org.elasticsearch

elasticsearch

com.bbossgroups.plugins

bboss-elasticsearch-spring-boot-starter

5.9.5

slf4j-log4j12

org.slf4j

http://org.projectlombok

lombok

1.18.6

provided

配置:

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;

/**

* 启动时初始化bBoss

*

* @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)

@Documented

public @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)

@Documented

public @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位双精度IEEE 754浮点类型

*/

_doule("doule"),

/**

* 32位单精度IEEE 754浮点类型

*/

_float("float"),

/**

* 16位半精度IEEE 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;

/**

* 原始HashMap不支持Lambda表达式,特此包装一个

*

* @author sunziwen

* @version 1.0

* @date 2019/12/13 11:09

**/

public class LambdaHashMap extends HashMap {

public static LambdaHashMap builder() {

return new LambdaHashMap<>();

}

public LambdaHashMap put(K key, Supplier 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 {

{ //初始化解析

ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();

// 获取第一个类型参数的真实类型

Class clazz = (Class) 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 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 put = LambdaHashMap.builder()

.put("mappings", () -> LambdaHashMap.builder()

.put(indexType, () -> LambdaHashMap.builder()

.put("properties", () -> {

Field[] fields = clazz.getDeclaredFields();

LambdaHashMap builder = LambdaHashMap.builder();

for (Field field : fields) {

builder.put(field.getName(), () -> toEsjson(field));

}

return builder;

})))

;

this.mapping = JSON.toJSONString(put);

}

private LambdaHashMap toEsjson(Field field) {

//基本数据类型

if (ClassUtil.isSimpleTypeOrArray(field.getType())) {

//对字符串做大小限制、分词设置

if (new ArrayList(Collections.singletonList(String.class)).contains(field.getType())) {

LambdaHashMap 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(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(Arrays.asList(double.class, Double.class, float.class, Float.class)).contains(field.getType())) {

return "double";

} else if (new ArrayList(Arrays.asList(Date.class, java.sql.Date.class, LocalDate.class, LocalDateTime.class, LocalTime.class)).contains(field.getType())) {

return "date";

} else if (new ArrayList(Arrays.asList(boolean.class, Boolean.class)).contains(field.getType())) {

return "boolean";

}

return "text";

});

} else {

//设置对象类型

LambdaHashMap properties = LambdaHashMap.builder()

.put("properties", () -> {

Field[] fields = field.getType().getDeclaredFields();

LambdaHashMap 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

**/

@Slf4j

public class ElasticBaseService extends AbstractElasticBase {

@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 ts, Boolean refresh) {

return starter.getRestClient().addDocuments(indexName, indexType, ts, "refresh=" + refresh);

}

/**

* 分页-添加文档集合

*

* @param ts 实体类集合

* @param refresh 是否强制刷新

*/

protected void addDocumentsOfPage(List ts, Boolean refresh) {

this.delIndex();

this.createIndex();

int start = 0;

int rows = 100;

Integer size;

do {

List 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 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 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);

}

}

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

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

定义数据模型:

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")

@Data

public 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 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 {

}

完毕。

已经可以进行索引和文档的crud操作了,至于复杂的检索操作就需要在xml中定义了。这里只介绍了我增强的功能,大部分功能都在bBoss中定义好了,读者可以去看bBoss文档(笔者认为的他的唯一缺陷是不能通过实体配合注解实现自动索引,还要每次手动指定xml位置,手动写mapping是很痛苦的事情,特此进行了增强)。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:保险查询API(保险查询个人)
下一篇:【BP数据预测】基于matlab灰狼算法优化BP神经网络数据预测(多输入多输出)【含Matlab源码 2026期】
相关文章

 发表评论

暂时没有评论,来抢沙发吧~