经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Elasticsearch » 查看文章
关于注解式的分布式Elasticsearch的封装案例
来源:jb51  时间:2021/1/25 12:19:50  对本文有异议

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

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

依赖

  1. <dependency>
  2. <groupId>org.elasticsearch</groupId>
  3. <artifactId>elasticsearch</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.bbossgroups.plugins</groupId>
  7. <artifactId>bboss-elasticsearch-spring-boot-starter</artifactId>
  8. <version>5.9.5</version>
  9. <exclusions>
  10. <exclusion>
  11. <artifactId>slf4j-log4j12</artifactId>
  12. <groupId>org.slf4j</groupId>
  13. </exclusion>
  14. </exclusions>
  15. </dependency>
  16. <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
  17. <dependency>
  18. <groupId>org.projectlombok</groupId>
  19. <artifactId>lombok</artifactId>
  20. <version>1.18.6</version>
  21. <scope>provided</scope>
  22. </dependency>

配置:

  1. import com.rz.config.ElsConfig;
  2. import org.frameworkset.elasticsearch.boot.ElasticSearchBoot;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.boot.ApplicationArguments;
  5. import org.springframework.boot.ApplicationRunner;
  6. import org.springframework.core.annotation.Order;
  7. import org.springframework.stereotype.Component;
  8. import java.util.HashMap;
  9. import java.util.Map;
  10. /**
  11. * 启动时初始化bBoss
  12. *
  13. * @author sunziwen
  14. * @version 1.0
  15. * @date 2019/12/12 16:54
  16. **/
  17. @Component
  18. @Order(value = 1)
  19. public class StartElastic implements ApplicationRunner {
  20. @Autowired
  21. private ElsConfig config;
  22. @Override
  23. public void run(ApplicationArguments args) throws Exception {
  24. Map properties = new HashMap();
  25. properties.put("elasticsearch.rest.hostNames", config.getElsClusterNodes());
  26. ElasticSearchBoot.boot(properties);
  27. }
  28. }

注解和枚举:

  1. package com.rz.szwes.annotations;
  2. import java.lang.annotation.*;
  3. /**
  4. * 标识实体对应的索引信息
  5. *
  6. * @author sunziwen
  7. * 2019/12/13 10:14
  8. * @version 1.0
  9. **/
  10. @Target({ElementType.TYPE})
  11. @Retention(RetentionPolicy.RUNTIME)
  12. @Documented
  13. public @interface ESDsl {
  14. /**
  15. * xml的位置
  16. */
  17. String value();
  18. String indexName();
  19. /**
  20. * elasticsearch7.x版本已经删除该属性
  21. */
  22. String indexType() default "";
  23. }
  1. package com.rz.szwes.annotations;
  2. import java.lang.annotation.*;
  3. /**
  4. * 为字段指定映射类型
  5. *
  6. * @author sunziwen
  7. * 2019/12/14 10:06
  8. * @version 1.0
  9. **/
  10. @Target({ElementType.FIELD})
  11. @Retention(RetentionPolicy.RUNTIME)
  12. @Documented
  13. public @interface ESMapping {
  14. //映射类型
  15. ESMappingType value();
  16. //加权
  17. int boost() default 1;
  18. //分词标识analyzed、not_analyzed
  19. String index() default "analyzed";
  20. //分词器ik_max_word、standard
  21. String analyzer() default "ik_max_word";
  22. //String作为分组聚合字段的时候需要设置为true
  23. boolean fildData() default false;
  24. }
  1. package com.rz.szwes.annotations;
  2. /**
  3. * Es映射类型枚举(定义了大部分,有缺失请使用者补全)当前版本基于elasticsearch 6.8
  4. *
  5. * @author sunziwen
  6. * 2019/12/14 10:09
  7. * @version 1.0
  8. **/
  9. public enum ESMappingType {
  10. /**
  11. * 全文搜索。
  12. */
  13. text("text"),
  14. /**
  15. * keyword类型适用于索引结构化(排序、过滤、聚合),只能通过精确值搜索到。
  16. */
  17. keyword("keyword"),
  18. /
  19. /**
  20. * -128~127 在满足需求的情况下,尽可能选择范围小的数据类型。
  21. */
  22. _byte("byte"),
  23. /**
  24. * -32768~32767
  25. */
  26. _short("short"),
  27. /**
  28. * -2^31~2^31-1
  29. */
  30. _integer("integer"),
  31. /**
  32. * -2^63~2^63-1
  33. */
  34. _long("long"),
  35. /
  36. /**
  37. * 64位双精度IEEE 754浮点类型
  38. */
  39. _doule("doule"),
  40. /**
  41. * 32位单精度IEEE 754浮点类型
  42. */
  43. _float("float"),
  44. /**
  45. * 16位半精度IEEE 754浮点类型
  46. */
  47. half_float("half_float"),
  48. /**
  49. * 缩放类型的的浮点数
  50. */
  51. scaled_float("scaled_float"),
  52. /
  53. /**
  54. * 时间类型
  55. */
  56. date("date"),
  57. _boolean("boolean"),
  58. /**
  59. * 范围类型
  60. */
  61. range("range"),
  62. /**
  63. * 嵌套类型
  64. */
  65. nested("nested"),
  66. /**
  67. * 地理坐标
  68. */
  69. geo_point("geo_point"),
  70. /**
  71. * 地理地图
  72. */
  73. geo_shape("geo_shape"),
  74. /**
  75. * 二进制类型
  76. */
  77. binary("binary"),
  78. /**
  79. * ip 192.168.1.2
  80. */
  81. ip("ip");
  82. private String value;
  83. ESMappingType(String value) {
  84. this.value = value;
  85. }
  86. public String getValue() {
  87. return value;
  88. }
  89. }

