原生的Rest Level Client不好用,构建检索等很多重复操作。
对bboss-elasticsearch进行了部分增强:通过注解配合实体类进行自动构建索引和自动刷入文档,复杂的业务检索需要自己在xml中写Dsl。用法与mybatis-plus如出一辙。
依赖
- <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;
-
- /**
- * 启动时初始化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<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
- **/
- @Slf4j
- public 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);
- }
- }
写复杂Dsl的xml:(如何写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")
- @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<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中定义了。这里只介绍了我增强的功能,大部分功能都在bBoss中定义好了,读者可以去看bBoss文档(笔者认为的他的唯一缺陷是不能通过实体配合注解实现自动索引,还要每次手动指定xml位置,手动写mapping是很痛苦的事情,特此进行了增强)。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持w3xue。如有错误或未考虑完全的地方,望不吝赐教。