工具类:对HashMap进行了增强

  1. package com.rz.szwes.util;
  2. import java.util.HashMap;
  3. import java.util.function.Supplier;
  4. /**
  5. * 原始HashMap不支持Lambda表达式,特此包装一个
  6. *
  7. * @author sunziwen
  8. * @version 1.0
  9. * @date 2019/12/13 11:09
  10. **/
  11. public class LambdaHashMap<K, V> extends HashMap<K, V> {
  12. public static <K, V> LambdaHashMap<K, V> builder() {
  13. return new LambdaHashMap<>();
  14. }
  15. public LambdaHashMap<K, V> put(K key, Supplier<V> supplier) {
  16. super.put(key, supplier.get());
  17. //流式
  18. return this;
  19. }
  20. }

核心类两个:

  1. package com.rz.szwes.core;
  2. import cn.hutool.core.util.ClassUtil;
  3. import com.alibaba.fastjson.JSON;
  4. import com.frameworkset.orm.annotation.ESId;
  5. import com.rz.szwes.annotations.ESDsl;
  6. import com.rz.szwes.annotations.ESMapping;
  7. import com.rz.szwes.util.LambdaHashMap;
  8. import org.springframework.util.StringUtils;
  9. import java.lang.reflect.Field;
  10. import java.lang.reflect.ParameterizedType;
  11. import java.time.LocalDate;
  12. import java.time.LocalDateTime;
  13. import java.time.LocalTime;
  14. import java.util.ArrayList;
  15. import java.util.Arrays;
  16. import java.util.Collections;
  17. import java.util.Date;
  18. /**
  19. * 抽象类解析泛型
  20. *
  21. * @author sunziwen
  22. * 2019/12/14 16:04
  23. * @version 1.0
  24. **/
  25. public abstract class AbstractElasticBase<T> {
  26. { //初始化解析
  27. ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
  28. // 获取第一个类型参数的真实类型
  29. Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];
  30. parseMapping(clazz);
  31. }
  32. /**
  33. * 索引名称
  34. */
  35. protected String indexName;
  36. /**
  37. * 索引类型
  38. */
  39. protected String indexType;
  40. /**
  41. * es写dsl的文件路径
  42. */
  43. protected String xmlPath;
  44. /**
  45. * 索引映射
  46. */
  47. protected String mapping;
  48. //将Class解析成映射JSONString
  49. private void parseMapping(Class<T> clazz) {
  50. if (clazz.isAnnotationPresent(ESDsl.class)) {
  51. ESDsl esDsl = clazz.getAnnotation(ESDsl.class);
  52. this.xmlPath = esDsl.value();
  53. this.indexName = esDsl.indexName();
  54. //如果类型为空,则采用索引名作为其类型
  55. this.indexType = StringUtils.isEmpty(esDsl.indexType()) ? esDsl.indexName() : esDsl.indexType();
  56. } else {
  57. throw new RuntimeException(clazz.getName() + "缺失注解[@ESDsl]");
  58. }
  59. //构建索引映射
  60. LambdaHashMap<Object, Object> put = LambdaHashMap.builder()
  61. .put("mappings", () -> LambdaHashMap.builder()
  62. .put(indexType, () -> LambdaHashMap.builder()
  63. .put("properties", () -> {
  64. Field[] fields = clazz.getDeclaredFields();
  65. LambdaHashMap<Object, Object> builder = LambdaHashMap.builder();
  66. for (Field field : fields) {
  67. builder.put(field.getName(), () -> toEsjson(field));
  68. }
  69. return builder;
  70. })))
  71. ;
  72. this.mapping = JSON.toJSONString(put);
  73. }
  74. private LambdaHashMap<Object, Object> toEsjson(Field field) {
  75. //基本数据类型
  76. if (ClassUtil.isSimpleTypeOrArray(field.getType())) {
  77. //对字符串做大小限制、分词设置
  78. if (new ArrayList<Class>(Collections.singletonList(String.class)).contains(field.getType())) {
  79. LambdaHashMap<Object, Object> put = LambdaHashMap.builder()
  80. .put("type", () -> "text")
  81. .put("fields", () -> LambdaHashMap.builder()
  82. .put("keyword", () -> LambdaHashMap.builder()
  83. .put("type", () -> "keyword")
  84. .put("ignore_above", () -> 256)));
  85. if (field.isAnnotationPresent(ESMapping.class)) {
  86. ESMapping esMapping = field.getAnnotation(ESMapping.class);
  87. //设置聚合分组
  88. if (esMapping.fildData()) {
  89. put.put("fildData", () -> true);
  90. }
  91. //设置加权
  92. if (esMapping.boost() != 1) {
  93. put.put("boost", esMapping::boost);
  94. }
  95. //设置是否进行分词
  96. if (!"analyzed".equals(esMapping.index())) {
  97. put.put("analyzed", esMapping::analyzer);
  98. }
  99. //分词器
  100. put.put("analyzer", esMapping::analyzer);
  101. }
  102. return put;
  103. }
  104. //设置默认类型
  105. return LambdaHashMap.builder().put("type", () -> {
  106. if (field.isAnnotationPresent(ESMapping.class)) {
  107. ESMapping esMapping = field.getAnnotation(ESMapping.class);
  108. return esMapping.value().getValue();
  109. }
  110. 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())) {
  111. return "long";
  112. } else if (new ArrayList<Class>(Arrays.asList(double.class, Double.class, float.class, Float.class)).contains(field.getType())) {
  113. return "double";
  114. } else if (new ArrayList<Class>(Arrays.asList(Date.class, java.sql.Date.class, LocalDate.class, LocalDateTime.class, LocalTime.class)).contains(field.getType())) {
  115. return "date";
  116. } else if (new ArrayList<Class>(Arrays.asList(boolean.class, Boolean.class)).contains(field.getType())) {
  117. return "boolean";
  118. }
  119. return "text";
  120. });
  121. } else {
  122. //设置对象类型
  123. LambdaHashMap<Object, Object> properties = LambdaHashMap.builder()
  124. .put("properties", () -> {
  125. Field[] fields = field.getType().getDeclaredFields();
  126. LambdaHashMap<Object, Object> builder = LambdaHashMap.builder();
  127. for (Field field01 : fields) {
  128. builder.put(field01.getName(), toEsjson(field01));
  129. }
  130. return builder;
  131. });
  132. if (field.isAnnotationPresent(ESMapping.class)) {
  133. ESMapping esMapping = field.getAnnotation(ESMapping.class);
  134. properties.put("type", esMapping.value().getValue());
  135. }
  136. return properties;
  137. }
  138. }
  139. }
  1. package com.rz.szwes.core;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.frameworkset.elasticsearch.boot.BBossESStarter;
  4. import org.frameworkset.elasticsearch.client.ClientInterface;
  5. import org.frameworkset.elasticsearch.client.ClientUtil;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import java.util.*;
  8. /**
  9. * Elastic基础函数
  10. *
  11. * @author sunziwen
  12. * @version 1.0
  13. * @date 2019/12/13 9:56
  14. **/
  15. @Slf4j
  16. public class ElasticBaseService<T> extends AbstractElasticBase<T> {
  17. @Autowired
  18. private BBossESStarter starter;
  19. /**
  20. * Xml创建索引
  21. */
  22. protected String createIndexByXml(String xmlName) {
  23. ClientInterface restClient = starter.getConfigRestClient(xmlPath);
  24. boolean existIndice = restClient.existIndice(this.indexName);
  25. if (existIndice) {
  26. restClient.dropIndice(indexName);
  27. }
  28. return restClient.createIndiceMapping(indexName, xmlName);
  29. }
  30. /**
  31. * 自动创建索引
  32. */
  33. protected String createIndex() {
  34. ClientInterface restClient = starter.getRestClient();
  35. boolean existIndice = restClient.existIndice(this.indexName);
  36. if (existIndice) {
  37. restClient.dropIndice(indexName);
  38. }
  39. log.debug("创建索引:" + this.mapping);
  40. return restClient.executeHttp(indexName, this.mapping, ClientUtil.HTTP_PUT);
  41. }
  42. /**
  43. * 删除索引
  44. */
  45. protected String delIndex() {
  46. return starter.getRestClient().dropIndice(this.indexName);
  47. }
  48. /**
  49. * 添加文档
  50. *
  51. * @param t 实体类
  52. * @param refresh 是否强制刷新
  53. */
  54. protected String addDocument(T t, Boolean refresh) {
  55. return starter.getRestClient().addDocument(indexName, indexType, t, "refresh=" + refresh);
  56. }
  57. /**
  58. * 添加文档
  59. *
  60. * @param ts 实体类集合
  61. * @param refresh 是否强制刷新
  62. */
  63. protected String addDocuments(List<T> ts, Boolean refresh) {
  64. return starter.getRestClient().addDocuments(indexName, indexType, ts, "refresh=" + refresh);
  65. }
  66. /**
  67. * 分页-添加文档集合
  68. *
  69. * @param ts 实体类集合
  70. * @param refresh 是否强制刷新
  71. */
  72. protected void addDocumentsOfPage(List<T> ts, Boolean refresh) {
  73. this.delIndex();
  74. this.createIndex();
  75. int start = 0;
  76. int rows = 100;
  77. Integer size;
  78. do {
  79. List<T> list = pageDate(start, rows);
  80. if (list.size() > 0) {
  81. //批量同步信息
  82. starter.getRestClient().addDocuments(indexName, indexType, ts, "refresh=" + refresh);
  83. }
  84. size = list.size();
  85. start += size;
  86. } while (size > 0);
  87. }
  88. /**
  89. * 使用分页添加文档必须重写该类
  90. *
  91. * @param start 起始
  92. * @param rows 项数
  93. * @return
  94. */
  95. protected List<T> pageDate(int start, int rows) {
  96. return null;
  97. }
  98. /**
  99. * 删除文档
  100. *
  101. * @param id id
  102. * @param refresh 是否强制刷新
  103. * @return
  104. */
  105. protected String delDocument(String id, Boolean refresh) {
  106. return starter.getRestClient().deleteDocument(indexName, indexType, id, "refresh=" + refresh);
  107. }
  108. /**
  109. * 删除文档
  110. *
  111. * @param ids id集合
  112. * @param refresh 是否强制刷新
  113. * @return
  114. */
  115. protected String delDocuments(String[] ids, Boolean refresh) {
  116. return starter.getRestClient().deleteDocumentsWithrefreshOption(indexName, indexType, "refresh=" + refresh, ids);
  117. }
  118. /**
  119. * id获取文档
  120. *
  121. * @param id
  122. * @return
  123. */
  124. protected T getDocument(String id, Class<T> clazz) {
  125. return starter.getRestClient().getDocument(indexName, indexType, id, clazz);
  126. }
  127. /**
  128. * id更新文档
  129. *
  130. * @param t 实体
  131. * @param refresh 是否强制刷新
  132. * @return
  133. */
  134. protected String updateDocument(String id, T t, Boolean refresh) {
  135. return starter.getRestClient().updateDocument(indexName, indexType, id, t, "refresh=" + refresh);
  136. }
  137. }

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

  1. <properties>
  2. </properties>

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

定义数据模型:

  1. package com.rz.dto;
  2. import com.frameworkset.orm.annotation.ESId;
  3. import com.rz.szwes.annotations.ESDsl;
  4. import com.rz.szwes.annotations.ESMapping;
  5. import com.rz.szwes.annotations.ESMappingType;
  6. import lombok.Data;
  7. import java.util.List;
  8. /**
  9. * 对应elasticsearch服务器的数据模型
  10. *
  11. * @author sunziwen
  12. * @version 1.0
  13. * @date 2019/12/16 11:08
  14. **/
  15. @ESDsl(value = "elasticsearch/zsInfo.xml", indexName = "zsInfo")
  16. @Data
  17. public class ElasticZsInfoDto {
  18. @ESMapping(ESMappingType._byte)
  19. private int least_hit;
  20. private int is_must_zz;
  21. private int zs_level;
  22. private int cur_zs_ct;
  23. private int least_score_yy;
  24. private int least_score_yw;
  25. private int area_id;
  26. private String coll_name;
  27. private String coll_code;
  28. private long coll_pro_id;
  29. private int is_must_wl;
  30. private int cur_year;
  31. private int is_two;
  32. private long logo;
  33. @ESId
  34. private int id;
  35. private String area;
  36. private int college_id;
  37. private String is_must_yy;
  38. private int is_double;
  39. private int least_score_zz;
  40. private int least_score_wl;
  41. private String grade;
  42. private int is_nine;
  43. private String pro_name;
  44. private int least_score_sx;
  45. private int relevanceSort;
  46. private int pre_avg;
  47. private String is_must_dl;
  48. private String profession_code;
  49. private int least_score_sw;
  50. private String is_must_ls;
  51. private int grade_zk;
  52. private int least_score_wy;
  53. private int is_must_hx;
  54. private int profession_id;
  55. private String is_grad;
  56. private String is_must_yw;
  57. private int is_must_sw;
  58. private int least_score_ls;
  59. private int least_score_dl;
  60. private String zs_memo;
  61. private String is_must_sx;
  62. private String introduce;
  63. private int is_must_wy;
  64. private int grade_bk;
  65. private String pre_name;
  66. private int least_score_hx;
  67. private String coll_domain;
  68. private int pre_wch;
  69. private List<String> courses;
  70. }

定义服务

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

完毕。

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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持w3xue。如有错误或未考虑完全的地方,望不吝赐教。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号