• [技术干货] Arthas中JVM相关命令详解
    Arthas是一款由Alibaba开源的Java诊断工具,它能够在不重启JVM的情况下,实时查看和修改JVM的运行参数,从而帮助开发者诊断和解决生产环境中的性能问题。以下是Arthas中JVM相关命令的详细解析:1. dashboard功能:查看当前系统的实时数据面板,包括CPU、内存、线程、GC等关键指标。参数:-i:刷新实时数据的时间间隔(ms),默认5000ms。使用示例:dashboard -i 10000:间隔10秒刷新一次大盘信息。注意:此命令是一个可视化的控制台,显示CPU、内存等使用指标以及线程的状态,是初步查看系统性能问题的常用命令。2. thread功能:查看当前JVM的线程堆栈信息。参数:id:指定线程ID,查看该线程的运行堆栈。-n <N>:指定最忙的前N个线程并打印堆栈。-b:找出当前阻塞其他线程的线程。-i <value>:指定CPU使用率统计的采样间隔,单位为毫秒,默认值为200。--all:显示所有匹配的线程。使用示例:thread:显示当前线程。thread -n 3:显示最忙的前3个线程并打印堆栈。thread --state RUNNABLE:查看运行时状态的线程。thread -b:打印当前阻塞其他线程的线程。3. jvm功能:查看当前JVM的信息,包括线程数、文件描述符数、内存信息等。使用示例:jvm:查看JVM的基本信息。数据说明:THREAD相关:包括活跃的线程数、守护线程数、曾经活着的最大线程数等。文件描述符相关:包括最大可以打开的文件描述符数、当前打开的文件描述符数等。内存信息:包括堆大小、非堆大小、代码缓存、元空间等。4. memory功能:查看JVM的内存信息,包括堆内存和非堆内存的使用情况。使用示例:memory:查看JVM的内存信息。5. vmoption功能:查看和修改JVM里诊断相关的参数。参数:查看所有option:vmoption查看指定option:vmoption <optionName>更新指定option(如果可写):vmoption <optionName> <newValue>注意:不是所有的JVM启动参数都可以在运行时通过vmoption命令进行修改。一些关键参数(如-Xms和-Xmx)在JVM启动时设置,并且在运行期间不能修改。6. heapdump功能:dump Java堆内存,类似于jmap命令的heap dump功能。参数:--live:可选参数,表示只打印有活跃引用的对象,丢弃进行垃圾回收的对象。[文件路径/文件名.hprof]:指定dump文件的保存路径和名称。使用示例:heapdump --live /root/test/study-06-01.hprof:dump当前JVM的堆内存到指定文件,只包含活跃对象。7. 其他命令getstatic:查看类的静态属性。不推荐使用,推荐使用ognl命令。ognl:执行OGNL表达式,用于更复杂的对象属性访问和操作。mbean:查看MBean的信息。MBean是一种规范的JavaBean,用于在JMX中注册和管理。sysenv:查看JVM的环境变量。sysprop:查看和修改JVM的系统属性。vmtool:从JVM里查询对象,执行forceGC等操作。总结Arthas提供的JVM相关命令覆盖了线程、内存、JVM信息、堆dump等多个方面,为Java应用的性能诊断和调优提供了强大的工具。开发者可以根据实际需要选择合适的命令来监控和分析JVM的性能问题。
  • [技术干货] 探索高效能搜索引擎新境界:Easy-Es
    探索高效能搜索引擎新境界:Easy-Es 项目介绍 Easy-Es,一个致力于简化Elasticsearch操作的开源框架,它提供了一种全自动智能索引托管模式,让开发者不必再为繁琐的索引管理而烦恼。与传统的SpringData-Elasticsearch相比,Easy-Es在功能丰富度、易用性和性能上都有着显著优势。它采用了Elasticsearch官方的RestHighLevelClient,确保原始性能的同时,带来了更高的灵活性。  项目技术分析 Easy-Es的独特之处在于它的全自动索引托管,这是全球开源领域的创新之举。它智能地处理索引的创建、更新以及数据迁移,整个过程无需停机,用户无感知。此外,框架还能智能推断字段类型,避免了因不当使用导致的问题。其语法设计借鉴了Mybatis-Plus,降低了学习曲线,使得即便是对Elasticsearch不甚熟悉的开发者也能快速上手。  项目及技术应用场景 无论是大型企业还是初创公司,只要有大量数据搜索的需求,Easy-Es都能大显身手。例如,在电子商务网站中,它可以实现高效的商品搜索;在新闻门户中,用于实时热点新闻的检索;在社交媒体平台,支持用户动态的快速定位。其丰富的功能涵盖了MySQL的大部分特性,并针对Elasticsearch特有的如分词、权重、高亮、地理位置和IP地址查询提供了支持。  项目特点 全自动索引托管 - 解放开发者,专注于业务逻辑。 智能字段类型推断 - 减少错误,提升效率。 屏蔽语言差异 - MySQL语法友好,易于理解。 代码量极低 - 相较于直接使用RestHighLevelClient,代码量大大减少。 零魔法值 - 字段名直接从实体获取,清晰无混淆。 低学习成本 - 类似Mybatis-Plus的语法,易于迁移。 强大的功能集 - 支持多种高级查询和Elasticsearch特性。 语法优雅 - Lambda风格链式编程,增强代码可读性。 安全可靠 - 完全的墨菲安全扫描,代码覆盖率达到95%以上。 完善文档 - 中英文双语文档,助您迅速上手。 社区支持与贡献 Easy-Es有一个活跃的技术交流群,有专门的健身教练为您解答技术问题和分享健康计划。此外,项目在GitHub和Gitee上的官方主页包含了详细的教程、示例和最新版本信息。您的星标、关注和fork都是对我们工作的极大鼓励!  立即加入Easy-Es的世界,开启高效、便捷的Elasticsearch开发之旅吧!让我们一起打造更好的开源生态,为中国的开发者社区贡献力量。 ———————————————— 原文链接:https://blog.csdn.net/gitblog_00045/article/details/138841825 
  • [技术干货] springboot整合easy-es实现数据的增删改查
    背景 目前公司的一个老项目,查询贼慢,需要想办法提升一下速度,于是就想到了ES,现在尝试一下将ES整合到项目中来提升检索效率。  ES是基于倒排索引实现的,倒排索引中一个表相当于一个索引,表中的每条记录都是一个文档(JSON数据),系统会先对字段数据进行分词,然后给词条建立索引,并映射到文档id。在查询的时候根据输入进行分词,然后根据词条走索引查询文档id,再根据文档id查询文档并放入结果集,最后将结果集返回。  一般来说,ES算是难度较高的一个技术栈,需要中高级才能熟练驾驭,新手入门比较难,因而我选中了对新手更加友好的easy-es,其在ES的基础上做了封装,使得使用起来和MybatisPlus很像,简单上手。 开始使用easy-es之前,建议先看一下避坑指南 Elastic Search下载 ES官网 按照官方推荐下载7.x的ES,我下载了和官方demo一样的7.14.0版本。 输入7.14.0搜索该版本并下载 下载完成之后解压,并去到bin目录,双击elasticsearch.bat文件启动elasticsearch。  Springboot整合ES 打开Springboot项目(或创建一个Springboot项目),先全局搜索elastic,看看项目是否已经引入过ES,如果有,需要去掉或者更改版本为7.14.0。印象中不同版本的Springboot默认引入的一定版本的ES。 在POM文件引入依赖         <!-- 引入easy-es最新版本的依赖-->         <dependency>             <groupId>org.dromara.easy-es</groupId>             <artifactId>easy-es-boot-starter</artifactId>             <!--这里Latest Version是指最新版本的依赖,比如2.0.0,可以通过下面的图片获取-->             <version>2.0.0-beta4</version>         </dependency>          <!-- 排除springboot中内置的es依赖,以防和easy-es中的依赖冲突-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>             <exclusions>                 <exclusion>                     <groupId>org.elasticsearch.client</groupId>                     <artifactId>elasticsearch-rest-high-level-client</artifactId>                 </exclusion>                 <exclusion>                     <groupId>org.elasticsearch</groupId>                     <artifactId>elasticsearch</artifactId>                 </exclusion>             </exclusions>         </dependency>         <dependency>             <groupId>org.elasticsearch.client</groupId>             <artifactId>elasticsearch-rest-high-level-client</artifactId>             <version>7.14.0</version>         </dependency>         <dependency>             <groupId>org.elasticsearch</groupId>             <artifactId>elasticsearch</artifactId>             <version>7.14.0</version>         </dependency>  YAML文件增加ES配置(更多配置可以在官网看) easy-es:   enable: true #默认为true,若为false则认为不启用本框架   address: 127.0.0.1:9200 # es的连接地址,必须含端口 若为集群,则可以用逗号隔开 例如:127.0.0.1:9200,127.0.0.2:9200 # username: elastic #若无 则可省略此行配置。因为刚下载的ES默认不用账号密码登录,所以注掉 #  password: WG7WVmuNMtM4GwNYkyWH #若无 则可省略此行配置 在启动类设置ES的mapper包扫描路径(一般应该设置成带*的通配格式,方便多模块项目的扫描。)同时注意,该mapper的包和mybatisplus的包不能是同一个,不然框架区分不开。  @EsMapperScan("org.jeecg.modules.test.esmapper") @SpringBootApplication public class JeecgSystemApplication extends SpringBootServletInitializer {      @Override     protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {         return application.sources(JeecgSystemApplication.class);     }      public static void main(String[] args) throws UnknownHostException {         ConfigurableApplicationContext application = SpringApplication.run(JeecgSystemApplication.class, args);     } } 创建实体类(加上@IndexName注解) package org.jeecg.modules.message.entity;  import org.dromara.easyes.annotation.IndexField; import org.dromara.easyes.annotation.IndexName; import org.dromara.easyes.annotation.rely.Analyzer; import org.dromara.easyes.annotation.rely.FieldType; import org.jeecg.common.aspect.annotation.Dict; import org.jeecg.common.system.base.entity.JeecgEntity; import org.jeecgframework.poi.excel.annotation.Excel; import org.springframework.format.annotation.DateTimeFormat;  import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat;  import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors;  @Data @IndexName public class SysMessage{     /** ID */     @TableId(type = IdType.ASSIGN_ID)     private java.lang.String id;     /**推送内容*/     private java.lang.String esContent;     /**推送所需参数Json格式*/     private java.lang.String esParam;     /**接收人*/     private java.lang.String esReceiver;     /**推送失败原因*/     private java.lang.String esResult;     /**发送次数*/     private java.lang.Integer esSendNum;     /**推送状态 0未推送 1推送成功 2推送失败*/     private java.lang.String esSendStatus;     /**推送时间*/     @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")     @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")     private java.util.Date esSendTime;     /**消息标题*/     private java.lang.String esTitle;     /**推送方式:1短信 2邮件 3微信*/     private java.lang.String esType;     /**备注*/     private java.lang.String remark;         /** 创建人 */     private java.lang.String createBy;     /** 创建时间 */     @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")     private java.util.Date createTime;     /** 更新人 */     private java.lang.String updateBy;     /** 更新时间 */     @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")     private java.util.Date updateTime; } 创建mapper类 package org.jeecg.modules.test.esmapper;  import org.dromara.easyes.core.core.BaseEsMapper; import org.jeecg.modules.message.entity.SysMessage; public interface DocumentMapper extends BaseEsMapper<SysMessage> {  } 编写测试类     @Resource     private DocumentMapper documentMapper;      @GetMapping("/createIndex")     @ApiOperation("创建索引")     public Object createIndex(String a){         Boolean index = documentMapper.createIndex();         System.out.println(index);         return index;     }      @GetMapping("/createDoc")     @ApiOperation("创建文档")     public Object createDoc(String a,String b){         SysMessage sysMessage=new SysMessage();         sysMessage.setEsContent(a);         sysMessage.setEsTitle(b);         sysMessage.setEsReceiver("系统管理员");         sysMessage.setEsSendNum(10);         Integer insert = documentMapper.insert(sysMessage);         return insert;     }     @GetMapping("/updateDoc")     @ApiOperation("updateDoc")     public Object updateDoc(String a){         LambdaEsUpdateWrapper<SysMessage> wrapper=new LambdaEsUpdateWrapper<>();         wrapper.eq(SysMessage::getEsContent,a)                 .set(SysMessage::getEsContent,"更改后的标题");         Integer update = documentMapper.update(null, wrapper);         return update;     }     @GetMapping("/getDoc")     @ApiOperation("查询文档")     public Object getDoc(String a){         List<SysMessage> list = EsWrappers.lambdaChainQuery(documentMapper).like(SysMessage::getEsContent, a).list();         return list;     }  浏览器安装一个ES可视化插件。我安装的是es-client 添加连接 选中创建的连接,目前还没有索引。  测试 启动Springboot项目,调用createIndex接口,创建索引 。然后回到浏览器插件,点击刷新,可以看到创建了一个索引。 调用createDoc接口,创建一个文档记录 点击左侧的数据展示选项,右上角选中创建的索引,点击刷新,可以看到多了一条记录。 调用getDoc接口,查询记录,成功查出。 更新的语法和MybatisPlus的wrapper差不多。先用查询条件eq,in等去筛选要更新的记录,然后用set去设置新的值,然后调用update方法即可。(如下,通过“测试内容”找到记录,并将其的标题改成新的内容) 在浏览器刷新,可以看到数据更新了。 删除的语法比更新还简单,也是创建一个esupdatewrapper,用eq、in等筛选,然后调用delete方法就可以了,就不演示了。 总结 Springboot整合ES最大可能遇到的问题就是ES版本的问题,也就是依赖冲突。如果依赖冲突,在项目启动的时候会有一个ERROR日志提醒,看到了就想办法去掉原来带着的ES依赖或者更改依赖版本为7.14.0 关于ES的数据更新,就要去了解ES同步数据库相关的知识了。 想要了解easy-es的更多特性,建议去看easy-es的官网文档。 ————————————————            原文链接:https://blog.csdn.net/weixin_43975276/article/details/134670744 
  • [技术干货] 关于这款开源的ES的ORM框架-Easy-Es适合初学者入手不
    前言 最近笔者为了捡回以前自学的ES知识,准备重新对ES的一些基础使用做个大致学习总结。然后在摸鱼逛开源社区时无意中发现了一款不错的ElasticSearch插件-Easy-ES,可称之为“ES界的MyBatis-Plus”。联想到之前每次用RestHighLevelClient写一些DSL操作时都很麻烦(复杂点的搜索代码量确实不少),加之用过MyBatisPlus,深感其对于简化开发、提高效率确实有一套,不知道这个Easy-ES能高效到什么水平,因此抱着学习的心态结合其文档一探究竟。  Easy-ES介绍 Easy-Es(简称EE)是一款基于ElasticSearch(简称Es)官方提供的RestHighLevelClient打造的ORM开发框架,在 RestHighLevelClient 的基础上,只做增强不做改变,为简化开发、提高效率而生,属于由国内开发者打造并完全开源的ElasticSearch-ORM框架!  因为它采用和Mybatis-Plus一致的语法设计,一定程度上能够显著降低ElasticSearch搜索引擎使用门槛,和额外学习成本,并大幅减少开发者工作量,帮助企业降本提效。如果有用过Mybatis-Plus(简称MP),那么基本可以零学习成本直接上手EE,EE是MP的Es平替版,在有些方面甚至比MP更简单,同时也融入了更多Es独有的功能,助力咱们快速实现各种场景的开发.  优势点 全自动索引托管: 全球开源首创的索引托管模式,开发者无需关心索引的创建更新及数据迁移等繁琐步骤,索引全生命周期皆可托管给框架,由框架自动完成,过程零停机,用户无感知,彻底解放开发者  智能字段类型推断: 根据索引类型和当前查询类型上下文综合智能判断当前查询是否需要拼接.keyword后缀,减少小白误用的可能  屏蔽语言差异: 开发者只需要会MySQL语法即可使用Es,真正做到一通百通,无需学习枯燥易忘的Es语法,Es使用相对MySQL较低频,学了长期不用也会忘,没必要浪费这时间,开发就应该专注于业务  代码量极少: 与直接使用RestHighLevelClient相比,相同的查询平均可以节省3-5倍左右的代码量  零魔法值: 字段名称直接从实体中获取,无需输入字段名称字符串这种魔法值,提高代码可读性,杜绝因字段名称修改而代码漏改带来的Bug  零额外学习成本: 开发者只要会国内最受欢迎的Mybatis-Plus语法,即可无缝迁移至EE,EE采用和前者相同的语法,消除使用者额外学习成本,直接上手,爽  降低开发者门槛: Es通常需要中高级开发者才能驾驭,但通过接入EE,即便是只了解ES基础的初学者也可以轻松驾驭ES完成绝大多数需求的开发,可以提高人员利用率,降低企业成本  主要特点 无侵入:只做增强不做改变,引入它不会对现有工程产生影响  损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作  强大的 CRUD 操作:内置通用 Mapper,仅仅通过少量配置即可实现大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求  支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错段  支持主键自动生成:支持2 种主键策略,可自由配置,完美解决主键问题  支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作  支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )  内置分页插件:基于RestHighLevelClient 物理分页,开发者无需关心具体操作,且无需额外配置插件,写分页等同于普通 List 查询,且保持和PageHelper插件同样的分页返回字段,无需担心命名影响  ES功能全覆盖:ES中支持的功能通过EE都可以轻松实现  支持ES高阶语法:支持高亮搜索,分词查询,权重查询,Geo地理位置查询,IP查询,聚合查询等高阶语法  良好的拓展性:底层仍使用RestHighLevelClient,可保持其拓展性,开发者在使用EE的同时,仍可使用RestHighLevelClient的功能  ……  与Spring Data的功能对比 由于ES本身的高复杂性和高门槛,以及相比MySQL更少的用户群体,这块领域高投入,低回报,因此像ES这类的ORM框架并不多,截止目前除了Springdata-Es几乎没有竞对,两者在使用体感上可以类比Mybait-Plus与SpringData-JPA,由于双方底层都是ES官方套件,所以对比ES官方套件本身就支持的原生查询功能毫无意义,于是笔者根据Easy-ES汇总的功能对比如下:  Easy-ES    SpringData-ES 语法    支持    支持 索引自动创建    支持    不支持 索引自动更新    支持    不支持 索引手动创建及更新    支持    不支持 简单CRUD    支持    支持 复杂CRUD    支持    不支持 父子查询    支持    不支持 嵌套查询    支持    不支持 排序及权重    支持    不支持 分页查询    支持全部三种模式    仅支持一种 GEO地理位置查询    支持    不支持 聚合查询    支持    不支持 字段类型及查询推断    支持    不支持 数据自动平滑迁移    支持    不支持 性能    高(优于SpringData20%)    高 代码量    极低    中低 与原生查询的语法对比 再回头看看传统的原生查询操作:    // ES原生的RestHighLevel语法         List<Integer> values = Arrays.asList(2, 3);         BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();         boolQueryBuilder.must(QueryBuilders.termQuery("business_type", 1));         boolQueryBuilder.must(QueryBuilders.boolQuery()                      .must(QueryBuilders.termQuery("state", 9))                                     .should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("state", 8))                                     .must(QueryBuilders.termQuery("bidding_sign", 1))));         boolQueryBuilder.should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("business_type", 2))                                       .must(QueryBuilders.termsQuery("state", values))); 在对比Easy-Es的简化后:  // Easy-Es及Mybatis-Plus语法         wrapper.eq("business_type", 1)                    .and(a -> a.eq("state", 9).or(b -> b.eq("state", 8).eq("bidding_sign", 1)))                    .or(i -> i.eq("business_type", 2).in("state", 2, 3)); 综合看来,Easy-ES基本实碾压的姿态...可能有夸大嫌疑哈,不过文末笔者也会根据自己的一些了解做点简单总结~ 下面接着看~  使用注意 官方文档很早就把这块放在使用手册前面了,并且明确说明了,该框架在使用时需要避坑的地方:由于开发者开发Easy-ES时底层用了ES官方的RestHighLevelClient,所以对ES版本有要求,要求ES和RestHighLevelClient JAR依赖版本必须为7.14.0,至于es客户端,实际7.X任意版本都可以很好的兼容。  值得注意的是,由于SpringData-ElasticSearch的存在,Springboot它内置了和ES及RestHighLevelClient依赖版本,这导致了不同版本的Springboot实际引入的ES及RestHighLevelClient 版本不同,而ES官方的这两个依赖在不同版本间的兼容性非常差,进一步导致很多用户无法正常使用Easy-Es。可谓非常良心,也就是说在使用时必须指定ES和RestHighLevelClient JAR依赖版本必须为7.14.0,其实笔者也试了一下,因为笔者本地ES是7.6.1的,在不排除依赖冲突的时候,其实也可以正常运行项目的,并且执行一些基础操作也是可以的(低版本的没试过)。但是会报错:   部分操作是正常的,但是总有这个异常看着也很难受,因此在后续实践中也改为了7.14.0,不知道随着ES8.0出现后,这个框架会不会优化更新,敬请期待~  排除依赖冲突也很容易,直接照着避坑指南重新引入7.14.0依赖即可:  <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>             <exclusions>                 <exclusion>                     <groupId>org.elasticsearch.client</groupId>                     <artifactId>elasticsearch-rest-high-level-client</artifactId>                 </exclusion>                 <exclusion>                     <groupId>org.elasticsearch</groupId>                     <artifactId>elasticsearch</artifactId>                 </exclusion>             </exclusions>         </dependency>         <dependency>             <groupId>org.elasticsearch.client</groupId>             <artifactId>elasticsearch-rest-high-level-client</artifactId>             <version>7.14.0</version>         </dependency>         <dependency>             <groupId>org.elasticsearch</groupId>             <artifactId>elasticsearch</artifactId>             <version>7.14.0</version>         </dependency>          <!-- https://mvnrepository.com/artifact/org.dromara.easy-es/easy-es-boot-starter -->         <dependency>             <groupId>org.dromara.easy-es</groupId>             <artifactId>easy-es-boot-starter</artifactId>             <version>2.0.0-beta2</version>         </dependency>   后续简单配置一些基础配置后运行就正常了,客户端保持7.6.1版本也不影响使用。  基础配置介绍 这里主要是在yml文件中对ES的一些基础配置,如果并没有太多需求,只要配置个地址即可,如果ES设置了账号密码认证,相应配置上账号密码即可。  easy-es:   banner: true   address: 127.0.0.1:9200 # es连接地址+端口 格式必须为ip:port,如果是集群则可用逗号隔开   connect-timeout: 5000 #  username: zhangsan #  password: 123456 另外如果为了提高生产环境性能,也持支按需配置:  easy-es:   keep-alive-millis: 1000 # 心跳策略时间 单位:ms   connect-timeout: 2000 # 连接超时时间 单位:ms   socket-timeout: 3000 # 通信超时时间 单位:ms    request-timeout: 4000 # 请求超时时间 单位:ms   connection-request-timeout: 5000 # 连接请求超时时间 单位:ms   max-conn-total: 20 # 最大连接数 单位:个   max-conn-per-route: 20 # 最大连接路由数 单位:个   #其他全局配置----------------------------------------------------   enable: true # 是否开启Easy-ES自动配置 默认开启,为false时则不启用该框架   schema: http # 默认为http 可缺省   banner: true # 默认为true 打印banner 若您不期望打印banner,可配置为false   global-config:     process-index-mode: smoothly #索引处理模式,smoothly:平滑模式,默认开启此模式, not_smoothly:非平滑模式, manual:手动模式     print-dsl: true # 开启控制台打印通过本框架生成的DSL语句,默认为开启,测试稳定后的生产环境建议关闭,以提升少量性能     distributed: false # 当前项目是否分布式项目,默认为true,在非手动托管索引模式下,若为分布式项目则会获取分布式锁,非分布式项目只需synchronized锁.     reindexTimeOutHours: 72 # 重建索引超时时间 单位小时,默认72H 可根据ES中存储的数据量调整     async-process-index-blocking: true # 异步处理索引是否阻塞主线程 默认阻塞 数据量过大时调整为非阻塞异步进行 项目启动更快     active-release-index-max-retry: 4320 # 分布式环境下,平滑模式,当前客户端激活最新索引最大重试次数,若数据量过大,重建索引数据迁移时间超过4320/60=72H,可调大此参数值,此参数值决定最大重试次数,超出此次数后仍未成功,则终止重试并记录异常日志     active-release-index-fixed-delay: 60 # 分布式环境下,平滑模式,当前客户端激活最新索引最大重试次数 分布式环境下,平滑模式,当前客户端激活最新索引重试时间间隔 若您期望最终一致性的时效性更高,可调小此值,但会牺牲一些性能          db-config:       map-underscore-to-camel-case: false # 是否开启下划线转驼峰 默认为false       index-prefix: daily_ # 索引前缀,可用于区分环境  默认为空 用法和MP的tablePrefix一样的作用和用法       id-type: customize # id生成策略 customize为自定义,id值由用户生成,比如取MySQL中的数据id,如缺省此项配置,则id默认策略为es自动生成       field-strategy: not_empty # 字段更新策略 默认为not_null       enable-track-total-hits: true # 默认开启,开启后查询所有匹配数据,若不开启,会导致无法获取数据总条数,其它功能不受影响,若查询数量突破1W条时,需要同步调整@IndexName注解中的maxResultWindow也大于1w,并重建索引后方可在后续查询中生效(不推荐,建议分页查询).       refresh-policy: immediate # 数据刷新策略,默认为不刷新,若对数据时效性要求比较高,可以调整为immediate,但性能损耗高,也可以调整为折中的wait_until       batch-update-threshold: 10000 # 批量更新接口的阈值 默认值为1万,突破此值需要同步调整enable-track-total-hits=true,@IndexName.maxResultWindow > 1w,并重建索引.       smartAddKeywordSuffix: true # 是否智能为字段添加.keyword后缀 默认开启,开启后会根据当前字段的索引类型及当前查询类型自动推断本次查询是否需要拼接.keyword后缀  如果需要实时记录DSL的执行日志,也可以进行相应的日志信息打印配置:  #开启es的DSL日志 logging:   level:     trace: trace 核心注解使用介绍 在Easy-ES中也有相对于MyBatisPlus那样的注解支持。比较重点的注解有如下4个:  @EsMapperScan  @IndexName  @IndexId  @IndexField  @EsMapperScan  这个注解类似于Mybatis框架的mapper扫描注解。在ES项目中只要我们的mapper接口继承BaseEsMapper<>就能调用内部封装的可供直接使用的方法。  package com.yy.config.mapper;   import com.yy.config.pojo.TestUser; import org.dromara.easyes.core.core.BaseEsMapper; import org.springframework.stereotype.Component;   /**  * @author young  * Date 2023/5/25 16:01  * Description: springboot-demo08-elasticsearch  */ @Component public interface TestMapper extends BaseEsMapper<TestUser> { } 但是前提是需要@EsMapperScan扫描到改包,在SpringBoot项目启动类上标明mapper接口所在包的位置@EsMapperScan("com.yy.config.mapper")即可,否则是会出错的。另外为了区别于MyBatis的扫描注解扫描mapper(dao)接口,因为两个框架彼此独立,扫描的时候没办法隔离,所以格外需要注意将MyBatis扫描的包与Easy-ES扫描的包区分开来,不能共用一个,否则也会报错!  @IndexName  同MyBatisPlus中的@TableName注解一样,主要是在实体类中标识对应的索引名称,因为ES中没有表的概念,而是对应的Index。其字段功能如下表所示:  属性    类型    必须指定    默认值    描述 value    String    否    ""    索引名,可简单理解为MySQL表名 shardsNum    int    否    1    索引分片数 replicasNum    int    否    1    索引副本数 aliasName    String    否    ""    索引别名 keepGlobalPrefix    boolean    否    false    是否保持使用全局的 tablePrefix 的值,与MP用法一致 child    boolean    否    false    是否子文档 childClass    Class    否    DefaultChildClass.class    父子文档-子文档类 maxResultWindow    int    否    10000    分页返回的最大数据量,默认值为1万条,超出推荐使用searchAfter或滚动查询等方式,详见拓展功能章节. 当此值调整至大于1W后,需要重建索引并同步开启配置文件中的enable-track-total-hits=true方可生效 routing    String    否    ""    路由,CRUD作用的路由 如果在实体类上不使用该注解,则ES会默认将实体类名作为索引名。如果有全局配置或者自动生成过索引名,但是也用注解指定了,则优先级排序: 注解索引>全局配置索引前缀>自动生成。另外Easy-ES也支持动态索引名称,可以调用mapper或者CRUD中的wrapper修改索引名称。  @IndexId  这个同样对应@TableId,可以指定id的生成类型并且标识索引id。在ES中如果实体类中有一个类型为String的id,在不添加该注解的条件下,会默认将该id识别为ES中的_id。如果是其他名称(比如ids),则会新建ids的索引字段。此时如果用@IndexId注解标识该ids,则不会创建新字段,而是映射为 _id。  @IndexField  用于实体类字段的注解。标识实体类中被作为ES索引字段的字段,参数功能如下:  属性    类型    必须指定    默认值    描述 value    String    否    ""    字段名 exist    boolean    否    true    字段是否存在 fieldType    Enum    否    FieldType.NONE    字段在es索引中的类型 fieldData    boolean    否    false    text类型字段是否支持聚合 analyzer    String    否    Analyzer.NONE    索引文档时用的分词器 searchAnalyzer    String    否    Analyzer.NONE    查询分词器 strategy    Enum    否    FieldStrategy.DEFAULT    字段验证策略 dateFormat    String    否    ""    es索引中的日期格式,如yyyy-MM-dd nestedClass    Class    否    DefaultNestedClass.class    嵌套类 parentName    String    否    ""    父子文档-父名称 childName    String    否    ""    父子文档-子名称 joinFieldClass    Class    否    JoinField.class    父子文档-父子类型关系字段类 ignoreCase    boolean    否    false    keyword类型字段是否忽略大小写 可根据自己不同场景的应用需求灵活配置:  public class TestUser {               private String stephen;          // 场景一:标记es中不存在的字段     @IndexField(exist = false)     private String token;              // 场景二:更新时,此字段非空字符串才会被更新     @IndexField(strategy = FieldStrategy.NOT_EMPTY)     private String description;          // 场景三: 指定fieldData     @IndexField(fieldType = FieldType.TEXT, fieldData = true)     private String content;          // 场景四:自定义字段名     @IndexField("my_music")         private String music;       // 场景五:支持日期字段在es索引中的format类型     @IndexField(fieldType = FieldType.DATE, dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")     private String createTime;       // 场景六:支持指定字段在es索引中的分词器类型     @IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_MAX_WORD)     private String username;          // 场景七:支持指定字段在es的索引中忽略大小写,以便在term查询时不区分大小写,仅对keyword类型字段生效,es的规则,并非框架限制.     @IndexField(fieldType = FieldType.KEYWORD, ignoreCase = true)     private String title; }  额外需要注意的是strategy = FieldStrategy.NOT_EMPTY,它有三个可选配置:  not_null: 非Null判断,字段值为非Null时,才会被更新  not_empty: 非空判断,字段值为非空字符串时才会被更新  ignore: 忽略判断,无论字段值为什么,都会被更新  并且该配置是可以在yml配置文件中全局配置的,但是也可以通过该注解个性化配置,不过此时:全局配置的优先级是小于注解配置  索引模式介绍 在ES中,索引的创建和更新不仅复杂,而且难于维护,一旦索引有变动,就必须面对索引重建带来的服务停机和数据丢失等问题... 尽管ES官方提供了索引别名机制来解决问题,但门槛依旧很高,步骤繁琐,在生产环境中由人工操作非常容易出现失误带来严重的问题. 为了解决这些痛点,Easy-Es提供了多种策略,将用户彻底从索引的维护中解放出来,并提供了多种索引处理策略,来满足不同用户的个性化需求。  其中它支持三种索引托管模式可供我们使用:自动平滑模式、自动非平滑模式和手动模式。  自动平滑模式  该模式相当于将索引的创建、更新、数据迁移等操作全部交由Easy-Es自动完成,过程零停机,连索引类型都可以自动推断,这个方式也是目前Easy-Es默认支持的方式之一。其核心处理流程如下图(来源于Easy-Es官网-索引托管模式):   需要注意的是:在自动托管模式下,系统会自动生成一条名为ee-distribute-lock的索引,该索引为框架内部使用,用户可忽略,若不幸因断电等其它因素极小概率下发生死锁,可删除该索引即可。另外,在使用时如碰到索引变更,原索引名称可能会被追加后缀s0或s1。关于s0和s1后缀,在此模式下无法避免,因为要保留原索引数据迁移,又不能同时存在两个同名索引。  自动非平滑模式  该模式下,索引额创建及更新由EE全自动异步完成,但不处理数据迁移工作,适合在开发及测试环境使用,当然如果使用logstash等其它工具来同步数据,亦可在生产环境开启此模式,在此模式下不会出现s0和s1后缀,索引会保持原名称。其核心流程如下图所示:   以上两种自动模式中,索引信息主要依托于实体类,如果用户未对该实体类进行任何配置,Easy-Es依然能够根据字段类型智能推断出该字段在ES中的存储类型。当然,仅靠框架自动推断是不够的,我们仍然建议您在使用中尽量进行详细的配置,以便框架能自动创建出生产级的索引。  ES的自动推断映射表如下:  JAVA    ES byte    byte short    short int    integer long    long float    float double    double BigDecimal    keyword char    keyword String    keyword_text boolean    boolean Date    date LocalDate    date LocalDateTime    date List    text ...    ... 手动模式  在此模式下,索引的所有维护工作Easy-Es框架均不介入,由用户自行处理,Easy-Es提供了开箱即用的索引CRUD相关API,我们可以选择使用该API手动维护索引,由于API高度完善,就算自己创建也比原生简单。   在手动模式下,我们可以通过注解+mapper接口提供的createIndex方法创建索引。也可以通过api创建,每个需要被索引的字段都需要处理,比较繁琐,但灵活性最好,支持所有es能支持的所有索引创建,供0.01%场景使用(不推荐)  @Test     public void testCreatIndex() {         LambdaEsIndexWrapper<TestUser> wrapper = new LambdaEsIndexWrapper<>();          wrapper.indexName(TestUser.class.getSimpleName().toLowerCase());           // 此处将文章标题映射为keyword类型(不支持分词),文档内容映射为text类型,可缺省         // 支持分词查询,内容分词器可指定,查询分词器也可指定,,均可缺省或只指定其中之一,不指定则为ES默认分词器(standard)         wrapper.mapping(TestUser::getTitle, FieldType.KEYWORD)                 .mapping(TestUser::getContent, FieldType.TEXT,Analyzer.IK_MAX_WORD,Analyzer.IK_MAX_WORD);                  // 如果上述简单的mapping不能满足你业务需求,可自定义mapping         Map<String, Object> map = new HashMap<>();         Map<String, Object> prop = new HashMap<>();         Map<String, String> field = new HashMap<>();         field.put("type", FieldType.KEYWORD.getType());         prop.put("this_is_field", field);         map.put("properties", prop);         wrapper.mapping(map);           // 设置分片及副本信息,2个shards,1个replicas,可缺省         wrapper.settings(2,1);           // 如果上述简单的settings不能满足你业务需求,可自定义settings         // 设置别名信息,可缺省         String aliasName = "user";         wrapper.createAlias(aliasName);                  // 创建索引         boolean isOk = testUserMapper.createIndex(wrapper);     }   Tips:在使用手动模式时需要手动在yml文件中配置该模式:  easy-es:   global-config:     process_index_mode: manual #索引处理模式,smoothly:平滑模式,默认开启此模式, not_smoothly:非平滑模式, manual:手动模式 测试项目准备 完成这些基础了解后可以试着使用Easy-ES的功能了。  Step1:导入依赖  在自己的项目中根据要求导入7.14.0的ES依赖(不影响7.x的客户端使用),并配置yml文件中的ES服务地址信息,上面也说过,如果只用RestHighLevelClient不配置也无所谓,但是这个Easy-Es需要手动配置。  Step2:配置yml文件中的ES地址  server:   port: 8080 easy-es:   banner: true   address: 127.0.0.0:9200   global-config:     distributed: false   # connect-timeout: 5000 #开启es的DSL日志 logging:   level:     trace: trace spring:   application:     name: Easy-Es_Test 当然,配置这个好处就是不用我们手动再去配置RestHighLevelClient了,并且同样可以使用RestHighLevelClient来自定义需要的功能,保证了其扩展性。  Step3:创建ES独立的mapper接口  根据使用要求需要一个类MyBatis中的数据层接口继承Easy-Es的功能,这样就能使用其封装的功能了。但是不同之处在于省掉了service接口,相比MyBatisPlus更简洁了,功能全集中在mapper接口中。  package com.yy.config.mapper;   import com.yy.config.pojo.TestUser; import org.dromara.easyes.core.core.BaseEsMapper; import org.springframework.stereotype.Component;   /**  * @author young  * Date 2023/5/25 16:01  * Description: 继承Easy-Es功能的mapper接口  */ @Component public interface TestMapper extends BaseEsMapper<TestUser> { } Step4:扫描mapper接口  为了让mapper层功能正常使用,需要在SpringBoot项目启动类上扫描ES接口,并将其所在的包与MyBatis中的扫描包区分,不能将接口建在同一个扫描包路径下。  package com.yy;   import org.dromara.easyes.starter.register.EsMapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;   @SpringBootApplication @EsMapperScan("com.yy.config.mapper") public class SpringbootDemo08ElasticsearchApplication {       public static void main(String[] args) {         SpringApplication.run(SpringbootDemo08ElasticsearchApplication.class, args);     } } 功能使用 搭建好Easy-Es的基础使用框架后现在就可以试一下这个框架的功能咋样了。  数据CRUD 在Easy-Es中推荐了两种条件封装方式以便我们进行数据操作:创建LambdaEsQueryWrapper对象以及通过EsWrappers创建对象。第一种与MyBatisPlus中的QueryWrapper类似,而EsWrappers这个类更像一个工具类,方便创建不同的包装对象以及链式调用的对象,并在索引和数据的查询、更新中发挥包装执行条件的作用。  新增操作   @Test     void testAdd(){         //添加数据,可在数据同步时使用         TestUser lebron = new TestUser().setAge(38).setName("勒布朗詹姆斯").setTeam("Los Angeles Lakers").setDescription("历史得分王");         TestUser stephen = new TestUser().setAge(35).setName("斯蒂芬库里").setTeam("Golden State Warriors").setDescription("历史三分王");         TestUser young = new TestUser().setAge(17).setName("斯蒂芬young").setTeam("yy").setDescription("yy");         ArrayList<TestUser> testUsers = new ArrayList<>();         testUsers.add(lebron);         testUsers.add(stephen);         testUsers.add(young);         // 批量插入多条记录         System.out.println(testMapper.insertBatch(testUsers));     } 这里测试的是一个批量插入操作,向ES中插入数据,用过MyBatisPlus的应该很熟悉,方法名称都是一致的。  2023-05-31 21:33:31.468  INFO 18292 --- [           main] easy-es                                  : Elasticsearch jar version:7.14.0 2023-05-31 21:33:31.665  INFO 18292 --- [           main] easy-es                                  : Elasticsearch client version:7.6.1 2023-05-31 21:33:31.665  WARN 18292 --- [           main] easy-es                                  : Elasticsearch clientVersion:7.6.1 not equals jarVersion:7.14.0, It does not affect your use, but we still recommend keeping it consistent! 2023-05-31 21:33:32.535  INFO 18292 --- [           main] gbootDemo08ElasticsearchApplicationTests : Started SpringbootDemo08ElasticsearchApplicationTests in 4.123 seconds (JVM running for 5.533) 2023-05-31 21:33:32.604  INFO 18292 --- [           main] easy-es                                  : ===> Smoothly process index mode activated 2023-05-31 21:33:32.711  INFO 18292 --- [onPool-worker-1] easy-es                                  : ===> Index not exists, automatically creating index by easy-es... 2023-05-31 21:33:33.155  INFO 18292 --- [onPool-worker-1] easy-es                                  : ===> Congratulations auto process index by Easy-Es is done ! 3 在没有索引时,由于我们使用的是自动平滑模式生成索引,因此会在日志中生成相应提示以及插入操作成功返回的数据数3。  查看Es-head后即可看见相应的索引创建成功以及里面的数据成功插入:   修改操作     @Test     void testUpdate(){         TestUser newDate = new TestUser().setDescription("明年会退役吗?");         Integer update1 = EsWrappers.lambdaChainUpdate(testMapper).eq(TestUser::getName, "勒布朗詹姆斯").update(newDate);         System.out.println(update1>0?"修改成功!":"修改失败!");     } 这里笔者为了方便也是使用了链式调用一步完成的,修改name为“勒布朗詹姆斯”的数据:  2023-05-31 21:38:00.460  INFO 5616 --- [           main] easy-es                                  : Elasticsearch jar version:7.14.0 2023-05-31 21:38:00.672  INFO 5616 --- [           main] easy-es                                  : Elasticsearch client version:7.6.1 2023-05-31 21:38:00.672  WARN 5616 --- [           main] easy-es                                  : Elasticsearch clientVersion:7.6.1 not equals jarVersion:7.14.0, It does not affect your use, but we still recommend keeping it consistent! 2023-05-31 21:38:01.547  INFO 5616 --- [           main] gbootDemo08ElasticsearchApplicationTests : Started SpringbootDemo08ElasticsearchApplicationTests in 4.097 seconds (JVM running for 5.374) 2023-05-31 21:38:01.607  INFO 5616 --- [           main] easy-es                                  : ===> Smoothly process index mode activated 2023-05-31 21:38:01.688  INFO 5616 --- [onPool-worker-1] easy-es                                  : ===> Index exists, automatically updating index by easy-es... 2023-05-31 21:38:02.095  INFO 5616 --- [onPool-worker-1] easy-es                                  : ===> index has nothing changed 2023-05-31 21:38:02.095  INFO 5616 --- [onPool-worker-1] easy-es                                  : ===> Congratulations auto process index by Easy-Es is done ! 2023-05-31 21:38:02.367  INFO 5616 --- [           main] easy-es                                  : ===> Execute By Easy-Es:  index-name: test_user DSL:{"size":10000,"query":{"bool":{"must":[{"term":{"name.keyword":{"value":"勒布朗詹姆斯","boost":1.0}}}],"adjust_pure_negative":true,"boost":1.0}},"_source":{"includes":["_id"],"excludes":[]},"track_total_hits":2147483647} 修改成功! 控制台成功打印了相应的DSL日志及操作执行结果。  查询操作    @Test     void testQuery(){         //new构建查询条件,基础写法         LambdaEsQueryWrapper<TestUser> wrapper = new LambdaEsQueryWrapper<>();         //EsWrappers封装查询条件         //LambdaEsQueryWrapper<TestUser> queryWrapper = EsWrappers.lambdaQuery(TestUser.class).like(TestUser::getDescription, "历史");         wrapper.like(TestUser::getDescription,"历史");         wrapper.orderByDesc(TestUser::getAge);         //用EsWrappers创建链式写法并模糊匹配查询         TestUser one = EsWrappers.lambdaChainQuery(testMapper).likeLeft(TestUser::getName, "库里").one();         SearchResponse search1 = testMapper.search(wrapper);         System.out.println(Arrays.toString(search1.getHits().getHits()));         System.out.println(one);     } 查看执行结果:  index-name: test_user DSL:{"size":10000,"query":{"bool":{"must":[{"wildcard":{"name.keyword":{"wildcard":"*库里","boost":1.0}}}],"adjust_pure_negative":true,"boost":1.0}},"track_total_hits":2147483647} 2023-05-31 21:52:56.800  INFO 812 --- [           main] easy-es                                  : ===> Execute By Easy-Es:  index-name: test_user DSL:{"size":10000,"query":{"bool":{"must":[{"wildcard":{"description":{"wildcard":"*历史*","boost":1.0}}}],"adjust_pure_negative":true,"boost":1.0}},"sort":[{"age":{"order":"desc"}}],"track_total_hits":2147483647} [{   "_index" : "test_user",   "_type" : "_doc",   "_id" : "irYVcogBrMcQl9SRH46R",   "_score" : null,   "_source" : {     "age" : 38,     "description" : "历史得分王",     "name" : "勒布朗詹姆斯",     "team" : "Los Angeles Lakers"   },   "sort" : [     38   ] }, {   "_index" : "test_user",   "_type" : "_doc",   "_id" : "i7YVcogBrMcQl9SRH46R",   "_score" : null,   "_source" : {     "age" : 35,     "description" : "历史三分王",     "name" : "斯蒂芬库里",     "team" : "Golden State Warriors"   },   "sort" : [     35   ] }] TestUser(id=i7YVcogBrMcQl9SRH46R, name=斯蒂芬库里, description=历史三分王, age=35, team=Golden State Warriors)  可以基于like的模糊查询以及左匹配模糊查询结果也正确返回。  删除操作   @Test     void testDelete(){         Integer delete = testMapper.delete(EsWrappers.lambdaQuery(TestUser.class).filter(age -> age.le(TestUser::getAge, 30)));         System.out.println(delete>0?"删除成功!":"删除失败!");     } 这里通过filter过滤删除一条年龄小于30的数据:  …… 2023-05-31 22:01:06.816  INFO 872 --- [onPool-worker-1] easy-es                                  : ===> index has nothing changed 2023-05-31 22:01:06.817  INFO 872 --- [onPool-worker-1] easy-es                                  : ===> Congratulations auto process index by Easy-Es is done ! 删除成功! 结果也显而易见。  整体上,确实相比于原生写操作简单了不少。当然,以上只是些基础的功能而已,高阶的功能还要慢慢看~  高阶查询 通常在ES的使用中,在业务中的操作可能远不止简简单单的基础增删该查而已,因此Easy-Es也扩展了很多高阶功能,以便大家灵活使用。官网介绍了很多,但是这里笔者仅介绍几个我个人比较感兴趣的记录一下使用方法,其他读者可自行参照官网去学习。  高亮显示 高亮显示应该是ES作为数据搜索引擎用得最多的地方,但是在原生高亮使用中需要通过HighlightBuilder这个类来配置,但是在Easy-Es中,只需要在对应的实体类上添加高亮注解@HighLight就行了。并且省去了通过类配置自定义高亮样式的操作,只需要在注解对应的属性上设置就行了。 ————————————————                 原文链接:https://blog.csdn.net/qq_42263280/article/details/130993569 
  • [技术干货] Easy-Es框架实践测试整理 基于ElasticSearch的ORM框架
    介绍 Easy-Es(简称EE)是一款基于ElasticSearch(简称Es)官方提供的RestHighLevelClient打造的ORM开发框架,在 RestHighLevelClient 的基础上,只做增强不做改变,为简化开发、提高效率而生。EE是Mybatis-Plus的Es平替版,在有些方面甚至比MP更简单,同时也融入了更多Es独有的功能,助力您快速实现各种场景的开发. (1)Elasticsearch java 客户端种类 Elasticsearch 官方提供了很多版本的 Java 客户端,包含但不限于: 【1】Transport 客户端 【2】Java REST 客户端 【3】Low Level REST 客户端 【4】High Level REST 客户端 【5】Java API 客户端 非官方的 Java 客户端,包含但不限于: 【1】Jest 客户端 【2】BBoss 客户端 【3】Spring Data Elasticsearch客户端 【4】easy-es客户端  (2)优势和特性分析 【1】全自动索引托管 全球开源首创的索引托管模式,开发者无需关心索引的创建更新及数据迁移等繁琐步骤,索引全生命周期皆可托管给框架,由框架自动完成,过程零停机,用户无感知,彻底解放开发者。该特性可以帮助我们在修改索引名称,索引配置,索引结构后自动更新,并迁移数据,减少运维成本。  【2】屏蔽语言差异。 提供类似Mybatis-Plus使用方式,相对于RestHighLevelClient使用便利不少,相对于Springdata-ElasticSearch的使用也有进一步的改进,更加符合国人的使用习惯。  【3】零魔法值和代码量极少。 这一点主要是针对RestHighLevelClient代码的臃肿问题解决,使用上更加简单。  【4】强大的 CRUD 操作。 内置通用 Mapper,仅仅通过少量配置即可实现大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求。  【5】支持 Lambda 形式调用 通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错段。  【6】内置分页插件。 基于RestHighLevelClient 物理分页,开发者无需关心具体操作,且无需额外配置插件,写分页等同于普通 List 查询,且保持和PageHelper插件同样的分页返回字段,无需担心命名影响。  【7】支持ES高阶语法 支持高亮搜索,分词查询,权重查询,Geo地理位置查询,IP查询,聚合查询等高阶语法。  【8】良好的拓展性,支持混合使用。 底层仍使用RestHighLevelClient,可保持其拓展性,开发者在使用EE的同时,仍可使用RestHighLevelClient的功能。  (3)性能、安全、拓展、社区 Easy-Es对于性能和安全问题专门做了文档描述,链接如下所示: https://www.easy-es.cn/pages/6e2197 根据文档描述整体的性能还是很好的,安全上已接入OSCS墨菲安全扫描无安全风险。单元测试用例综合覆盖率超95%,已上线的所有功能均有测试用例覆盖,且经过生产环境和开源社区大量用户使用验证。  EE底层用的就是Es官方提供的RestHighLevelClient,我们只是对RestHighLevelClient做了增强,并没有改变减少或是削弱它原有的功能。我们在项目中可以使用EE框架也可以根据需要直接使用RestHighLevelClient,是支持混合使用的。  目前该开源框架已经加入dromara开源社区,社区目前活跃,每年会发很多个版本,不断提升用户体验。 gitee仓库情况: github仓库情况: 附上同类型的产品spring-data-elasticsearch仓库情况:  (2)ES版本及SpringBoot版本说明 Easy-Es底层用了ES官方的RestHighLevelClient,所以对ES版本有要求,要求ES和RestHighLevelClient JAR依赖版本必须为7.14.0,至于es客户端,实际 测下来7.X任意版本都可以很好的兼容.  值得注意的是,由于Springdata-ElasticSearch的存在,Springboot它内置了和ES及RestHighLevelClient依赖版本,这导致了不同版本的Springboot实际引入的ES及RestHighLevelClient 版本不同,而ES官方的这两个依赖在不同版本间的兼容性非常差,进一步导致很多用户无法正常使用Easy-Es,这实际上这是一个依赖冲突的问题. Easy-Es在项目启动时做了依赖校验,如果项目在启动时可以在控制台看到打印出级别为Error且内容为"Easy-Es supported elasticsearch and restHighLevelClient jar version is:7.14.0 ,Please resolve the dependency conflict!" 的日志时,则说明有依赖冲突待您解决. 解决方案其实很简单,可以像下面一样配置maven的exclude移除Springboot或Easy-Es已经声明的ES及RestHighLevelClient依赖,然后重新引入,引入时指定版本号为7.14.0即可解决.          <dependency>             <groupId>cn.easy-es</groupId>             <artifactId>easy-es-boot-starter</artifactId>             <version>1.1.0</version>             <exclusions>                 <exclusion>                     <groupId>org.elasticsearch.client</groupId>                     <artifactId>elasticsearch-rest-high-level-client</artifactId>                 </exclusion>                 <exclusion>                     <groupId>org.elasticsearch</groupId>                     <artifactId>elasticsearch</artifactId>                 </exclusion>             </exclusions>         </dependency>          <dependency>             <groupId>org.elasticsearch.client</groupId>             <artifactId>elasticsearch-rest-high-level-client</artifactId>             <version>7.14.0</version>         </dependency>         <dependency>             <groupId>org.elasticsearch</groupId>             <artifactId>elasticsearch</artifactId>             <version>7.14.0</version>         </dependency> 也可以简单粗暴的把springboot版本调整到2.5.5,其它都不需要调整,也可以勉强正常使用.  索引处理 (一)索引别名策略 为了更好的理解和使用索引的更新原理,最好先了解下ES索引别名的机制。 索引别名是指给一个或者多个索引定义另外一个名称,使索引别名和索引之间可以建立某种逻辑关系。 可以用别名表示别名和索引之间的包含关系,假设我们当前有多个日期日志索引记录,比如log_index_01,log_index_02,log_index_03…,那么我们为了统一检索可以设置一个别名为log_index,然后请求索引为log_index,这样就可以通过log_index查询多个索引的数据,而不用一个一个的指定查询了。 需要指出的是,在默认情况下,当一个别名只指向一个索引时,写入数据的请求可以指向这个别名,如果这个别名指向多个索引,则写入数据的请求是不可以指向这个别名的。 引入别名之后,还可以用别名表示索引之间的替代关系。这种关系一般是在某个索引被创建后,有些参数是不能更改的(如主分片的个数),但随着业务发展,索引中的数据增多,需要更改索引参数进行优化。我们需要平滑地解决该问题,既要更改索引的设置,又不能改变索引名称,这时就可以使用索引别名。 假设一个酒店的搜索别名设置为hotel,初期创建索引hotel_1时,主分片个数设置为5,然后设置hotel_1的别名为hotel。此时客户端使用索引别名hotel进行搜索请求,该请求会转发到索引hotel_1中。假设此时酒店索引中的新增数据急剧增长,索引分片需要扩展,需要将其扩展成为10个分片的索引。但是一个索引在创建后,主分片个数已经不能更改,因此只能考虑使用索引替换来完成索引的扩展。这时可以创建一个索引hotel_2,除了将其主分片个数设置为10外,其他设置与hotel_1相同。当hotel_2的索引数据准备好后,删除hotel_1的别名hotel,同时,置hotel_2的别名为hotel。此时客户端不用进行任何改动,继续使用hotel进行搜索请求时,该请求会转发给索引hotel_2。如果服务稳定,最后将hotel_1删除即可。此时借助别名就完成了一次索引替换工作。如下图所示,在左图中,hotel索引别名暂时指向hotel_1,hotel_2做好了数据准备;在右图中,hotel索引别名指向hotel_2,完成了索引的扩展切换。 参考《Elasticsearch搜索引擎构建入门与实战》 索引别名在这种需要变更索引的情况下,搜索端不需要任何变更即可完成切换,这在实际的生产环境中是非常方便的。 (二)easy-es索引的自动托管之平滑模式实践 (1)介绍 提示:如果在使用过程中发现索引未得到更新,建议对照文章“ES版本及SpringBoot版本”部分检查下版本。 自动托管之平滑模式(自动挡-雪地模式) 默认开启此模式。 在此模式下,索引的创建更新数据迁移等全生命周期用户均不需要任何操作即可完成,过程零停机,用户无感知,可实现在生产环境的平滑过渡,类似汽车的自动档-雪地模式,平稳舒适,彻底解放用户! 需要值得特别注意的是,在自动托管模式下,系统会自动生成一条名为ee-distribute-lock的索引,该索引为框架内部使用,用户可忽略,若不幸因断电等其它因素极小概率下发生死锁,可删除该索引即可.另外,在使用时如碰到索引变更,原索引名称可能会被追加后缀_s0或_s1,不必慌张,这是全自动平滑迁移零停机的必经之路,索引后缀不影响使用,框架会自动激活该新索引.关于_s0和_s1后缀,在此模式下无法避免,因为要保留原索引数据迁移,又不能同时存在两个同名索引,凡是都是要付出代价的,如果您不认可此种处理方式,可以使用其他的方式。 (2)实践测试 【1】创建实体类并绑定索引名称为document,添加相关的字段属性,当前索引未创建。 【2】修改日志为debug模式以便查看DSL语句。启动项目,可以看到框架帮助我们自动创建了索引。 【3】默认创建的主分片为1,副本数为1 【4】现在我们添加一些测试数据,直接通过框架提供的API测试,现在已经创建了三条数据。 【5】接下来修改实体类的主分片大小,副本数大小,添加字段,然后再重启项目。 【6】下面观察输出的DSL语句,分析实现原理。 1:首先创建一个新的索引,索引名称为:原索引名称_s0 2:然后通过reindex命令将原索引的数据迁移到新的索引上,DSL语句如下所示 3:修改别名,将原先的别名下包含的旧的索引去除,然后添加刚刚创建的新的索引,这样通过原先的索引别名依然可以正常查询处理数据。 4:在完成上述文档迁移操作后,将旧的索引直接删除 5:以上执行流程中还包括了创建ee-distribute-lock索引,该索引为框架内部使用,可忽略。接着通过查询接口查询过往的数据,可以正常查询到历史的数据,并且其_settings属性和_mapping表结构都得到了更新。  索引文档的增删改查 插入记录 // 插入一条记录 Integer insert(T entity);  // 批量插入多条记录 Integer insertBatch(Collection<T> entityList) 更新记录 //根据 ID 更新 Integer updateById(T entity);  // 根据ID 批量更新 Integer updateBatchByIds(Collection<T> entityList);  //  根据动态条件 更新记录 Integer update(T entity, LambdaEsUpdateWrapper<T> updateWrapper);  删除记录  // 根据 ID 删除 Integer deleteById(Serializable id);  // 根据 entity 条件,删除记录 Integer delete(LambdaEsQueryWrapper<T> wrapper);  // 删除(根据ID 批量删除) Integer deleteBatchIds(Collection<? extends Serializable> idList); keyword模糊查询         LambdaEsQueryWrapper<UserInfo> wrapper = new LambdaEsQueryWrapper<>();         wrapper.like(UserInfo::getUserName, userName); text分词查询 当我们需要对字段进行分词查询时,需要该字段的类型为text类型,并且指定分词器(不指定就用ES默认分词器,效果通常不理想). 比如EE中常用的API match()等都需要字段类型为text类型. 当使用match查询时未查询到预期结果时,可以先检查索引类型,然后再检查分词器,因为如果一个词没被分词器分出来,那结果也是查询不出来的.  中文需要提前在ES中安装分词器。      /**      * 分词测试      */     @GetMapping("/match")     public EsPageInfo<UserInfo> match(String word) {         LambdaEsQueryWrapper<UserInfo> wrapper = new LambdaEsQueryWrapper<>();         wrapper.match(UserInfo::getContent, word);         return EsPageInfo.of(userInfoMapper.selectList(wrapper));     } 条件构造器 query_string方式          // 假设我的查询条件是:创建者等于老王,且创建者分词匹配"隔壁"(比如:隔壁老汉,隔壁老王),或者创建者包含猪蹄         // 对应mysql语法是(creator="老王" and creator like "老王") or creator like "%猪蹄%",下面用es的queryString来演示实现一样的效果         // 足够灵活,非常适合前端页面中的查询条件列表字段及条件不固定,且可选"与或"的场景.         LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();         String queryStr = QueryUtils.combine(Link.OR,                 QueryUtils.buildQueryString(Document::getCreator, "老王", Query.EQ, Link.AND),                 QueryUtils.buildQueryString(Document::getCreator, "隔壁", Query.MATCH))                 + QueryUtils.buildQueryString(Document::getCreator, "*猪蹄*", Query.EQ);         wrapper.queryStringQuery(queryStr);         List<Document> documents = documentMapper.selectList(wrapper);         System.out.println(documents); 对应的DSL语句和结果展示  分页查询 关于分页,支持了ES的三种分页模式,大家可参考下表,按需选择.      // 物理分页     EsPageInfo<T> pageQuery(LambdaEsQueryWrapper<T> wrapper, Integer pageNum, Integer pageSize); 1 2 文档链接: https://www.easy-es.cn/pages/0cf11e/#浅分页  注意事项 【1】目前还不支持ElasticSearch8.X的版本,目前暂时只支持es7x,也不支持6.X版本。 【2】easy-es索引的自动托管之平滑模式使用起来很方便,但是注意它会把原索引删除,使用新的索引,虽然在框架内使用不受影响,但是如果其他地方依赖了该索引那么可能会造成影响,如果使用这种方式,建议采用别名策略,不直接访问索引。 【3】对比于spring-data-elasticsearch和easy-es,二者在使用上都相对于Es官方提供的RestHighLevelClient有着更简洁的使用操作性。spring-data-elasticsearch因为属于spring-data项目维护,社区更加活跃更新也比较频繁,目前已经支持8.X版本了。easy-es在使用上相对来说更加友好而且更加符合国人习惯。 【4】easy-es所支持的混合模式,当easy-es无法满足需求时可以使用原生的RestHighLevelClient这对于实际应用非常适用。 【5】ES查询功能非常强,特性众多,因时间问题无法测试所有的功能情况。但是目前针对于常用的功能进行的测试,从结果看,基本上是满足日常的开发使用的,而且操作起来还是非常简便的。根据issues的反馈,easy-es预计在今年推出2.X版本,该版本将会有很多的优化和新的功能点,进一步满足开发使用。  参考文档 https://www.easy-es.cn/pages/ec7460/  https://github.com/zwzhangyu/ZyCodeHub/tree/main/middleware/elasticsearch/easy-es ————————————————        原文链接:https://blog.csdn.net/Octopus21/article/details/128988806 
  • [技术干货] 再见RestTemplate,Spring 6.1新特性:RestClient 了解一下
    Spring6.1的M2版本带来了新特性RestClient,它是RestTemplate的现代替代品,提供类似WebClient的流畅API。RestClient简化了HTTP请求,支持GET、POST等操作,能直接转换响应为对象,且在遇到错误时抛出异常。通过exchange方法,开发者可以进行更复杂的请求处理。 摘要由CSDN通过智能技术生成 在最近发布的Spring 6.1 M2版本中,推出了一个全新的同步HTTP客户端:RestClient。用一句话来让Spring开发者认识RestClient的话:像WebClient一样具备流畅API的RestTemplate。所以,RestClient的使命就是淘汰已经有14年历史的RestTemplate。  关于WebClient和RestTemplate,之前在几种服务消费方式(RestTemplate、WebClient、Feign)这篇文章中有详细的介绍。如果您有一定的了解,那么对于RestClient一定可以快速上手。  RestClient案例 下面我们通过几个官方给出的案例一起来快速的认识一下RestClient。  HTTP请求 下面是一个最简单的GET请求,返回一个字符串。从这个例子中,我们可以看到API形式跟WebClient类似。不像以前用RestTemplate的时候那么麻烦。 RestClient restClient = RestClient.create();  String result = restClient.get()   .uri("https://example.com")   .retrieve()   .body(String.class); System.out.println(result); 关于GET请求,很多时候我们返回的不仅仅是String,更多的时候是一些实体;同时我们有时候还需要获取HTTP状态码以及头信息。这个时候,我们可以使用toEntity方法来返回一个更为通用的ResponseEntity来进行后续操作,比如下面这样:  ResponseEntity<String> result = restClient.get()   .uri("https://example.com")   .retrieve()   .toEntity(String.class);  System.out.println("Response status: " + result.getStatusCode()); System.out.println("Response headers: " + result.getHeaders()); System.out.println("Contents: " + result.getBody()); 在业务层面,为了更方便的解析业务数据。RestClient还支持对结果进行对象转换。比如下面的例子,就是把HTTP请求返回的JSON数据转化为Pet对象。这样就免去了开发者手动从ResponseEntity中获取内容,再进行消息转化的麻烦。  int id = ... Pet pet = restClient.get()   .uri("https://petclinic.example.com/pets/{id}", id)   .accept(APPLICATION_JSON)   .retrieve()   .body(Pet.class); 关于其他请求,也是类似的,比如下面的POST请求:  Pet pet = ... ResponseEntity<Void> response = restClient.post()   .uri("https://petclinic.example.com/pets/new")   .contentType(APPLICATION_JSON)   .body(pet)   .retrieve()   .toBodilessEntity(); 错误处理 默认情况下,RestClient在接收到4xx和5xx状态码的时候,会抛出一个RestClientException的子类。对于这个动作,我们可以通过onStatus方法去重写它,比如下面这样:  String result = restClient.get()   .uri("https://example.com/this-url-does-not-exist")   .retrieve()   .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {       throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders())   })   .body(String.class); 上面的例子是进一步做了包装,并重新抛出。当然您也可以做一些业务性的其他操作。  高级处理 Exchange 直接看下面的例子:  Pet result = restClient.get()   .uri("https://petclinic.example.com/pets/{id}", id)   .accept(APPLICATION_JSON)   .exchange((request, response) -> {     if (response.getStatusCode().is4xxClientError()) {       throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders());     }     else {       Pet pet = convertResponse(response);       return pet;     }   }); exchange方法提供了更灵活且完整的请求处理入口。在这里,开发者里获取到request信息,也可以操作response信息。所以,如果您有复杂的处理逻辑上一节中的请求方法无法满足你需要的时候,就可以通过这里的exchange方法来定制复杂的处理逻辑。  小结 相信大家对RestTemplate一定都不陌生,但实际应用估计已经不是很多了,更多的会使用一些其他的客户端来实现HTTP的调用。如今Spring 6.1将推出的RestClient将很好的弥补这块不足,同时与WebClient互相补充。好了,今天的学习就到这里!如果您学习过程中如遇困难?可以加入我们超高质量的Spring技术交流群,参与交流与讨论,更好的学习与进步!更多Spring Boot教程可以点击直达!,欢迎收藏与转发支持 ————————————————        原文链接:https://blog.csdn.net/dyc87112/article/details/131776216 
  • [技术干货] SpringBoot集成Elasticsearch并进行增删改查操作
    今天给大家简单的介绍一下SpringBoot如何集成Elasticsearch,并简单的介绍一下基于SpringBoot模式下怎么进行简单的增删改查操作,这边增删改查操作有点类似于JPA的模式。(什么是JPA模式,大家可以自行搜索答案) Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。史上最全Java技术栈面试题都在这里面了:Java面试题 废话不多说,现在马上开始我们今天的内容。如何新建Springboot项目我这边就不废话了,不会的同学可以看我以前写的教程。 文章地址:IDEA上面如何创建SpringBoot项目-CSDN博客 1.首先是引入相关的依赖,下面是我的pom文件。 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>       <groupId>com.elasticsearch</groupId>     <artifactId>elasticsearch</artifactId>     <version>0.0.1-SNAPSHOT</version>     <packaging>jar</packaging>       <name>ElasticSearch</name>     <description>ElasticSearch project for Spring Boot</description>       <parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>2.0.2.RELEASE</version>         <relativePath/> <!-- lookup parent from repository -->     </parent>       <properties>         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>         <java.version>1.8</java.version>     </properties>       <dependencies>         <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->         <dependency>             <groupId>com.google.code.gson</groupId>             <artifactId>gson</artifactId>             <version>2.8.0</version>         </dependency>           <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-elasticsearch -->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-elasticsearch</artifactId>             <version>2.0.2.RELEASE</version>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>           <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>             <scope>test</scope>         </dependency>     </dependencies>       <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>             </plugin>         </plugins>     </build> </project>  pom.xml文件中最重要的其实就是引入ES(Elasticsearch的简称后面我都这么叫),也就是spring-boot-starter-data-elasticsearch 依赖。  2.接下来就是对应的配置文件了,具体配置文件如下所示:  # elasticsearch集群名称,默认的是elasticsearch spring.data.elasticsearch.cluster-name=my-application   #节点的地址 注意api模式下端口号是9300,千万不要写成9200 spring.data.elasticsearch.cluster-nodes=192.168.11.24:9300   #是否开启本地存储 spring.data.elasticsearch.repositories.enable=true 3.索引对应的实体类如下所示:  package com.elasticsearch.entity; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field;   /**  * @author linzhiqiang  * @date 2018/5/16  */ @Document(indexName = "company",type = "employee", shards = 1,replicas = 0, refreshInterval = "-1") public class Employee {     @Id     private String id;     @Field     private String firstName;     @Field     private String lastName;     @Field     private Integer age = 0;     @Field     private String about;       public String getId() {         return id;     }       public void setId(String id) {         this.id = id;     }       public String getFirstName() {         return firstName;     }       public void setFirstName(String firstName) {         this.firstName = firstName;     }       public String getLastName() {         return lastName;     }       public void setLastName(String lastName) {         this.lastName = lastName;     }       public Integer getAge() {         return age;     }       public void setAge(Integer age) {         this.age = age;     }       public String getAbout() {         return about;     }       public void setAbout(String about) {         this.about = about;     } }  4.实体类对应的dao接口如下所示: package com.elasticsearch.dao;   /**  * Created by 19130 on 2018/5/16.  */ import com.elasticsearch.entity.Employee; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Component; /**  * @author linzhiqiang  */ @Component public interface EmployeeRepository extends ElasticsearchRepository<Employee,String>{       /**      * 查询雇员信息      * @param id      * @return      */     Employee queryEmployeeById(String id); }  5.实体类对应的控制类如下所示:  package com.elasticsearch.controller; import com.elasticsearch.dao.EmployeeRepository; import com.elasticsearch.entity.Employee; import com.google.gson.Gson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;   /**  * @author linzhiqiang  */ @RestController @RequestMapping("es") public class EmployeeController {       @Autowired     private EmployeeRepository employeeRepository;       /**      * 添加      * @return      */     @RequestMapping("add")     public String add() {         Employee employee = new Employee();         employee.setId("1");         employee.setFirstName("xuxu");         employee.setLastName("zh");         employee.setAge(26);         employee.setAbout("i am in peking");         employeeRepository.save(employee);         System.err.println("add a obj");         return "success";     }       /**      * 删除      * @return      */     @RequestMapping("delete")     public String delete() {         Employee employee = employeeRepository.queryEmployeeById("1");         employeeRepository.delete(employee);         return "success";     }       /**      * 局部更新      * @return      */     @RequestMapping("update")     public String update() {         Employee employee = employeeRepository.queryEmployeeById("1");         employee.setFirstName("哈哈");         employeeRepository.save(employee);         System.err.println("update a obj");         return "success";     }     /**      * 查询      * @return      */     @RequestMapping("query")     public Employee query() {         Employee accountInfo = employeeRepository.queryEmployeeById("1");         System.err.println(new Gson().toJson(accountInfo));         return accountInfo;     } }    以上所有的增删改查操作都要基于搭建好的ES系统(关于Linux上面如何搭建ES系统大家可以自行google) 关于我踩过的坑: 1.ES中API的端口号是9300而不是9200。 2.ES系统中Elasticsearch.yml配置文件中要加入network.host: 0.0.0.0,否则外网地址访问不了。 3.最新的资料一定要去官网上面查看,博客上面好多都是过时的。官网地址:Elasticsearch Platform — Find real-time answers at scale | Elastic 4.注意JDK、ES、Springboot三者之间的版本,很多时候错误都是版本冲突引起的。 ————————————————               原文链接:https://blog.csdn.net/linzhiqiang0316/article/details/80343401 
  • [技术干货] 操作Elasticsearch
    1 Easy-Es 使用过Spring Data操作ES的小伙伴应该有所了解,它只能实现一些非常基本的数据管理工作,一旦遇到稍微复杂点的查询,基本都要依赖ES官方提供的RestHighLevelClient,Spring Data只是在其基础上进行了简单的封装。最近发现一款更优雅的ES ORM框架Easy-Es,使用它能像MyBatis-Plus一样操作ES  点击了解 SpringBoot 整合ElasticSearch  1.1 简介 Easy-Es(简称EE)是一款基于Elasticsearch(简称ES)官方提供的RestHighLevelClient打造的ORM开发框架,在RestHighLevelClient的基础上,只做增强不做改变,为简化开发、提高效率而生。EE和Mybatis-Plus(简称MP)的用法非常相似,如果你之前使用过MP的话,应该能很快上手EE。EE的理念是:把简单、易用、方便留给用户,把复杂留给框架。 官网地址:https://www.easy-es.cn/  EE的主要特性如下:  全自动索引托管:开发者无需关心索引的创建、更新及数据迁移等繁琐步骤,框架能自动完成。 屏蔽语言差异:开发者只需要会MySQL的语法即可使用ES。 代码量极少:与直接使用官方提供的RestHighLevelClient相比,相同的查询平均可以节省3-5倍的代码量。 零魔法值:字段名称直接从实体中获取,无需手写。 零额外学习成本: 开发者只要会国内最受欢迎的Mybatis-Plus用法,即可无缝迁移至EE。 1.2 MySQL与Easy-Es语法对比 首先我们来对MySQL、Easy-Es和RestHighLevelClient的语法做过对比,来快速学习下Easy-Es的语法。  MySQL    Easy-Es    es-DSL/es java api and    and    must or    or    should =    eq    term !=    ne    boolQueryBuilder.mustNot(queryBuilder) >    gt    QueryBuilders.rangeQuery(‘es field’).gt() >=    ge    QueryBuilders.rangeQuery(‘es field’).gte() <    lt    QueryBuilders.rangeQuery(‘es field’).lt() <=    le    QueryBuilders.rangeQuery(‘es field’).lte() like ‘%field%’    like    QueryBuilders.wildcardQuery(field,value) not like ‘%field%’    notLike    must not wildcardQuery(field,value) like ‘%field’    likeLeft    QueryBuilders.wildcardQuery(field,*value) like ‘field%’    likeRight    QueryBuilders.wildcardQuery(field,value*) between    between    QueryBuilders.rangeQuery(‘es field’).from(xx).to(xx) notBetween    notBetween    must not QueryBuilders.rangeQuery(‘es field’).from(xx).to(xx) is null    isNull    must not QueryBuilders.existsQuery(field) is notNull    isNotNull    QueryBuilders.existsQuery(field) in    in    QueryBuilders.termsQuery(" xx es field", xx) not in    notIn    must not QueryBuilders.termsQuery(" xx es field", xx) group by    groupBy    AggregationBuilders.terms() order by    orderBy    fieldSortBuilder.order(ASC/DESC) min    min    AggregationBuilders.min max    max    AggregationBuilders.max avg    avg    AggregationBuilders.avg sum    sum    AggregationBuilders.sum order by xxx asc    orderByAsc    fieldSortBuilder.order(SortOrder.ASC) order by xxx desc    orderByDesc    fieldSortBuilder.order(SortOrder.DESC) -    match    matchQuery -    matchPhrase    QueryBuilders.matchPhraseQuery -    matchPrefix    QueryBuilders.matchPhrasePrefixQuery -    queryStringQuery    QueryBuilders.queryStringQuery select *    matchAllQuery    QueryBuilders.matchAllQuery() -    highLight    HighlightBuilder.Field 1.3 集成及配置 1.3.1 pom.xml <dependency>     <groupId>cn.easy-es</groupId>     <artifactId>easy-es-boot-starter</artifactId>     <version>1.0.2</version> </dependency> 由于底层使用了ES官方提供的RestHighLevelClient,这里ES的相关依赖版本需要统一下,这里使用的ES客户端版本为7.14.0,ES版本为7.17.3;  <dependencyManagement>     <dependencies>         <dependency>             <groupId>org.elasticsearch.client</groupId>             <artifactId>elasticsearch-rest-high-level-client</artifactId>             <version>7.14.0</version>         </dependency>         <dependency>             <groupId>org.elasticsearch</groupId>             <artifactId>elasticsearch</artifactId>             <version>7.14.0</version>         </dependency>     </dependencies> </dependencyManagement> 1.3.2 配置 再修改配置文件application.yml对Easy-Es进行配置。  easy-es:   # 是否开启EE自动配置   enable: true   # ES连接地址+端口   address: localhost:9200   # 关闭自带banner   banner: false 添加Easy-Es的Java配置,使用@EsMapperScan配置好Easy-Es的Mapper接口和文档对象路径,如果使用了MyBatis-Plus的话,需要和它的扫描路径区分开来。  /**  * EasyEs配置类  */ @Configuration @EsMapperScan("com.test.easyes") public class EasyEsConfig { } 1.4 使用 Easy-Es集成和配置完成后,就可以开始使用了。  1.4.1 注解的使用 下面我们来学习下Easy-Es中注解的使用。 首先我们需要创建文档对象EsProduct,然后给类和字段添加上Easy-Es的注解;  /**  * 搜索商品的信息  */ @Data @EqualsAndHashCode @IndexName(value = "pms", shardsNum = 1, replicasNum = 0) public class EsProduct implements Serializable {     private static final long serialVersionUID = -1L;     @IndexId(type = IdType.CUSTOMIZE)     private Long id;     @IndexField(fieldType = FieldType.KEYWORD)     private String productSn;     private Long brandId;     @IndexField(fieldType = FieldType.KEYWORD)     private String brandName;     private Long productCategoryId;     @IndexField(fieldType = FieldType.KEYWORD)     private String productCategoryName;     private String pic;     @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")     private String name;     @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")     private String subTitle;     @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")     private String keywords;     private BigDecimal price;     private Integer sale;     private Integer newStatus;     private Integer recommandStatus;     private Integer stock;     private Integer promotionType;     private Integer sort;     @IndexField(fieldType = FieldType.NESTED, nestedClass = EsProductAttributeValue.class)     private List<EsProductAttributeValue> attrValueList;     @Score     private Float score; }  /**  * 嵌套类型EsProductAttributeValue  * 搜索商品的属性信息  */ @Data @EqualsAndHashCode public class EsProductAttributeValue implements Serializable {     private static final long serialVersionUID = 1L;     @IndexField(fieldType = FieldType.LONG)     private Long id;     @IndexField(fieldType = FieldType.KEYWORD)     private Long productAttributeId;     //属性值     @IndexField(fieldType = FieldType.KEYWORD)     private String value;     //属性参数:0->规格;1->参数     @IndexField(fieldType = FieldType.INTEGER)     private Integer type;     //属性名称     @IndexField(fieldType=FieldType.KEYWORD)     private String name; } EsProduct 中的注解具体说明如下:  @IndexName:索引名注解,value是指定索引名;shardsNum:分片数;replicasNum:副本数 @IndexId:ES主键注解,type 指定注解类型,CUSTOMIZE 表示自定义 @IndexField:ES字段注解,fieldType 字段在索引中的类型,analyzer 索引文档时用的分词器,nestedClass 嵌套类 @Score:得分注解 decimalPlaces 得分保留小数位,实体类中被作为 ES 查询得分返回的字段使用 1.4.2 EsMapper接口 下面我们来实现几个简单的商品信息维护接口,包括商品信息的导入、创建和删除。 首先我们需要定义一个Mapper,继承BaseEsMapper;  /**  * 商品ES操作类  */ public interface EsProductMapper extends BaseEsMapper<EsProduct> {  } 然后在Service实现类中直接使用EsProductMapper内置方法实现即可  /**  * 搜索商品管理Service实现类  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductDao productDao;     @Autowired     private EsProductMapper esProductMapper;     @Override     public int importAll() {         List<EsProduct> esProductList = productDao.getAllEsProductList(null);         return esProductMapper.insertBatch(esProductList);     }      @Override     public void delete(Long id) {         esProductMapper.deleteById(id);     }      @Override     public EsProduct create(Long id) {         EsProduct result = null;         List<EsProduct> esProductList = productDao.getAllEsProductList(id);         if (esProductList.size() > 0) {             result = esProductList.get(0);             esProductMapper.insert(result);         }         return result;     }      @Override     public void delete(List<Long> ids) {         if (!CollectionUtils.isEmpty(ids)) {             esProductMapper.deleteBatchIds(ids);         }     } } 1.4.3 简单搜索 下面我们来实现一个最简单的商品搜索,分页搜索商品名称、副标题、关键词中包含指定关键字的商品。 通过QueryWrapper来构造查询条件,然后使用Mapper中的方法来进行查询,使用过MyBatis-Plus的小伙伴应该很熟悉了 /**  * 搜索商品管理Service实现类  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public PageInfo<EsProduct> search(String keyword, Integer pageNum, Integer pageSize) {         LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();         if(StrUtil.isEmpty(keyword)){             wrapper.matchAllQuery();         }else{             wrapper.multiMatchQuery(keyword,EsProduct::getName,EsProduct::getSubTitle,EsProduct::getKeywords);         }         return esProductMapper.pageQuery(wrapper, pageNum, pageSize);     } } 在控制台输出查看生成的DSL语句 把DSL语句直接复制到 Kibana 中即可执行查看结果了,这和我们手写DSL语句没什么两样的。 1.5 使用案例 1.5.1 综合商品搜索 下面我们来实现一个复杂的商品搜索,涉及到过滤、不同字段匹配权重不同以及可以进行排序。 首先来说需求,按输入的关键字搜索商品名称(权重10)、副标题(权重5)和关键词(权重2),可以按品牌和分类进行筛选,可以有5种排序方式,默认按相关度进行排序 下面是使用Easy-Es的实现方式 /**  * 搜索商品管理Service实现类  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public PageInfo<EsProduct> search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize,Integer sort) {         LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();         //过滤         if (brandId != null || productCategoryId != null) {             if (brandId != null) {                 wrapper.eq(EsProduct::getBrandId,brandId);             }             if (productCategoryId != null) {                 wrapper.eq(EsProduct::getProductCategoryId,productCategoryId).enableMust2Filter(true);             }         }         //搜索         if (StrUtil.isEmpty(keyword)) {             wrapper.matchAllQuery();         } else {             wrapper.and(i -> i.match(EsProduct::getName, keyword, 10f)                     .or().match(EsProduct::getSubTitle, keyword, 5f)                     .or().match(EsProduct::getKeywords, keyword, 2f));         }         //排序         if(sort==1){             //按新品从新到旧             wrapper.orderByDesc(EsProduct::getId);         }else if(sort==2){             //按销量从高到低             wrapper.orderByDesc(EsProduct::getSale);         }else if(sort==3){             //按价格从低到高             wrapper.orderByAsc(EsProduct::getPrice);         }else if(sort==4){             //按价格从高到低             wrapper.orderByDesc(EsProduct::getPrice);         }else{             //按相关度             wrapper.sortByScore(SortOrder.DESC);         }         return esProductMapper.pageQuery(wrapper, pageNum, pageSize);     } } 1.5.2 相关商品推荐 当我们查看相关商品的时候,一般底部会有一些商品推荐,这里简单来实现下。  首先来说下需求,可以根据指定商品的ID来查找相关商品 这里我们的实现原理是这样的:首先根据ID获取指定商品信息,然后以指定商品的名称、品牌和分类来搜索商品,并且要过滤掉当前商品,调整搜索条件中的权重以获取最好的匹配度;  /**  * 搜索商品管理Service实现类  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public PageInfo<EsProduct> recommend(Long id, Integer pageNum, Integer pageSize) {         LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();         List<EsProduct> esProductList = productDao.getAllEsProductList(id);         if (esProductList.size() > 0) {             EsProduct esProduct = esProductList.get(0);             String keyword = esProduct.getName();             Long brandId = esProduct.getBrandId();             Long productCategoryId = esProduct.getProductCategoryId();             //用于过滤掉相同的商品             wrapper.ne(EsProduct::getId,id);             //根据商品标题、品牌、分类进行搜索             wrapper.and(i -> i.match(EsProduct::getName, keyword, 8f)                     .or().match(EsProduct::getSubTitle, keyword, 2f)                     .or().match(EsProduct::getKeywords, keyword, 2f)                     .or().match(EsProduct::getBrandId, brandId, 5f)                     .or().match(EsProduct::getProductCategoryId, productCategoryId, 3f));             return esProductMapper.pageQuery(wrapper, pageNum, pageSize);         }         return esProductMapper.pageQuery(wrapper, pageNum, pageSize);     } } 1.5.3 聚合搜索商品相关信息 在搜索商品时,经常会有一个筛选界面来帮助我们找到想要的商品,这里我们来简单实现下。 首先来说下需求,可以根据搜索关键字获取到与关键字匹配商品相关的分类、品牌以及属性 这里我们可以使用ES的聚合来实现,搜索出相关商品,聚合出商品的品牌、商品的分类以及商品的属性,只要出现次数最多的前十个即可;  由于Easy-Es目前只用groupBy实现了简单的聚合,对于我们这种有嵌套对象的聚合无法支持,所以需要使用RestHighLevelClient来实现,如果对照之前的Spring Data实现方式的话,可以发现用法差不多,看样子Spring Data只是做了简单的封装而已。  /**  * 搜索商品管理Service实现类  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public EsProductRelatedInfo searchRelatedInfo(String keyword) {         SearchRequest searchRequest = new SearchRequest();         searchRequest.indices("pms_*");         SearchSourceBuilder builder = new SearchSourceBuilder();         //搜索条件         if (StrUtil.isEmpty(keyword)) {             builder.query(QueryBuilders.matchAllQuery());         } else {             builder.query(QueryBuilders.multiMatchQuery(keyword, "name", "subTitle", "keywords"));         }         //聚合搜索品牌名称         builder.aggregation(AggregationBuilders.terms("brandNames").field("brandName"));         //集合搜索分类名称         builder.aggregation(AggregationBuilders.terms("productCategoryNames").field("productCategoryName"));         //聚合搜索商品属性,去除type=1的属性         AbstractAggregationBuilder<NestedAggregationBuilder> aggregationBuilder = AggregationBuilders.nested("allAttrValues", "attrValueList")                 .subAggregation(AggregationBuilders.filter("productAttrs", QueryBuilders.termQuery("attrValueList.type", 1))                         .subAggregation(AggregationBuilders.terms("attrIds")                                 .field("attrValueList.productAttributeId")                                 .subAggregation(AggregationBuilders.terms("attrValues")                                         .field("attrValueList.value"))                                 .subAggregation(AggregationBuilders.terms("attrNames")                                         .field("attrValueList.name"))));         builder.aggregation(aggregationBuilder);         searchRequest.source(builder);         try {             SearchResponse searchResponse = esProductMapper.search(searchRequest, RequestOptions.DEFAULT);             return convertProductRelatedInfo(searchResponse);         } catch (IOException e) {             e.printStackTrace();         }         return null;     }      /**      * 将返回结果转换为对象      */     private EsProductRelatedInfo convertProductRelatedInfo(SearchResponse response) {         EsProductRelatedInfo productRelatedInfo = new EsProductRelatedInfo();         Map<String, Aggregation> aggregationMap = response.getAggregations().asMap();         //设置品牌         Aggregation brandNames = aggregationMap.get("brandNames");         List<String> brandNameList = new ArrayList<>();         for(int i = 0; i<((Terms) brandNames).getBuckets().size(); i++){             brandNameList.add(((Terms) brandNames).getBuckets().get(i).getKeyAsString());         }         productRelatedInfo.setBrandNames(brandNameList);         //设置分类         Aggregation productCategoryNames = aggregationMap.get("productCategoryNames");         List<String> productCategoryNameList = new ArrayList<>();         for(int i=0;i<((Terms) productCategoryNames).getBuckets().size();i++){             productCategoryNameList.add(((Terms) productCategoryNames).getBuckets().get(i).getKeyAsString());         }         productRelatedInfo.setProductCategoryNames(productCategoryNameList);         //设置参数         Aggregation productAttrs = aggregationMap.get("allAttrValues");         List<? extends Terms.Bucket> attrIds = ((ParsedStringTerms) ((ParsedFilter) ((ParsedNested) productAttrs).getAggregations().get("productAttrs")).getAggregations().get("attrIds")).getBuckets();         List<EsProductRelatedInfo.ProductAttr> attrList = new ArrayList<>();         for (Terms.Bucket attrId : attrIds) {             EsProductRelatedInfo.ProductAttr attr = new EsProductRelatedInfo.ProductAttr();             attr.setAttrId(Long.parseLong((String) attrId.getKey()));             List<String> attrValueList = new ArrayList<>();             List<? extends Terms.Bucket> attrValues = ((ParsedStringTerms) attrId.getAggregations().get("attrValues")).getBuckets();             List<? extends Terms.Bucket> attrNames = ((ParsedStringTerms) attrId.getAggregations().get("attrNames")).getBuckets();             for (Terms.Bucket attrValue : attrValues) {                 attrValueList.add(attrValue.getKeyAsString());             }             attr.setAttrValues(attrValueList);             if(!CollectionUtils.isEmpty(attrNames)){                 String attrName = attrNames.get(0).getKeyAsString();                 attr.setAttrName(attrName);             }             attrList.add(attr);         }         productRelatedInfo.setProductAttrs(attrList);         return productRelatedInfo;     } } 使用 Easy-Es 确实简单,但是对于复杂的聚合搜索功能,需要使用原生的 RestHighLevelClient 用法来实现。使用Easy-Es来操作ES确实足够优雅,它类似MyBatis-Plus的用法能大大降低我们的学习成本 ————————————————          原文链接:https://blog.csdn.net/u012060033/article/details/135918347 
  • [技术干货] SpringBoot整合Easy-Es
    目录 一、什么是Easy-Es  二、使用场景 2.1 检索类服务 2.2  问答类服务(本质上也是检索类)   2.3 地图类服务 三、spring boot整合 Easy-Es 3.1 pom.xml 3.2 配置文件 3.3 创建、删除、查询索引 3.4 创建一个实体类 3.5 新建Mapper类,类似Mybatis的dao 3.6 启动类扫描 dao 四、代码展示 五、 原生Api调用 5.1查看索引mapping关系 5.2查看某个文档,具体字段的分词  一、什么是Easy-Es     Easy-Es(简称EE)是一款基于ElasticSearch(简称Es)官方提供的RestHighLevelClient打造的ORM开发框架,在 RestHighLevelClient 的基础上,只做增强不做改变,为简化开发、提高效率而生,您如果有用过Mybatis-Plus(简称MP),那么您基本可以零学习成本直接上手EE,EE是MP的Es平替版,在有些方面甚至比MP更简单,同时也融入了更多Es独有的功能,助力您快速实现各种场景的开发.   二、使用场景 2.1 检索类服务 搜索文库 电商商品检索 海量系统日志检索 2.2  问答类服务(本质上也是检索类)  在线智能客服 机器人  2.3 地图类服务 打车app 外卖app 社区团购配送 陌生人社交  2.4 官网  https://www.easy-es.cn/  三、spring boot整合 Easy-Es 3.1 pom.xml 其中easy-es-boot-starter 安装最新版,可以复制easy-es-boot-starter到maven中央仓库搜索   <dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>             <exclusions>                 <exclusion>                     <groupId>org.elasticsearch.client</groupId>                     <artifactId>elasticsearch-rest-high-level-client</artifactId>                 </exclusion>                 <exclusion>                     <groupId>org.elasticsearch</groupId>                     <artifactId>elasticsearch</artifactId>                 </exclusion>             </exclusions>         </dependency>           <dependency>             <groupId>cn.easy-es</groupId>             <artifactId>easy-es-boot-starter</artifactId>             <version>2.0.0-beta1</version>         </dependency>         <dependency>             <groupId>org.elasticsearch.client</groupId>             <artifactId>elasticsearch-rest-high-level-client</artifactId>             <version>7.14.0</version>         </dependency>         <dependency>             <groupId>org.elasticsearch</groupId>             <artifactId>elasticsearch</artifactId>             <version>7.14.0</version>         </dependency>       </dependencies>       <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>             </plugin>         </plugins>     </build>  3.2 配置文件 如果有账号密码自行加上  # Easy-Es配置部分 easy-es:   # 启用Easy-Es功能   enable: true   # 设置Elasticsearch服务器地址和端口   address: 192.168.23.27:9200   # 全局配置项,设置是否打印执行的DSL语句(便于调试)   global-config:     print-dsl: true Elasticsearch DSL (Domain Specific Language) 是一种专门设计用来与Elasticsearch搜索引擎进行交互的查询语言。它是一种基于JSON格式的查询语法,允许用户以结构化的方式来构建复杂的查询、过滤条件、聚合操作以及其他高级功能。  通过Elasticsearch DSL,开发人员可以灵活且高效地执行各种查询操作,包括全文本搜索、精确匹配、范围查询、布尔组合查询、排序、分页、高亮显示文本、统计计算、地理位置查询以及复杂的聚合分析等。  例如,一个简单的Elasticsearch DSL查询语句可能是查找索引my_index中field1字段包含关键词value1的文档  3.3 创建、删除、查询索引  分词&模糊匹配 | Easy-Es  {   "query": {     "match": {       "field1": "value1"     }   } } 3.4 创建一个实体类 @IndexName( aliasName = "es_product") public class EsProduct {       @IndexId(type = IdType.CUSTOMIZE)     private Integer id;     @IndexField( fieldType= FieldType.TEXT, analyzer = Analyzer.IK_MAX_WORD,searchAnalyzer = Analyzer.IK_MAX_WORD)     private String name;     @IndexField( fieldType= FieldType.INTEGER)     private Integer categoryId;     @IndexField( fieldType= FieldType.DOUBLE) // 12.56     private BigDecimal price;     @IndexField( fieldType= FieldType.TEXT, analyzer = Analyzer.IK_MAX_WORD)     private String brief;     @IndexField( fieldType= FieldType.KEYWORD)     private String img;     @IndexField( fieldType= FieldType.TEXT, analyzer = Analyzer.IK_MAX_WORD)     private List<String> tags;     @IndexField( fieldType= FieldType.INTEGER) //198     private Integer highOpinion;     @IndexField( fieldType= FieldType.INTEGER)     private Integer salesVolume;     @IndexField( fieldType= FieldType.DATE)     private LocalDateTime productionDate; }  3.5 新建Mapper类,类似Mybatis的dao   package com.by.dao;     import cn.easyes.core.core.BaseEsMapper; import com.by.model.EsProduct;   public interface EsProductMapper extends BaseEsMapper<EsProduct> {   } 3.6 启动类扫描 dao   @SpringBootApplication @EsMapperScan("com.by.dao") public class App {     public static void main(String[] args) {         SpringApplication.run(App.class, args);     }   } 四、代码展示    @Test     void insert() {         EsProduct esProduct = EsProduct.builder()                 .id(1)                 .name("小米")                 .img("图片地址")                 .brief("小米(MI)Redmi Note12 5G 120Hz OLED屏幕 骁龙4移动平台 5000mAh长续航 8GB+128GB子夜黑 小米红米")                 .price(new BigDecimal(18))                 .categoryId(1)                 .highOpinion(20)                 .productionDate(LocalDateTime.now())                 .salesVolume(99)                 .tags(CollUtil.newArrayList("120高刷","舒适护眼"))                 .build();         esMapper.insert(esProduct);     }     @Test     void insert2() {         delete();         List<EsProduct> esProducts = CollUtil.newArrayList();         for (int i = 1; i <= 10; i++) {             EsProduct esProduct = EsProduct.builder()                     .id(i)                     .name("小米"+i)                     .img("图片地址"+i)                     .brief("小米(MI)Redmi Note"+i+" 5G 120Hz OLED屏幕 骁龙4移动平台 5000mAh长续航 8GB+128GB子夜黑 小米红米")                     .price(new BigDecimal(500.36+i))                     .categoryId(1)                     .highOpinion(100+i)                     .productionDate(LocalDateTime.now())                     .salesVolume(200+i)                     .tags(CollUtil.newArrayList("12"+i+"高刷","舒适护眼"))                     .build();             esProducts.add(esProduct);                 }           esMapper.insertBatch(esProducts);     }       @Test     void update() {         EsProduct esProduct = EsProduct.builder()                 .id(1)                 .name("su7")                 .img("图片地址")                 .brief("小米汽车")                 .price(new BigDecimal(18))                 .categoryId(9)                 .highOpinion(20)                 .productionDate(LocalDateTime.now())                 .salesVolume(99)                 .tags(CollUtil.newArrayList("120高刷","舒适护眼"))                 .build();         Integer integer = esMapper.updateById(esProduct);     }     @Test     void delete() {         Integer batchIds = esMapper.deleteBatchIds(CollUtil.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));     }       @Test     void select() {         LambdaEsQueryWrapper  queryWrapper = new LambdaEsQueryWrapper<EsProduct>();         queryWrapper.eq("id",1);         List list = esMapper.selectList(queryWrapper);     }     @Test     void select2() {         LambdaEsQueryWrapper  queryWrapper = new LambdaEsQueryWrapper<EsProduct>();         queryWrapper.queryStringQuery("汽车之家"); //所有字段都去匹配         List list = esMapper.selectList(queryWrapper);     }     @Test     void select3() {         LambdaEsQueryWrapper  queryWrapper = new LambdaEsQueryWrapper<EsProduct>();         queryWrapper.eq("categoryId",1);         List list = esMapper.selectList(queryWrapper);     }     @Test     void select4() {         LambdaEsQueryWrapper<EsProduct>  queryWrapper = new LambdaEsQueryWrapper<>();         queryWrapper.in(EsProduct::getCategoryId,1,9);                List list = esMapper.selectList(queryWrapper);     }       @Test     void select5() {         LambdaEsQueryWrapper<EsProduct>  queryWrapper = new LambdaEsQueryWrapper<>();         //queryWrapper.match(EsProduct::getBrief,"汽车",1.0F);         //queryWrapper.match(EsProduct::getName,"汽车",1.0F);         //queryWrapper.multiMatchQuery("汽车", Operator.OR, EsProduct::getName,EsProduct::getBrief);         queryWrapper.in("categoryId",1,9); //where categroyId in (1,9) and ( name like '%汽车%' or brief like '%汽车%')         queryWrapper.and(                 w->w.match(EsProduct::getBrief,"汽车",1.0F)                         .or().match(EsProduct::getName,"汽车",2.0F));         List list = esMapper.selectList(queryWrapper);         String dsl = esMapper.getSource(queryWrapper);         System.out.println(dsl);     }     @Test     void select6() {         LambdaEsQueryWrapper<EsProduct>  queryWrapper = new LambdaEsQueryWrapper<>();           queryWrapper.in("categoryId",1,9); //where categroyId in (1,9) and ( name like '%汽车%' or brief like '%汽车%')         queryWrapper.and(                 w->w.match(EsProduct::getBrief,"高刷",1.0F)                         .or().match(EsProduct::getName,"高刷",2.0F)                         .or().match(EsProduct::getTags,"高刷",1.0F));         queryWrapper.orderByDesc(EsProduct::getSalesVolume);         EsPageInfo<EsProduct> esProductEsPageInfo = esMapper.pageQuery(queryWrapper, 2, 3);           String dsl = esMapper.getSource(queryWrapper);         System.out.println(dsl);     }       @Test     void select7() {         LambdaEsQueryWrapper<EsProduct>  queryWrapper = new LambdaEsQueryWrapper<>();             queryWrapper.match(EsProduct::getName,"水汽车门");         List<EsProduct> esProducts = esMapper.selectList(queryWrapper);         }  五、 原生Api调用 5.1查看索引mapping关系 GET /es_product/_mapping  5.2查看某个文档,具体字段的分词 GET /product/_doc/2/_termvectors?fields=brief ————————————————               原文链接:https://blog.csdn.net/qq_65142821/article/details/137908103 
  • [技术干货] Spring Boot对调用REST服务的支持
    Spring Boot 提供了多种方便的调用远程 REST 服务的方法。如果你正在开发一个非阻塞的反应式应用程序并且使用的是 Spring WebFlux,那么可以使用 WebClient。如果你更喜欢阻塞式 API,那么可以使用 RestClient 或 RestTemplate。 WebClient 如果你的类路径中有 Spring WebFlux,建议使用 WebClient 来调用远程 REST 服务。WebClient 接口提供了函数式风格的 API,并且完全是反应式的。 提示:如果没有编写反应式的 Spring WebFlux 应用程序,可以使用 RestClient 而不是 WebClient。这提供了一个类似的功能性 API,但它是阻塞式的,而不是反应式的。 Spring Boot 会为你创建并预先配置一个原型 WebClient.Builder bean。强烈建议将其注入到组件中,并使用它来创建 WebClient 实例。Spring Boot 配置了该构建器以共享 HTTP 资源,并以与服务器设置相同的方式反映编解码器设置,等等。  以下代码展示了一个典型的示例:  import reactor.core.publisher.Mono;  import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient;  @Service public class MyService {      private final WebClient webClient;      public MyService(WebClient.Builder webClientBuilder) {         this.webClient = webClientBuilder.baseUrl("https://example.org").build();     }      public Mono<Details> someRestCall(String name) {         return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);     }  }  WebClient 运行时 Spring Boot 将根据应用程序类路径上可用的库自动检测要使用哪个 ClientHttpConnector 来驱动 WebClient。按照优先级顺序,支持以下客户端:  Reactor Netty Jetty RS 客户端 Apache HttpClient JDK HttpClient 如果类路径上存在多个客户端,将使用首选的客户端。  spring-boot-starter-webflux starter 默认依赖于 io.projectreactor.netty:reactor-netty,该依赖同时提供了服务器和客户端的实现。如果选择使用 Jetty 作为反应式服务器,应该添加对 Jetty Reactive HTTP 客户端库 org.eclipse.jetty:jetty-reactive-httpclient 的依赖。使用相同的技术来构建服务器和客户端有其优势,因为它会自动在客户端和服务器之间共享 HTTP 资源。  开发人员可以通过提供一个自定义的 ReactorResourceFactory 或 JettyResourceFactory bean 来覆盖 Jetty 和 Reactor Netty 的资源配置——这将对客户端和服务器都生效。  如果希望覆盖客户端的选择,可以定义自己的 ClientHttpConnector bean,并完全控制客户端的配置。  WebClient 自定义 根据你希望自定义的适用范围有多广泛,WebClient 自定义主要有三种方法。  要使任何自定义的范围尽可能狭窄,请注入自动配置的 WebClient.Builder,然后按需调用其方法。WebClient.Builder 实例是有状态的:对构建器的任何更改都会反映在使用该构建器随后创建的所有客户端中。如果想使用相同的构建器创建多个客户端,还可以考虑使用 WebClient.Builder other = builder.clone(); 对构建器进行克隆。  要对所有 WebClient.Builder 实例进行全局、附加的自定义,可以声明 WebClientCustomizer bean,并在注入点本地更改 WebClient.Builder。  最后,可以回退到原始 API 并使用 WebClient.create()。在这种情况下,不会应用任何自动配置或 WebClientCustomizer。  WebClient SSL支持 如果你需要对 WebClient 所使用的 ClientHttpConnector 进行自定义的 SSL 配置,你可以注入一个 WebClientSsl 实例,该实例可以与构建器的 apply 方法一起使用。  WebClientSsl 接口提供了对你在 application.properties 或 application.yaml 文件中定义的任何 SSL bundle 的访问。  以下代码展示了一个典型的例子:  import reactor.core.publisher.Mono;  import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientSsl; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient;  @Service public class MyService {      private final WebClient webClient;      public MyService(WebClient.Builder webClientBuilder, WebClientSsl ssl) {         this.webClient = webClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();     }      public Mono<Details> someRestCall(String name) {         return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);     }  }  RestClient 如果你的应用程序中未使用 Spring WebFlux 或 Project Reactor,建议使用 RestClient 来调用远程 REST 服务。  RestClient 接口提供了一个功能性的阻塞式 API。  Spring Boot 会为你创建并预先配置一个原型 RestClient.Builder bean。强烈建议在组件中注入它,并使用它来创建 RestClient 实例。Spring Boot 会为该构建器配置 HttpMessageConverters 和适当的 ClientHttpRequestFactory。  以下代码展示了一个典型的例子:  import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient;  @Service public class MyService {      private final RestClient restClient;      public MyService(RestClient.Builder restClientBuilder) {         this.restClient = restClientBuilder.baseUrl("https://example.org").build();     }      public Details someRestCall(String name) {         return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);     }  }  RestClient 自定义 RestClient 的自定义主要有三种方法,具体取决于你希望自定义的适用范围有多广泛。  要使任何自定义的范围尽可能狭窄,请注入自动配置的 RestClient.Builder,然后按需调用其方法。RestClient.Builder 实例是有状态的:对构建器的任何更改都会反映在使用该构建器随后创建的所有客户端中。如果想使用相同的构建器创建多个客户端,还可以考虑使用 RestClient.Builder other = builder.clone(); 对构建器进行克隆。  要对所有 RestClient.Builder 实例进行全局、附加的自定义,可以声明 RestClientCustomizer bean,并在注入点本地更改 RestClient.Builder。  最后,可以回退到原始 API 并使用 RestClient.create()。在这种情况下,不会应用任何自动配置或 RestClientCustomizer。  RestClient SSL支持 如果你需要对 RestClient 所使用的 ClientHttpRequestFactory 进行自定义的 SSL 配置,你可以注入一个 RestClientSsl 实例,该实例可以与构建器的 apply 方法一起使用。  RestClientSsl 接口提供了对你在 application.properties 或 application.yaml 文件中定义的任何 SSL bundle 的访问。  以下代码展示了一个典型的例子:  import org.springframework.boot.autoconfigure.web.client.RestClientSsl; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient;  @Service public class MyService {      private final RestClient restClient;      public MyService(RestClient.Builder restClientBuilder, RestClientSsl ssl) {         this.restClient = restClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();     }      public Details someRestCall(String name) {         return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);     }  }  如果你需要在 SSL bundle 之外应用其它自定义设置,你可以使用 ClientHttpRequestFactorySettings 类与 ClientHttpRequestFactories 配合使用:  import java.time.Duration;  import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.web.client.ClientHttpRequestFactories; import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient;  @Service public class MyService {      private final RestClient restClient;      public MyService(RestClient.Builder restClientBuilder, SslBundles sslBundles) {         ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS             .withReadTimeout(Duration.ofMinutes(2))             .withSslBundle(sslBundles.getBundle("mybundle"));         ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings);         this.restClient = restClientBuilder.baseUrl("https://example.org").requestFactory(requestFactory).build();     }      public Details someRestCall(String name) {         return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);     }  } RestTemplate Spring Framework 的 RestTemplate 类早于 RestClient,是许多应用程序用于调用远程 REST 服务的经典方式。当你有不想迁移到 RestClient 的现有代码,或者因为你已经熟悉 RestTemplate API 时,你可能会选择使用 RestTemplate。  由于 RestTemplate 实例在使用前通常需要进行自定义,因此 Spring Boot 不提供任何单个自动配置的 RestTemplate bean。但是,它确实自动配置了一个 RestTemplateBuilder,可以在需要时用于创建 RestTemplate 实例。自动配置的 RestTemplateBuilder 确保将合理的 HttpMessageConverters 和适当的 ClientHttpRequestFactory 应用到 RestTemplate 实例上。  以下代码展示了一个典型的例子:  import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate;  @Service public class MyService {      private final RestTemplate restTemplate;      public MyService(RestTemplateBuilder restTemplateBuilder) {         this.restTemplate = restTemplateBuilder.build();     }      public Details someRestCall(String name) {         return this.restTemplate.getForObject("/{name}/details", Details.class, name);     }  }  RestTemplateBuilder 包含许多有用的方法,可用于快速配置 RestTemplate。例如,要添加基本认证(BASIC authentication)支持,你可以使用 builder.basicAuthentication("user", "password").build()。  RestTemplate 自定义 RestTemplate 的自定义主要有三种方法,具体取决于你希望自定义的适用范围有多广泛。  要使任何自定义的范围尽可能狭窄,请注入自动配置的 RestTemplateBuilder,然后按需调用其方法。每次方法调用都会返回一个新的 RestTemplateBuilder 实例,因此这些自定义只影响该构建器的使用。  要进行全局的、附加的自定义,请使用 RestTemplateCustomizer bean。所有这样的 bean 都会自动注册到自动配置的 RestTemplateBuilder 中,并应用于使用它构建的任何模板。  以下示例展示了一个自定义器,它配置了对除 192.168.0.5 之外的所有主机使用代理:  import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner; import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.protocol.HttpContext;  import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate;  public class MyRestTemplateCustomizer implements RestTemplateCustomizer {      @Override     public void customize(RestTemplate restTemplate) {         HttpRoutePlanner routePlanner = new CustomRoutePlanner(new HttpHost("proxy.example.com"));         HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build();         restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));     }      static class CustomRoutePlanner extends DefaultProxyRoutePlanner {          CustomRoutePlanner(HttpHost proxy) {             super(proxy);         }          @Override         protected HttpHost determineProxy(HttpHost target, HttpContext context) throws HttpException {             if (target.getHostName().equals("192.168.0.5")) {                 return null;             }             return super.determineProxy(target, context);         }      }  } 置所做的那样,可以使用 RestTemplateBuilderConfigurer 进行配置。以下示例展示了一个 RestTemplateBuilder,它与 Spring Boot 的自动配置所做的相匹配,但还指定了自定义的连接和读取超时:  import java.time.Duration;  import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  @Configuration(proxyBeanMethods = false) public class MyRestTemplateBuilderConfiguration {      @Bean     public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {         return configurer.configure(new RestTemplateBuilder())             .setConnectTimeout(Duration.ofSeconds(5))             .setReadTimeout(Duration.ofSeconds(2));     }  }  最极端(且很少使用)的选项是创建自己的 RestTemplateBuilder bean,而不使用配置器。除了替换自动配置的构建器外,这还阻止了任何 RestTemplateCustomizer bean 的使用。  RestTemplate SSL支持 如果你需要在 RestTemplate 上进行自定义的 SSL 配置,你可以像这个示例中所示,将 SSL bundle 应用到 RestTemplateBuilder 上:  import org.springframework.boot.docs.io.restclient.resttemplate.Details; import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate;  @Service public class MyService {      private final RestTemplate restTemplate;      public MyService(RestTemplateBuilder restTemplateBuilder, SslBundles sslBundles) {         this.restTemplate = restTemplateBuilder.setSslBundle(sslBundles.getBundle("mybundle")).build();     }      public Details someRestCall(String name) {         return this.restTemplate.getForObject("/{name}/details", Details.class, name);     }  }   RestClient和RestTemplate的HTTP客户端检测 Spring Boot会根据应用程序类路径上可用的库自动检测使用RestClient和RestTemplate的HTTP客户端。按照优先级顺序,支持以下客户端:  Apache HttpClient Jetty HttpClient OkHttp(已弃用) 简单的JDK客户端(HttpURLConnection) 如果类路径上存在多个客户端,将使用最优先的客户端。 ————————————————  原文链接:https://blog.csdn.net/panghuangang/article/details/136156769 
  • [技术干货] springboot系列八: springboot静态资源访问,Rest风格请求处理
    在本篇文章中,我们将探讨如何在 Spring Boot 中处理静态资源访问、实现 Rest 风格的请求处理以及使用接收参数相关的注解。这些功能将帮助您更高效地开发和管理 Spring Boot 应用程序。 本篇需要用到的项目:  WEB开发-静态资源访问 官方文档 在线文档: 基本介绍 1.只要静态资源放在类路径下: /static, /public, /resources, /META-INF/resources 可以被直接访问 - 对应文件 WebProperties.java private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { “classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, “classpath:/public/” }; 2.常见静态资源: JS, CSS, 图片(.jpg, .png, .gif, .bmp, .svg), 字体文件(Fonts)等 3.访问方式: 默认: 项目根路径 / + 静态资源名, 比如 http://localhost:8080/1.jpg - 设置WebMvcProperties.java private String staticPathPattern = “/**”; 快速入门 1.创建SpringBoot项目springbootweb, 这里使用灵活配置方式来创建项目, 参考springboot快速入门 2.创建相关静态资源目录, 并放入测试图片, 没有目录, 自己创建即可, 浏览器访问 http://localhost:8080/4.png , 完成测试. 注意事项和细节 1.静态资源访问原理: 静态映射是 /** , 也就是对所有请求拦截. 请求进来, 先看Controller能不能处理, 不能处理的请求交给静态资源处理器, 如果静态资源找不到则响应404页面. 2.改变静态资源访问前缀, 比如我们希望 http://localhost:8080/image/1.png 去请求静态资源. 应用场景: http://localhost:8080/1.png, 静态资源访问前缀和控制器请求路径冲突 被Controller拦截 解决方案: 1)创建src/main/resources/application.yml spring:   mvc:     static-path-pattern: /image/** 2)启动, http://localhost:8080/image/1.png 测试 3.改变默认的静态资源路径, 比如希望在类路径下增加zzwimg目录 作为静态资源路径, 并完成测试. 1)如图所示 2)配置 application.yml, 增加路径 spring:   mvc:     static-path-pattern: /image/** #修改静态资源访问 路径/前缀   web:     resources:       #修改/指定 静态资源的访问路径/位置       static-locations: [classpath:/zzwimg/] #String[] staticLocations 3)测试, 浏览器输入 http://localhost:8080/image/6.png(没错, 是image/6.png, 不是image/zzwimg/6.png). 还有, 一定要保证工作目录target下 有 6.png, 如果没有, 请rebuild下项目, 再重启项目. 4)如果你配置 static-locations, 原来的访问路径就会被覆盖, 如果需要保留, 再指定一下即可.    web:     resources:       #修改/指定 静态资源的访问路径/位置 String[] staticLocations       static-locations: ["classpath:/zzwimg/", "classpath:/META-INF/resources/",                          "classpath:/resources/", "classpath:/static/", "classpath:/public/"] Rest风格请求处理 基本介绍 1.Rest风格支持 (使用HTTP请求方式来表示对资源的操作)  2.举例说明 请求方式: /monster GET-获取妖怪 DELETE-删除妖怪 PUT-修改妖怪 POST-保存妖怪  应用实例 1.创建src/main/java/com/zzw/springboot/controller/MonsterController.java  @RestController public class MonsterController {      //等价的写法     //@RequestMapping(value = "/monster", method= RequestMethod.GET)     @GetMapping("/monster")     public String getMonster() {         return "GET-查询妖怪";     }      //等价写法     //@RequestMapping(value = "/monster", method = RequestMethod.POST)     @PostMapping("/monster")     public String saveMonster() {         return "POST-保存妖怪";     }      //等价写法     @RequestMapping(value = "/monster", method = RequestMethod.PUT)     @PutMapping("/monster")     public String putMonster() {         return "PUT-修改妖怪";     }      //等价写法     @RequestMapping(value = "/monster", method = RequestMethod.DELETE)     @DeleteMapping("/monster")     public String deleteMonster() {         return "DELETE-删除妖怪";     } }  2.使用Postman完成测试, 请求url: http://localhost:8080/monster 注意事项和细节 在SpringMVC中我们学过,SpringMVC系列四: Rest-优雅的url请求风格 1.客户端是Postman 可以直接发送 Put, Delete等方式请求, 可不设置Filter. 2.如果要SpringBoot支持 页面表单的Rest功能, 则需要注意如下细节 1)Rest风格请求核心Filter: HiddenHttpMethodFilter, 表单请求会被HiddenHttpMethodFilter拦截, 获取到表单_method的值, 再判断是PUT/DELETE/PATCH(注意: PATCH方法是新引入的, 是对PUT方法的补充, 用来对已知资源进行局部更新, PATCH和PUT方法的区别)  2)如果要SpringBoot支持 页面表单的Rest功能, 需要在application.yml启用filter功能, 否则无效.  3)修改application.yml, 启用filter功能  spring:   mvc:     static-path-pattern: /image/** #修改静态资源访问 路径/前缀     hiddenmethod:       filter:         enabled: true #启动HiddenHttpMethodFilter, 支持rest 4)修改对应的页面, 然后测试.  <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>rest</title> </head> <body> <h1>测试rest风格的url, 完成get请求</h1> <form action="/monster" method="get">   u: <input type="text" name="name"><br/>   <input type="submit" value="点击提交"> </form> <h1>测试rest风格的url, 完成post请求</h1> <form action="/monster" method="post">   u: <input type="text" name="name"><br/>   <input type="submit" value="点击提交"> </form> <h1>测试rest风格的url, 完成put请求</h1> <form action="/monster" method="post">     <!--通过隐藏域传递_method参数指定值-->     <input type="hidden" name="_method" value="PUT">   u: <input type="text" name="name"><br/>   <input type="submit" value="点击提交"> </form> <h1>测试rest风格的url, 完成delete请求</h1> <form action="/monster" method="post">     <input type="hidden" name="_method" value="delete">   u: <input type="text" name="name"><br/>   <input type="submit" value="点击提交"> </form> </body> </html> 原文链接:https://blog.csdn.net/qq_18817831/article/details/139912069
  • [技术干货] Easy-Es笔记
    一、Easy-ES概述 Easy-Es(简称EE)是一款由国内开发者打造并完全开源的ElasticSearch-ORM框架。在原生 RestHighLevelClient 的基础上,只做增强不做改变,为简化开发、提高效率而生。Easy-Es采用和MP一致的语法设计,降低ElasticSearch搜索引擎使用门槛,和额外学习成本,并大幅减少开发者工作量,帮助企业降本提效。在有些方面甚至比MP更简单,同时也融入了更多ElasticSearch独有的功能,助力您快速实现各种场景的开发。(也可以理解为是操作es的mp)  官网地址:https://www.easy-es.cn/ 项目地址:https://gitee.com/dromara/easy-es/ 开源社区:https://gitee.com/dromara/ EE特性: 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 强大的 CRUD 操作:内置通用 Mapper,仅仅通过少量配置即可实现大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求。 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错段。 支持主自动生成:支持2 种主键策略,可自由配置,完美解决主键问题。 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作。 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )。 内置分页插件:基于RestHighLevelClient 物理分页,开发者无需关心具体操作,且无需额外配置插件,写分页等同于普通 List 查询,且保持和PageHelper插件同样的分页返回字段,无需担心命名影响。  MySQL功能全覆盖:MySQL中支持的功能通过EE都可以轻松实现。 支持ES高阶语法:支持高亮搜索,分词查询,权重查询,Geo地理位置查询,IP查询,聚合查询等高阶语法。 良好的拓展性:底层仍使用RestHighLevelClient,可保持其拓展性,开发者在使用EE的同时,仍可使用RestHighLevelClient的功能。  EE框架结构: EE与Spring Data ES技术对比: 二、Easy-ES快速入门 1、创建maven工程 2、引入依赖:  <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>      <!-- springboot工程 -->     <parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>2.4.5</version>         <relativePath/> <!-- lookup parent from repository -->     </parent>      <groupId>cn.aopmin</groupId>     <artifactId>EE-demo</artifactId>     <version>1.0-SNAPSHOT</version>      <properties>         <maven.compiler.source>8</maven.compiler.source>         <maven.compiler.target>8</maven.compiler.target>         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>         <!--es相对稳定版本,esay-es会依赖这个版本的jar包-->         <elasticsearch.version>7.14.0</elasticsearch.version>     </properties>      <dependencies>         <!-- web,排除springboot内置的es依赖,以防和easy-es中的依赖冲突 -->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>             <exclusions>                 <exclusion>                     <groupId>org.elasticsearch.client</groupId>                     <artifactId>elasticsearch-rest-high-level-client</artifactId>                 </exclusion>                 <exclusion>                     <groupId>org.elasticsearch</groupId>                     <artifactId>elasticsearch</artifactId>                 </exclusion>             </exclusions>         </dependency>          <!-- elasticsearch -->         <dependency>             <groupId>org.elasticsearch.client</groupId>             <artifactId>elasticsearch-rest-high-level-client</artifactId>             <version>${elasticsearch.version}</version>         </dependency>         <dependency>             <groupId>org.elasticsearch</groupId>             <artifactId>elasticsearch</artifactId>             <version>${elasticsearch.version}</version>         </dependency>          <!-- easy-es -->         <dependency>             <groupId>org.dromara.easy-es</groupId>             <artifactId>easy-es-boot-starter</artifactId>             <version>2.0.0-beta3</version>         </dependency>          <!-- junit -->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>             <scope>test</scope>         </dependency>     </dependencies>      <!--打包插件-->     <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>             </plugin>         </plugins>     </build>  </project> 3、创建启动类:  package cn.aopmin;  import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;  @SpringBootApplication public class EEApplication {     public static void main(String[] args) {         SpringApplication.run(EEApplication.class, args);     } } 4、在application.yml中配置easy-es:  easy-es:    address: 192.168.150.123:9200   # username: XXXX #如果es无账号密码则可不配置此行   # password: XXXX #如果es无账号密码则可不配置此行 5、创建实体类:  package cn.aopmin.pojo;  import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.dromara.easyes.annotation.IndexField; import org.dromara.easyes.annotation.IndexId; import org.dromara.easyes.annotation.IndexName; import org.dromara.easyes.annotation.rely.Analyzer; import org.dromara.easyes.annotation.rely.FieldType; import org.dromara.easyes.annotation.rely.IdType;  /**  * ES实体类  *  * @author 白豆五  * @version 2023/07/26  * @since JDK8  */ @Data @AllArgsConstructor @NoArgsConstructor @IndexName("document") // 指定索引名称 public class Document {      /**      * es中的唯一id      */     @IndexId(type = IdType.CUSTOMIZE) //id生成策略为自定义     private String id;      /**      * 文档标题      */     @IndexField( fieldType = FieldType.TEXT, analyzer = Analyzer.IK_MAX_WORD, searchAnalyzer = Analyzer.IK_SMART)     private String title;      /**      * 文档内容      */     @IndexField( fieldType = FieldType.KEYWORD)     private String content; } 6、创建mapper接口:  package cn.aopmin.mapper;  import cn.aopmin.pojo.Document; import org.dromara.easyes.core.core.BaseEsMapper;  /**  * Mapper接口  *  * @author 白豆五  * @version 2023/07/26  * @since JDK8  */ public interface DocumentMapper extends BaseEsMapper<Document> { } 7、在启动类上配置包扫描:(@EsMapperScan)  package cn.aopmin;  import org.dromara.easyes.starter.register.EsMapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;  @EsMapperScan("cn.aopmin.mapper") // 指定Mapper接口所在的包名 @SpringBootApplication public class EEApplication {     public static void main(String[] args) {         SpringApplication.run(EEApplication.class, args);     } } 8、测试  示例1:创建索引库  package cn.aopmin.test;  import cn.aopmin.mapper.DocumentMapper; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest;  import javax.annotation.Resource;  /**  * EasyEs快速入门测试类  *  * @author 白豆五  * @version 2023/07/26  * @since JDK8  */ @SpringBootTest public class EasyEsTest {      @Resource     private DocumentMapper documentMapper;      /**      * 测试创建索引库      */     @Test     public void testCreateIndex() {         //创建索引,根据当前mapper对应实体类信息及其注解配置生成索引信息         Boolean success = documentMapper.createIndex();         System.out.println(success ? "创建索引成功" : "创建索引失败");     } }  示例2:添加文档  /**  * 测试添加文档  */ @Test public void testAddDocument() {     // 持久层添加数据跟mp类似也是insert方法     documentMapper.insert(new Document("1", "程序员的幽默", "为了让你开心,我特意编写了一段关于程序员的小笑话,希望你能喜欢。")); } 示例3:测试查询标题中包含程序员的文档列表(就连解析结果的活都帮我们做了)  /**  * 测试查询title中包含程序员的文档列表  */ @Test public void testSearch() {     // 条件构造器     LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();     wrapper.eq(Document::getTitle, "程序"); // 类似match查询     // 查询文档列表     List<Document> list = documentMapper.selectList(wrapper);     list.forEach(System.out::println); } ————————————————              原文链接:https://blog.csdn.net/qq_46921028/article/details/131945524 
  • [技术干货] es相关的官方客户端与spring客户端对比与介绍
     1. 客户端版本种类 es提供的  TransportClient 传统的客户端,基于TCP传输协议与Elasticsearch通信。 已经被弃用,不推荐使用。 适用于Elasticsearch 5.x及以前的版本 因为Elasticsearch 6.x及以上版本已不再支持TCP Transport协议,TransportClient无法连接Elasticsearch集群。 RestHighLevelClient 是一个高级的REST客户端,主要用于与Elasticsearch集群通信。 基于Java Low Level REST Client构建,提供更高级的API,隐藏底层细节。 主要用于Kibana和Logstash等工具。 RestHighLevelClient适用于Elasticsearch 6.2.0及以上版本。 spring提供的  ES存储库操作:简单的CRUD 特点 当你需要进行简单的CRUD操作(创建、读取、更新、删除文档)时,使用ES存储库操作是一个不错的选择。 这是因为Spring Data Elasticsearch会自动生成存储库接口的实现,从而使你的代码更简洁、易于理解。 ElasticsearchTemplate 基于ElasticsearchRestTemplate封装,提供更高级的API,如各种CRUD操作。 也需要依赖Spring Framework。 ElasticsearchRestTemplate 是一个Spring的RestTemplate的扩展,用于与Elasticsearch RESTful API交互。 基于Spring的RestTemplate,提供Elasticsearch的自动化JSON序列化和反序列化。 需要依赖Spring Framework。 ElasticsearchOperations:接口实现 使用选择 如果需要更多spring集成相关的功能,则使用ElasticsearchTemplate。 如果需要直接访问Elasticsearch RESTful接口,则使用ElasticsearchRestTemplate。 性能和功能上,ElasticsearchTemplate略胜一筹。 2. spring的相关操作客户端 2.1 ES存储库操作:简单的CRUD 特点  当你需要进行简单的CRUD操作(创建、读取、更新、删除文档)时,使用ES存储库操作是一个不错的选择。 这是因为Spring Data Elasticsearch会自动生成存储库接口的实现,从而使你的代码更简洁、易于理解。 核心  简单的crud操作 不支持复杂的查询检索 使用步骤  实体类  @Document(indexName = "encrypt_log", type = "encrypt_log") public class EncryptLogEntity {        //对应字段     private String srcIp;     private String dstIp;     //...          // getters and setters }  1 2 3 4 5 6 7 8 9 10 11 创建ElasticsearchRepository接口  public interface EncryptLogRepository extends ElasticsearchRepository<EncryptLogEntity, String> {      } 1 2 3 对应的服务中注入接口,并编写方法  @Autowired private MyDocumentRepository myDocumentRepository;  // 创建文档 myDocumentRepository.save(document);  // 读取文档 MyDocument document = myDocumentRepository.findById(id).orElse(null);  // 更新文档 myDocumentRepository.save(updatedDocument);  // 删除文档 myDocumentRepository.deleteById(id); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 2.2 ElasticsearchTemplate:复杂的操作:增删改查 特点  当你需要执行更复杂的查询、聚合操作,或者需要使用一些特定的Elasticsearch功能时,使用ElasticsearchTemplate可能是一个更好的选择。 基于 Transport Client与ES交互。 核心  更复杂的查询、聚合操作 -> ElasticsearchTemplate 需要更简单、方便地集成ES时,可以使用ElasticsearchTemplate。 使用  @Autowired private ElasticsearchTemplate elasticsearchTemplate;  // 构建查询 QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "example");  // 执行查询 SearchHits<MyDocument> searchHits = elasticsearchTemplate.search(new NativeSearchQueryBuilder()         .withQuery(queryBuilder)         .build(), MyDocument.class);  1 2 3 4 5 6 7 8 9 10 11 简单查询例子  @Autowired private ElasticsearchTemplate esTemplate;  List<Map<String, Object>> documents = ... esTemplate.bulkIndex(documents); 1 2 3 4 5 2.3 ElasticsearchRestTemplate:跨节点的搜索查询 特点  适用于更复杂的查询操作 情景  需要更高级ES功能时,使用ElasticsearchRestTemplate。 需要更高级ES功能时,使用ElasticsearchRestTemplate。 样例  @Autowired private ElasticsearchRestTemplate esRestTemplate;  List<Map<String, Object>> documents = ... esRestTemplate.bulkIndex(documents, Map.class);    SearchQuery searchQuery = new NativeSearchQueryBuilder()      .withQuery(matchQuery("name", "spring"))      .build(); List<Map> results = esRestTemplate.search(searchQuery, Map.class)      .getContent(); 1 2 3 4 5 6 7 8 9 10 11 2.4 ElasticsearchTemplate和ElasticsearchRestTemplate主要的区别在于 底层实现:  ElasticsearchTemplate 底层使用 Elasticsearch Java API 实现。 ElasticsearchRestTemplate 底层使用 Elasticsearch Rest API 实现。 使用范围:  ElasticsearchTemplate 只能在内部使用,不支持跨节点操作。 ElasticsearchRestTemplate 支持跨节点操作。 功能:  ElasticsearchTemplate 功能更全面,支持多种操作,如索引、搜索、删除等。 ElasticsearchRestTemplate 主要用于搜索。 性能:  ElasticsearchTemplate 性能更高,不需要经过 HTTP 请求。 ElasticsearchRestTemplate 需要通过 HTTP 请求,性能相对较低。 复杂度:  ElasticsearchTemplate 复杂度较高,需要深入理解 Elasticsearch Java API。 ElasticsearchRestTemplate 复杂度较低,只需要理解 Elasticsearch 的 REST API。 总的来说:  如果只在单个节点上操作 Elasticsearch,优先选择 ElasticsearchTemplate。 如果需要跨节点操作,或者为了简单起见,可以选择 ElasticsearchRestTemplate。 2.4 ElasticsearchOperations:接口 特点  ElasticsearchOperations是一个接口,代表了基础的Elasticsearch操作。 ElasticsearchTemplate是ElasticsearchOperations的一个实现。 功能  ElasticsearchOperations定义了基本的Elasticsearch操作,如插入、查询、删除等。 ElasticsearchTemplate实现了ElasticsearchOperations接口,提供了更多功能,例如: 批量操作 分页查询 对复杂对象进行转换 映射管理 索引管理 使用  一般情况下,程序直接使用ElasticsearchTemplate。  在测试中,可以使用ElasticsearchOperations接口,达到分离依赖的目的。  3. 不同版本的依赖及对应方法的选择 进入spring对应的官网  随便找一个api——找到历史文档及版本对应版本对应   然后找到对应的官方操作客户端方法   4. 总结 es客户端 TransportClient :已过时 RestHighLevelClient:es官方推荐的版本,但有时候在spring中被废弃 spring客户端 ES存储库 适用于简单的CRUD操作 可阅读性强,操作编写难度麻烦 ElasticsearchTemplate 复杂的增删改查操作 单节点中使用 ElasticsearchRestTemplate 多节点,和相对于ElasticsearchTemplate 更便捷的方法调用 更加多样的查询封装方法 ElasticsearchOperations 是一个接口,使用的较少 ————————————————               原文链接:https://blog.csdn.net/weixin_44245613/article/details/130619332 
  • [技术干货] ES快速开发,ElasticsearchRestTemplate基本使用以及ELK快速部署
    最近博主有一些elasticsearch的工作,所以更新的慢了些,现在就教大家快速入门,并对一些基本的查询、更新需求做一下示例,废话不多说开始:  1. ES快速上手 es下载:[https://elasticsearch.cn/download/]()这里关于es所需要的链接基本都有,可以快速下载使用 当你解压好了归档文件之后,Elasticsearch 已经准备好运行了  1 cd elasticsearch-<version> 2 ./bin/elasticsearch  es默认端口9200;transport端口是9300;transport端口主要用来java客户端操作,启动成功后,我们来安装一下kibana界面操作es的工具,也可以下载header,但是kibana界面比较友好并且最后部署ELK的时候也需要该工具,所以博主就直接安装kibana了  kibana下载:[https://elasticsearch.cn/download/]()还是跟es一样的链接,下载后解压并编辑config目录下编辑kibana.yml文件,主要配置如下:    1 server.port: 15601     1 server.host: "0.0.0.0"     1 elasticsearch.hosts: ["http://localhost:9200"]  只需要修改这几处配置就可以,前提是kibana的版本必须与es的版本是相同的,否则会包很多错误,并且启动失败,Linux启动时不能使用root用户启动,必须自己添加个人用户才可以,命令如下:  添加用户: 1 useradd testuser  设置密码: 1 passwd testuser  将我们的文件夹用户权限改变一下要不然启动时候老是提示没有权限: 1 chown -R testuser:testuser kibana  现在进入我们kibana的文件夹,以testuser启动kibana: 1 /bin/kibana  访问地址:http://localhost:15601 当看到这里的时候就已经OK了,我们终于可以开始使用es了。  我就不介绍es是干啥用的了,es具有分片的概念,分为主分片和副本分片,创建索引的时候一旦设置副本分片,必须有大于等于2台的机器,每个机器都有es,es之间的交互,需要自己在配置文件中作修改,否则不配置,永远只是单机,并且主分片在建索引的时候必须考虑清楚减多少个主分片,因为以后如果需要修改主分片,必须重新创建索引,你添加或则减少一个主分片,es往分片中存放数据的时候都会变,但是副本分片不一样,因为他是数据冗余的,一旦主分片宕机,副本会当选主分片,并且是要主分片存在,副本没有也可以,副本的作用就是提高数据的吞吐量。好了,开始实战: 点击kibana的Dev Tools按钮,就可以在面板里写语句操作索引了: 建立索引:shards主分片  replicas副本分片设置的数量,看你有几台机器-1  PUT /test {   "settings": {     "number_of_shards": 5,     "number_of_replicas": 1   },   "mappings": {     "_doc": {       "properties": {         "name": {           "type": "text",           "analyzer": "ik_max_word",           "search_analyzer": "ik_smart"         },         "age": {           "type":"integer"         }       }     }   } }   建立mappings做好字段类型,并且text类型中使用分词器,不要使用默认的分词器,默认的分词器一个汉字分一个,查询出来基本没啥价值,中文分词器是ik,可以上网搜一下下载到es里面。    大家上过语文课,都知道语句有歧义问题,就比如武汉市长江大桥,可以断成武汉市长、江大桥;武汉市、长江大桥;这就是分词器如何切分的问题,所以es有关键字查询term,进行完全匹配,不进行分词器query匹配,除了这些,中文还有同义词一说,比如苹果水果与苹果手机,大家搜索的时候基本都是输入苹果,但是出来的却是苹果手机,水果很少,这就是因为es也可以做同义词查询。但是需要配置同义词文件,具体操作可以自行上网解决,主要就是创建索引的时候,使用自己在config中编辑的文本文件,该文件中有自己要使用到的同义词,比如:iPhone,苹果手机;    我们现在再来进行实战开发,本人接触的是使用ElasticsearchRestTemplate进行开发,该类基本含括了大部分需求开发查询。下面开始举例:  搜索查询:   1 String[] includes = new String[] {  2                     "paperBaseId"  3                     ,"auditInfoStatus"  4             };  5             SourceFilter sourceFilter = new FetchSourceFilterBuilder().withIncludes(includes).build();  6             SearchQuery searchQuery = new NativeSearchQueryBuilder()  7                     .withSourceFilter(sourceFilter)  8                     // 添加查询条件  9                     .withQuery(QueryBuilders.termsQuery("paperBaseId",paperBaseId)) 10                     .build(); 11             List<EsPaperBase> esPaperBaseList = elasticsearchRestTemplate.queryForList(searchQuery,EsPaperBase.class);  1 //单索引匹配更新      2             Map<String, Object> params = new HashMap<String, Object>();  3             params.put("flag", deleteFlag);  4             //ctx._source即为该索引本身  5             String code = "ctx._source.deleteFlag=params.flag;";  6             ScriptType type = ScriptType.INLINE;  7             //使用脚本进行更新字段值  8             Script script = new Script(type, Script.DEFAULT_SCRIPT_LANG, code, params);  9  10             UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(); 11             updateByQueryRequest.indices("exam_information");//设置索引 12             updateByQueryRequest.setDocTypes("doc");//设置文档,固定doc 13             updateByQueryRequest.setQuery(QueryBuilders.termsQuery("paperBaseId", paperBaseId));//设置查询 14             updateByQueryRequest.setScript(script);//如果有脚本,则添加 15             updateByQueryRequest.setConflicts("proceed"); // 设置版本冲突时继续 16             updateByQueryRequest.setRefresh(true);//请求结束后,对我们写入的索引进行调用刷新 17             this.elasticsearchTemplate.getClient().updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT);//进行更新  1 //多索引匹配批量更新  2         Map<String,Object> updateMap = new HashMap<>();  3         updateMap.put("deleteFlag",deleteFlag);  4         updateMap.put("lastUpdateTime",currDatetime);  5         UpdateRequest doc = new UpdateRequest().doc(updateMap);  6         List<UpdateQuery> updateQuerys = new ArrayList<>();  7         //生成批量更新操作  8         paperBaseId.stream().forEach(id ->{  9             UpdateQuery build = new UpdateQueryBuilder() 10                     .withUpdateRequest(doc) 11                     .withDoUpsert(true) 12                     .withIndexName("paper_base") 13                     .withType("doc") 14                     .withId(id).build(); 15             updateQuerys.add(build); 16         }); 17         elasticsearchTemplate.bulkUpdate(updateQuerys,BulkOptions.builder().withRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).build()); 18           1 //查询操作  2         MatchQueryBuilder lastUpdateUser = QueryBuilders.matchQuery("personId", userId);  3         MatchQueryBuilder deleteflag = QueryBuilders.matchQuery("deleteFlag", BaseEntity.DEL_FLAG_DELETE);  4         //创建bool多条件查询  5         BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();  6         BoolQueryBuilder mustQuery = boolQueryBuilder.must(lastUpdateUser).must(deleteflag);  7         //嵌套索引,需要使用nest查询  8         mustQuery.must(QueryBuilders.nestedQuery("entityNodes", QueryBuilders.termQuery("entityNodes.node_type", recyclePaperDTO.getNodeType()), ScoreMode.None));  9         //可以使用should查询,不是必需条件 10         BoolQueryBuilder nodeQueryBuilder = QueryBuilders.boolQuery(); 11         nodeQueryBuilder.should(QueryBuilders.nestedQuery("entityNodes", QueryBuilders.wildcardQuery("entityNodes.parent_ids", "*," + recyclePaperDTO.getNodeId() + "*"), ScoreMode.None)); 12         nodeQueryBuilder.should(......); 13         mustQuery.must(nodeQueryBuilder); 14         //查询使用排序 15         SortBuilder order = new FieldSortBuilder("lastUpdateTime").order(SortOrder.DESC); 16         //可以使用高亮显示,就是html标签 17         HighlightBuilder highlightBuilder = new HighlightBuilder(); 18         highlightBuilder.preTags("<span class='highlighted'>") 19                 .postTags(</span>) 20                 .field("paperBaseName");//哪个字段高亮 21         //使用分页查询 22         SearchQuery nativeSearchQueryBuilder = new NativeSearchQueryBuilder() 23                 .withQuery(mustQuery).withSort(order).withHighlightBuilder(highlightBuilder) 24                 .withPageable(PageRequest.of(recyclePaperDTO.getPageNum()-1, recyclePaperDTO.getPageSize())).build(); 25         //进行查询,entityMapper使用默认的也可,EsPaperBase.class是需要自己映射的查询类 26         elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder, EsPaperBase.class, new HighlightResultMapper(entityMapper)); 27           1 @Data  2 @Builder  3 @NoArgsConstructor  4 @AllArgsConstructor  5 @Document(indexName = "paper_base", type = "doc")  6 @Setting(settingPath = "/elasticsearch/settings.json")//可设置主分片、副本分片、设置默认停用词等  7 public class EsPaperBase {  8   9     @Id 10     @Field(type = FieldType.Keyword, name = "paperBaseId") 11     private String paperBaseId; 12  13     /** 14      * 试卷名称 15      */ 16     @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "standard" , name = "paperBaseName"), 17             otherFields = { 18                     @InnerField(suffix = "zh", type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart"), 19                     @InnerField(suffix = "en", type = FieldType.Text, analyzer = "english"), 20             }) 21     private String paperBaseName; 22  23     /** 24      * 共享级别名,可以使用分词器查询,模糊匹配 25      */ 26     @Field(type = FieldType.Text, name = "shareLevelName") 27     private String shareLevelName; 28  29  30     /** 31      * 创建人,不可使用分词器查询,精准匹配 32      */ 33     @Field(type = FieldType.Keyword, name = "personId") 34     private String personId; 35  36  37     /** 38      * 创建时间 39      */ 40     @Field(type = FieldType.Date, name = "createtime", format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss") 41     private String createtime; 42  43     /** 44      * 更新时间 45      */ 46     @Field(type = FieldType.Date, name = "lastUpdateTime", format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss") 47     private String lastUpdateTime; 48  49     /** 50      * 删除标识    0:未删除,1:已删除 51      */ 52     @Field(type = FieldType.Keyword, name = "deleteFlag") 53     private String deleteFlag; 54     /** 55      * 试卷推荐,内嵌字段 56      */ 57     @Field(type=FieldType.Nested,name="paperRecommends") 58     private List<EsPaperRecommend> paperRecommends; 59      60     ...... 61 }  1 {//setting.json  2     "index": {  3         "number_of_shards": "5",  4         "number_of_replicas": "2",  5         "refresh_interval": "1s",  6         "max_rescore_window": 10000000  7     },  8     "analysis": {  9       "filter": { 10         "spanish_stop": { 11           "type":        "stop", 12           "stopwords": [ "si", "esta", "el", "la" ]   13         }, 14         "light_spanish": {  15           "type":     "stemmer", 16           "language": "light_spanish" 17         } 18       }, 19       "analyzer": { 20         "my_spanish": { 21           "tokenizer": "spanish", 22           "filter": [ //顺序很重要 23             "lowercase", 24             "asciifolding", 25             "spanish_stop", 26             "light_spanish" 27           ] 28         } 29       } 30     } 31   }    现在很多公司基本使用分布式架构应用,公司每个应用模块都有好几台机器,看日志问题也就衍生而来,我们最笨的方法就是每个服务器后台都打开进行查看,效率低下,此时,我们就可以使用es、kibana、logstash;简称ELK进行查看分布式日志系统,但是本文不会进行安装logstash进行演示,因为只做日志查询的需求,我们使用ELK的变种EFK即可,filebeat轻量级做日志收集即可,最主要的就是看我们如何进行配置,然后使用kibana进行查询日志。    安装完logstash后,解压在config中新建my-logstash.conf,该配置中注意三大块,input、filter、output;其中input是作为吸取日志的以.log为后缀的日志文件,filter是过滤配置,不用管,output则是导入到哪个elasticsearch中;配置如下:   1 input {  2         file {  3                 type => "log"  4                 path => ["/apps/svr/server/*/log.file"]  5                 start_position => "end"  6                 ignore_older => 0  7                 codec=> multiline {  8                         pattern => "^\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}"  9                         negate => true 10             auto_flush_interval => 5 11                         what => "previous" 12                 } 13         } 14         beats { 15             port => 5044 16         } 17 } 18 output { 19         if [type] == "log" { 20                 elasticsearch { 21                         hosts => ["http://127.0.0.1:19200"] 22                         index => "logstash-%{+YYYY.MM}" 23                         #user => es 24                         #password => es2018 25                 } 26         } 27 }   如果自己动手配置的话,最好自己手动输入,不要复制粘贴,很有可能会有特殊字符出现导致启动失败;启动命令:./bin/logstah -f my-logstash.conf     最终我们就可以这样使用kibana进行查询日志的操作了。简单的基本应用就到此为止了,工作中基础的应用是没有问题了; ————————————————              原文链接:https://blog.csdn.net/qq_40280043/article/details/140465060 
  • [技术干货] SpringBoot之ElasticsearchRestTemplate常用示例
    目录 1.引入pom依赖 2.application 配置 3.JavaBean配置以及ES相关注解 3.1 Student实体类 3.2 Teacher实体类 3.3 Headmaster 实体类 4. 启动类配置 5.elasticsearchRestTemplate 新增 ==5.1 createIndex && putMapping 创建索引及映射== 5.1.1 Controller层 5.1.2 service层 5.1.3 serviceimpl层 createIndex && putMapping 创建索引及映射测试结果: ==5.2 save 添加文档== 5.2.1 Controller层 5.2.2 service层 5.2.3 serviceimpl层 save 添加文档测试结果: 6.elasticsearchRestTemplate 删除 ==6.1 deleteIndex 删除索引== 6.1.1 Controller层 6.1.2 service层 6.1.3 serviceimpl层 deleteIndex 删除索引测试结果: ==6.2 delete 删除文档(通过主键删除)== 6.2.1 Controller层 6.2.2 service层 6.2.3 serviceimpl层 delete(通过主键删除) 删除文档测试结果: ==6.3 delete 删除文档(删除通过query所搜索到的所有数据)== 6.3.1 Controller层 6.3.2 service层 6.3.3 serviceimpl层 delete(删除通过query所搜索到的所有数据) 删除文档测试结果: 7.elasticsearchRestTemplate 修改 ==7.1 update 修改文档(根据主键修改)== 7.1.1 Controller层 7.1.2 service层 7.1.3 serviceimpl层 update 修改文档(根据主键修改)测试结果: 8.elasticsearchRestTemplate 查询 ==8.1 get 查询文档(根据主键查询)== 8.1.1 Controller层 8.1.2 service层 8.1.3 serviceimpl层 get 查询文档(根据主键查询)测试结果: ==8.2 exists 查询文档(判断主键id是否存在)== 8.2.1 Controller层 8.2.2 service层 8.2.3 serviceimpl层 exists 查询文档(判断主键id是否存在)测试结果: ==8.3 queryForPage 查询文档(分页查询)== 8.3.1 Controller层 8.3.2 service层 8.3.3 serviceimpl层 queryForPage 查询文档(分页查询)测试结果: ==8.4 AnalyzeRequest (根据入参内容返回分词后结果)== 8.4.1 Controller层 8.4.2 service层 8.4.3 serviceimpl层 AnalyzeRequest (根据入参内容返回分词后结果)测试结果: ==8.5 termQuery 查询文档(入参不分词,精确匹配)== 8.5.1 Controller层 8.5.2 service层 8.5.3 serviceimpl层 termQuery 查询文档(入参不分词,精确匹配)测试结果: ==8.6 match 查询文档(会对查询条件进行分词)== 8.6.1 Controller层 8.6.2 service层 8.6.3 serviceimpl层 match 查询文档(会对查询条件进行分词)测试结果: ==8.7 matchAllQuery 查询文档(查询此索引下所有文档)== 8.7.1 Controller层 8.7.2 service层 8.7.3 serviceimpl层 matchAllQuery 查询文档(查询此索引下所有文档)测试结果: ==8.8 wildcardQuery 查询文档(模糊查询)== 8.8.1 Controller层 8.8.2 service层 8.8.3 serviceimpl层 wildcardQuery 查询文档(模糊查询)测试结果: ==8.9 prefixQuery 查询文档(前缀查询)== 8.9.1 Controller层 8.9.2 service层 8.9.3 serviceimpl层 prefixQuery 查询文档(前缀查询)测试结果: ==8.10 regexpQuery 查询文档(正则表达式查询)== 8.10.1 Controller层 8.10.2 service层 8.10.3 serviceimpl层 regexpQuery 查询文档(正则表达式查询) 测试结果: ==8.11 rangeQuery 查询文档(范围查询)== 8.11.1 Controller层 8.11.2 service层 8.11.3 serviceimpl层 rangeQuery 查询文档(范围查询) 测试结果: ==8.12 Sort 查询文档 (排序)== 8.12.1 Controller层 8.12.2 service层 8.12.3 serviceimpl层 Sort 查询文档 (排序) 测试结果: ==8.13 queryStringQuery 查询文档 (多条件查询 一值多域)== 8.13.1 Controller层 8.13.2 service层 8.13.3 serviceimpl层 queryStringQuery 查询文档 (多条件查询 一值多域) 测试结果: ==8.14 boolQuery 查询文档 (对多个查询条件连接。连接方式:must)== 8.14.1 Controller层 8.14.2 service层 8.14.3 serviceimpl层 boolQuery 查询文档 (对多个查询条件连接。连接方式:must) 测试结果: ==8.15 boolQuery 查询文档 (对多个查询条件连接。连接方式:filter)== 8.15.1 Controller层 8.15.2 service层 8.15.3 serviceimpl层 boolQuery 查询文档 (对多个查询条件连接。连接方式:filter) 测试结果: ==8.16 boolQuery 查询文档 (对多个查询条件连接。连接方式:must_not)== 8.16.1 Controller层 8.16.2 service层 8.16.3 serviceimpl层 boolQuery 查询文档 (对多个查询条件连接。连接方式:must_not) 测试结果: ==8.17 boolQuery 查询文档 (对多个查询条件连接。连接方式:should)== 8.17.1 Controller层 8.17.2 service层 8.17.3 serviceimpl层 boolQuery 查询文档 (对多个查询条件连接。连接方式:should) 测试结果: ==8.18 HighlightBuilder 查询文档 (高亮)== 8.18.1 Controller层 8.18.2 service层 8.18.3 serviceimpl层 HighlightBuilder 查询文档 (高亮) 测试结果: ==8.19 AggregationBuilders 查询文档 (聚合查询)== 8.19.1 Controller层 8.19.2 service层 8.19.3 serviceimpl层 AggregationBuilders 查询文档 (聚合查询) 测试结果: ==8.20 SuggestBuilders查询文档 (completionSuggestion---搜索建议补全)== 8.20.1 Controller层 8.20.2 service层 8.20.3 serviceimpl层 SuggestBuilders查询文档 (completionSuggestion---搜索建议补全) 测试结果: ==8.21 SuggestBuilders查询文档 (termSuggestion---纠错补全)== 8.21.1 Controller层 8.21.2 service层 8.21.3 serviceimpl层 SuggestBuilders查询文档 (termSuggestion---纠错补全) 测试结果: ==~~8.22 SuggestBuilders查询文档 (phraseSuggestion---纠错补全)~~== ~~8.22.1 Controller层~~ ~~8.22.2 service层~~ ~~8.22.3 serviceimpl层~~ ~~SuggestBuilders查询文档 (termSuggestion---纠错补全) 测试结果:~~ 链接:[windows版本 Elasticsearch服务端-7.4.0以及kibana-7.4.0和ik分词器下载地址](https://mp.csdn.net/mp_download/manage/download/UpDetailed) 链接:[SpringBoot之ElasticsearchRestTemplate常用示例 源代码下载地址](https://download.csdn.net/download/JAVA_MHH/72555390) 1.引入pom依赖 主要引用 elasticsearch使用相关依赖 注意ES 服务端版本(7.4.0)要与客户端版本相容 注意 hutool的工具类 实体转map 版本      <!--引入springboot父工程依赖-->     <!--引入依赖作用:     可以省去version标签来获得一些合理的默认配置     -->     <parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>2.3.2.RELEASE</version>     </parent>      <dependencies>          <!--elasticsearch使用相关依赖-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-elasticsearch</artifactId>         </dependency>          <!--引入提供Web开发场景依赖-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>          <!--这里用到了类型转Map,这里用到hutool的工具类(版本高一些,低版本的 如果传的就是Map类型,在map转map时会报错)-->         <dependency>             <groupId>cn.hutool</groupId>             <artifactId>hutool-core</artifactId>             <version>5.7.13</version>         </dependency>          <!--用@Data 减少JavaBean get...set...方法-->         <dependency>             <groupId>org.projectlombok</groupId>             <artifactId>lombok</artifactId>         </dependency>          <!--两个用来做测试的jar包-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>         </dependency>         <dependency>             <groupId>junit</groupId>             <artifactId>junit</artifactId>         </dependency>      </dependencies>  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49  2.application 配置  spring:   elasticsearch:     rest:       uris: 127.0.0.1:9200               #---ES的连接地址,多个地址用逗号分隔       username:                          #---用户名       password:                          #---密码       connection-timeout: 1000           #---连接超时时间(默认1s)       read-timeout: 1000                 #---读取超时时间(默认30s) 1 2 3 4 5 6 7 8 9  3.JavaBean配置以及ES相关注解 注解:@Document用来声明Java对象与ElasticSearch索引的关系              indexName 索引名称(是字母的话必须是小写字母)              type 索引类型              shards 主分区数量,默认5              replicas 副本分区数量,默认1              createIndex 索引不存在时,是否自动创建索引,默认true                         不建议自动创建索引(自动创建的索引 是按着默认类型和默认分词器) 注解:@Id 表示索引的主键 注解:@Field 用来描述字段的ES数据类型,是否分词等配置,等于Mapping描述              index 设置字段是否索引,默认是true,如果是false则该字段不能被查询              store 默认为no,被store标记的fields被存储在和index不同的fragment中,以便于快速检索。虽然store占用磁盘空间,但是减少了计算。              type 数据类型(text、keyword、date、object、geo等)              analyzer 对字段使用分词器,注意一般如果要使用分词器,字段的type一般是text。              format 定义日期时间格式,详细见 官方文档: https://www.elastic.co/guide/reference/mapping/date-format/. 注解:@CompletionField 定义关键词索引 要完成补全搜索              analyzer 对字段使用分词器,注意一般如果要使用分词器,字段的type一般是text。              searchAnalyzer 显示指定搜索时分词器,默认是和索引是同一个,保证分词的一致性。              maxInputLength:设置单个输入的长度,默认为50 UTF-16 代码点 常用数据类型:  简单类型:  字符串类型: - string(不再支持)可知string类型的field已经被移除了, 我们需要用text或keyword类型来代替string.                     text:会分词,不支持聚合                     keyword:不会分词,将全部内容作为一个词条,支持聚合  数字类型: 尽可能选择范围小的数据类型, 字段的长度越短, 索引和搜索的效率越高;(优先考虑使用带缩放因子的浮点类型)                 byte :            有符号的8位整数, 范围: [-128 ~ 127]                 short :           有符号的16位整数, 范围: [-32768 ~ 32767]                 integer :        有符号的32位整数, 范围: [−231 ~ 231-1]                 long :            有符号的64位整数, 范围: [−263 ~ 263-1]                 float :             32位单精度浮点数                 double :         64位双精度浮点数                 half_float :     16位半精度IEEE 754浮点类型                 scaled_float : 缩放类型的的浮点数, 比如price字段只需精确到分, 57.34缩放因子为100, 存储结果为5734  日期类型:date JSON没有日期数据类型, 所以在ES中, 日期可以是:   —   包含格式化日期的字符串 “2018-10-01”, 或"2018/10/01 12:10:30"    (可以通过 format属性 定义日期时间格式)DateFormat 官方文法.   —   代表时间毫秒数的长整型数字.   —   代表时间秒数的整数.  布尔类型: boolean 可以接受表示真、假的字符串或数字:   —  真值: true, “true”, “on”, “yes”, “1”…   —  假值: false, “false”, “off”, “no”, “0”, “”(空字符串), 0.0, 0  复杂类型:  数组[]: 由数组中第一个非空值决定数组类型(type = FieldType.Keyword) List集合: 由数组中第一个非空值决定数组类型(type = FieldType.Keyword) 嵌套类型: list里泛型是object形式的或自定义对象(type = FieldType.Nested) 对象:{ }object for single JSON objects 单个JSON对象(type = FieldType.Object)  3.1 Student实体类  import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType;  import java.util.Date; import java.util.List;  /*  * 学生  * */ @Data @AllArgsConstructor @NoArgsConstructor //indexName名字如果是字母那么必须是小写字母 @Document(indexName = "student", type = "_doc", replicas = 1, shards = 1, createIndex = true) public class Student {      @Id     @Field(index = true, store = true, type = FieldType.Keyword)     private String sId;      @Field(index = true, store = true, type = FieldType.Keyword)     private String sName;      @Field(index = true, store = true, type = FieldType.Text, analyzer = "ik_smart")     //Text可以分词 ik_smart=粗粒度分词 ik_max_word 为细粒度分词     private String sAddress;      @Field(index = false, store = true, type = FieldType.Integer)     private Integer sAge;      @Field(index = false, store = true, type = FieldType.Date, format = DateFormat.basic_date_time)     private Date sCreateTime;      @Field(index = false, store = true, type = FieldType.Object)     private Headmaster sHeadmaster;//班主任      @Field(index = true, store = false, type = FieldType.Keyword)     private String[] sCourseList; //数组类型 由数组中第一个非空值决定(这里数组和集合一个意思了)      @Field(index = true, store = false, type = FieldType.Keyword)     private List<String> sColorList; //集合类型 由数组中第一个非空值决定      @Field(index = true, store = false, type = FieldType.Nested)//嵌套类型list里泛型是object形式的或自定义对象     private List<Teacher> sTeacherList; //教所有科目的老师  }  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55  3.2 Teacher实体类  import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.*; import org.springframework.data.elasticsearch.core.completion.Completion;  import java.util.Date;  /*  * 科目老师  * */ @Data @NoArgsConstructor @AllArgsConstructor @Document(indexName = "teacher", type = "_doc", replicas = 1, shards = 1, createIndex = true) public class Teacher {      //主键id     @Id     @Field(index = true, store = true, type = FieldType.Keyword)//index:设置通过这个字段是否可以进行搜索     private String tId;      //姓名     @Field(index = true, store = true, type = FieldType.Keyword)     private String tName;      //英文姓名     @Field(index = true, store = true, type = FieldType.Keyword)     private String tEnglishName;      //班级     @Field(index = true, store = true, type = FieldType.Keyword)     private String tClassName;      //地址     @Field(index = true, store = true, type = FieldType.Text, analyzer = "ik_smart")     private String tAddress;      //至理名言     @Field(index = true, store = true, type = FieldType.Keyword)     private String tFamous;      //年龄     @Field(index = true, store = true, type = FieldType.Integer)     private Integer tAge;      //日期     @Field(index = true, store = true, type = FieldType.Date, format = DateFormat.basic_date_time)     private Date tCreateTime;      //定义关键词索引 要完成补全搜索,必须要用到特殊的数据类型completion,     //要汉字拼音都能补全,必须要使用自定义的ik+pinyin分词器     //                                                       maxInputLength:设置单个输入的长度,默认为50 UTF-16 代码点     @CompletionField(analyzer = "ik_smart", searchAnalyzer = "ik_smart", maxInputLength = 100)     private Completion completion;      public Teacher(String tId, String tName, String tEnglishName, String tClassName, String tAddress, Integer tAge, Date tCreateTime) {         this.tId = tId;         this.tName = tName;         this.tEnglishName = tEnglishName;         this.tClassName = tClassName;         this.tAddress = tAddress;         this.tAge = tAge;         this.tCreateTime = tCreateTime;     } } 3.3 Headmaster 实体类  import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import java.util.Date; import java.util.List;  /*  * 班主任  * */ @Data @NoArgsConstructor @AllArgsConstructor @Document(indexName = "headmaster", type = "_doc", replicas = 1, shards = 1, createIndex = true) public class Headmaster {      @Field(index = true, store = false, type = FieldType.Keyword)     private String hId;      @Field(index = true, store = false, type = FieldType.Keyword)     private String hName;      @Field(index = true, store = false, type = FieldType.Keyword)     private String hAddress;      @Field(index = false, store = false, type = FieldType.Integer)     private Integer hSalary;      @Field(index = false, store = false, type = FieldType.Keyword)     private List<String> hClass;      @Field(index = true, store = true, type = FieldType.Date, format = DateFormat.basic_date_time)     private Date  hCreateTime; }  4. 启动类配置 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;  /*  * 搜索引擎客户端启动类  * */ @SpringBootApplication public class ElasticsearchApplication {     public static void main(String[] args) {         SpringApplication.run(ElasticsearchApplication.class, args);     } }  5.elasticsearchRestTemplate 新增 5.1 createIndex && putMapping 创建索引及映射 createIndex :新增索引(数据库) putMapping :新增映射(表结构) 一定要通过这种方法创建,否则创建出来的映射都是默认的 createIndex && putMapping 创建索引及映射测试  入参classType : 对象是要被ES注解所引用的的(基于@Field和@Documen注解属性 创建索引和映射) 5.1.1 Controller层  import com.it.mhh.elasticsearch.service.ElasticsearchSaveService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增类  */ @RestController @RequestMapping("/elasticSearch/save") public class ElasticsearchSaveController {      @Autowired     private ElasticsearchSaveService elasticsearchSaveService;      /**      * @param classType : 要创建es的索引及映射(一定要通过这种方法创建,否则都是创建出来的映射都是默认的)基于传入对象中的@Document注解      * @return boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:43      */     @PostMapping("createIndexAndMapping")     public boolean createIndexAndMapping(Class<?> classType) {         return elasticsearchSaveService.createIndexAndMapping(classType);     }  }  5.1.2 service层  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增  */ public interface ElasticsearchSaveService {      /**      * @param classType : 要创建es的索引及映射(一定要通过这种方法创建,否则都是创建出来的映射都是默认的)基于传入对象中的@Document注解      * @return boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:43      */     boolean createIndexAndMapping(Class<?> classType); }  5.1.3 serviceimpl层  import com.it.mhh.elasticsearch.service.ElasticsearchSaveService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.stereotype.Service;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增  */ @Service public class ElasticsearchSaveServiceImp implements ElasticsearchSaveService {      @Autowired     private ElasticsearchRestTemplate elasticsearchRestTemplate;      /**      * @param classType : 要创建es的索引及映射(一定要通过这种方法创建,否则都是创建出来的映射都是默认的)基于传入对象中的@Document注解      * @return boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:43      */     @Override     public boolean createIndexAndMapping(Class<?> classType) {         if (elasticsearchRestTemplate.indexExists(classType))             return true;         boolean index = elasticsearchRestTemplate.createIndex(classType);         boolean mapping = elasticsearchRestTemplate.putMapping(classType);         return index && mapping;     } }  createIndex && putMapping 创建索引及映射测试结果: 根据入参对象 ES注解 创建索引及映射  import com.it.mhh.elasticsearch.controller.ElasticsearchSaveController; import com.it.mhh.elasticsearch.entity.Headmaster; import com.it.mhh.elasticsearch.entity.Student; import com.it.mhh.elasticsearch.entity.Teacher; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.completion.Completion; import org.springframework.test.context.junit4.SpringRunner; import java.util.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 测试  */ @SpringBootTest @RunWith(SpringRunner.class) public class ElasticsearchSaveControllerTest {      @Autowired     private ElasticsearchSaveController elasticsearchSaveController;      /**      * @param classType : 要创建es的索引及映射(一定要通过这种方法创建,否则都是创建出来的映射都是默认的)基于传入对象中的@Document注解      * @return boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:43      */     @Test     public void createIndexAndMapping() {         boolean indexAndMapping = elasticsearchSaveController.createIndexAndMapping(Student.class);         System.err.println(indexAndMapping);     }  }  5.2 save 添加文档 save(Iterable entities):可传集合 批量添加(同种类对象集合) save(T entity) :单个对象 save(T… entities) : 可变参 save 添加文档测试  入参entities :有重载 可传集合 单个 或者 可变参 5.2.1 Controller层  import com.it.mhh.elasticsearch.service.ElasticsearchSaveService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增类  */ @RestController @RequestMapping("/elasticSearch/save") public class ElasticsearchSaveController {      @Autowired     private ElasticsearchSaveService elasticsearchSaveService;      /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @PostMapping("saveList")     public  <T> Iterable<T> save(@RequestBody Iterable<T> entities) {         return elasticsearchSaveService.save(entities);     }      /**      * @param t : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @PostMapping("save")     public <T> T save(@RequestBody T t) {         return elasticsearchSaveService.save(t);     }  }  5.2.2 service层  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增  */ public interface ElasticsearchSaveService {         /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     public <T> Iterable<T> save(Iterable<T> entities);      /**      * @param t : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */      public <T> T save(T t); }  5.2.3 serviceimpl层  import com.it.mhh.elasticsearch.service.ElasticsearchSaveService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.stereotype.Service;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增  */ @Service public class ElasticsearchSaveServiceImp implements ElasticsearchSaveService {      @Autowired     private ElasticsearchRestTemplate elasticsearchRestTemplate;       /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @Override     public <T> Iterable<T> save(Iterable<T> entities) {         return elasticsearchRestTemplate.save(entities);      }      /**      * @param t : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */      @Override     public <T> T save(T t) {         return elasticsearchRestTemplate.save(t);     }  }  save 添加文档测试结果: 根据被ES注解引用的入参对象存入相应索引中  import com.it.mhh.elasticsearch.controller.ElasticsearchSaveController; import com.it.mhh.elasticsearch.entity.Headmaster; import com.it.mhh.elasticsearch.entity.Student; import com.it.mhh.elasticsearch.entity.Teacher; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.completion.Completion; import org.springframework.test.context.junit4.SpringRunner; import java.util.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 测试  */ @SpringBootTest @RunWith(SpringRunner.class) public class ElasticsearchSaveControllerTest {      @Autowired     private ElasticsearchSaveController elasticsearchSaveController;      /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @Test     public void savesStudentES() {          Headmaster headmaster = new Headmaster("1", "小白主任", "山东", 7500, Arrays.asList("一班", "二班"), new Date());//班主任         List<String> colorList = new ArrayList<>();//颜色         colorList.add("red");         colorList.add("white");         colorList.add("black");          List<Teacher> teacherList = new ArrayList<>();//所有科目老师         teacherList.add(new Teacher("2", "小黑", "black", "一班", "山东省济南市历下区", 13, new Date()));         teacherList.add(new Teacher("2", "小蓝", "blue", "二班", "山东省菏泽市单县", 14, new Date()));          Student student = new Student("1", "mhh", "济南", 12, new Date(), headmaster, new String[]{"语文", "数学", "英语"}, colorList, teacherList);         Student save = elasticsearchSaveController.save(student);         System.out.println(save);     }      /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @Test     public void savesTeacherES() {          Teacher teacher = new Teacher("1", "小黑老师", "black", "一班", "河北省保定市莲池区", 14, new Date());         Teacher save = elasticsearchSaveController.save(teacher);         System.out.println(save);     }      /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @Test     public void savesTeacherListES() {          List<Teacher> teacherList = new ArrayList<>();         teacherList.add(new Teacher("1", "小黑老师", "black", "一班", "山东省泰安市岱岳区","且随疾风前行,身后亦须留心", 15, new Date(), new Completion(new String[]{"山东省泰安市岱岳区"})));         teacherList.add(new Teacher("2", "小白老师", "white", "二班", "山东省济南市历下区", "此剑之势,愈斩愈烈",17, new Date(), new Completion(new String[]{"山东省济南市历下区"})));         teacherList.add(new Teacher("3", "小黄老师", "yellow", "二班", "河北省保定市莲池区", "荣耀存于心,而非流于形",18, new Date(), new Completion(new String[]{"河北省保定市莲池区"})));         teacherList.add(new Teacher("5", "小蓝教授", "blue", "二班", "山东省济南市高新区", "吾之初心,永世不忘",21, new Date(), new Completion(new String[]{"山东省济南市高新区"})));         teacherList.add(new Teacher("4", "小绿教授", "green", "一班", "山西省太原市杏花岭区", "有些失误无法犯两次",24, new Date(), new Completion(new String[]{"山西省太原市杏花岭区"})));         Iterable<Teacher> save = elasticsearchSaveController.save(teacherList);         System.out.println(save);     }  }  6.elasticsearchRestTemplate 删除 6.1 deleteIndex 删除索引 deleteIndex :删除索引(数据库) deleteIndex 删除索引测试  入参classType : 删除的哪个索引(从传入对象中的@Document注解中indexName属性获取) 6.1.1 Controller层  import com.it.mhh.elasticsearch.service.ElasticsearchDeleteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 删除类  */ @RestController @RequestMapping("/elasticSearch/delete") public class ElasticsearchDeleteController {      @Autowired     private ElasticsearchDeleteService elasticsearchDeleteService;      /**      * @param clazz : 删除的哪个索引(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @explain : 删除索引      * @Author Mhh      * @Date 2021/12/27 14:27      */     @PostMapping("deleteIndex")     public Boolean deleteIndex(Class<?> clazz) {         return elasticsearchDeleteService.deleteIndex(clazz);     } }  6.1.2 service层  import org.springframework.data.elasticsearch.core.query.Query; /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 查询  */ public interface ElasticsearchDeleteService {      /**      * @param clazz : 删除的哪个索引(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @explain : 删除索引      * @Author Mhh      * @Date 2021/12/27 14:27      */     public Boolean deleteIndex(Class<?> clazz); }  6.1.3 serviceimpl层  import com.it.mhh.elasticsearch.service.ElasticsearchDeleteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.stereotype.Service;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @Service public class ElasticsearchDeleteServiceImp implements ElasticsearchDeleteService {      @Autowired     private ElasticsearchRestTemplate elasticsearchRestTemplate;            /**      * @param clazz : 删除的哪个索引(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @explain : 删除索引      * @Author Mhh      * @Date 2021/12/27 14:27      */      public Boolean deleteIndex(Class<?> clazz) {         return elasticsearchRestTemplate.deleteIndex(clazz);     }  }  deleteIndex 删除索引测试结果: 根据入参对象 ES@Document注解中indexName属性值删除索引 如果删除的 索引名不存在 返回false  import com.it.mhh.elasticsearch.controller.ElasticsearchDeleteController; import com.it.mhh.elasticsearch.entity.Teacher; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.TermsQueryBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.test.context.junit4.SpringRunner;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @SpringBootTest @RunWith(SpringRunner.class) public class ElasticsearchDeleteControllerTest {      @Autowired     private ElasticsearchDeleteController elasticsearchDeleteController;      /**      * @param clazz : 删除的哪个索引(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @explain : 删除索引      * @Author Mhh      * @Date 2021/12/27 14:27      */      @Test     public void deleteIndex() {         Boolean aBoolean = elasticsearchDeleteController.deleteIndex(Teacher.class);         System.err.println(aBoolean);     } }  6.2 delete 删除文档(通过主键删除) delete(Object entity):通过入参对象主键删除 delete(String id, Class<?> entityType) id表示要删除的主键,entityType表示 被@Document注解且有indexName属性值(indexName属性值为索引名) delete(通过主键删除) 删除文档测试  两种入参形式 都是通过主键id删除,即使入参对象其它属性设置了值也是通过主键id删除 6.2.1 Controller层  import com.it.mhh.elasticsearch.service.ElasticsearchDeleteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 删除类  */ @RestController @RequestMapping("/elasticSearch/delete") public class ElasticsearchDeleteController {      @Autowired     private ElasticsearchDeleteService elasticsearchDeleteService;      /**      * @param id         : 要删除的主键id      * @param entityType : 删除的哪个索引的数据(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */     @PostMapping("deleteById")     public Boolean delete(@RequestParam("id") String id, Class<?> entityType) {         return elasticsearchDeleteService.delete(id, entityType);     }      /**      * @param entity : 这个实体对象必须是被@Document注解且有indexName属性值) 以及主键必须有值,其它参数有无都没关系(和用主键id删除没区别)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */     @PostMapping("delete")     public Boolean delete(Object entity) {         return elasticsearchDeleteService.delete(entity);     }  }   6.2.2 service层  import org.springframework.data.elasticsearch.core.query.Query; /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 查询  */ public interface ElasticsearchDeleteService {      /**      * @param id         : 要删除的主键id      * @param entityType : 删除的哪个索引的数据(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */     Boolean delete(String id, Class<?> entityType);      /**      * @param entity : 这个实体对象必须是被@Document注解且有indexName属性值) 以及主键必须有值,其它参数有无都没关系(和用主键id删除没区别)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */      public Boolean delete(Object entity); }  6.2.3 serviceimpl层  import com.it.mhh.elasticsearch.service.ElasticsearchDeleteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.stereotype.Service;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @Service public class ElasticsearchDeleteServiceImp implements ElasticsearchDeleteService {      @Autowired     private ElasticsearchRestTemplate elasticsearchRestTemplate;      /**      * @param id         : 要删除的主键id      * @param entityType : 删除的哪个索引的数据(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */     @Override     public Boolean delete(String id, Class<?> entityType) {         String delete = elasticsearchRestTemplate.delete(id, entityType);         return true;     }      /**      * @param entity : 这个实体对象必须是被@Document注解且有indexName属性值) 以及主键必须有值,其它参数有无都没关系(和用主键id删除没区别)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */     @Override     public Boolean delete(Object entity) {         String delete = elasticsearchRestTemplate.delete(entity);         return true;     } } import com.it.mhh.elasticsearch.controller.ElasticsearchSelectController; import com.it.mhh.elasticsearch.entity.Student; import com.it.mhh.elasticsearch.entity.Teacher; import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.index.query.Operator; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Sort; import org.springframework.test.context.junit4.SpringRunner;  import java.io.IOException; import java.util.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @SpringBootTest @RunWith(SpringRunner.class) public class ElasticsearchSelectControllerTest {      @Autowired     private ElasticsearchSelectController elasticsearchSelectController;//es的查询      /**      * @param name      : es里索引的域(字段名)      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @param values    : 一域多值, 查询的值      * @return java.util.List<T>      * @explain : 聚合对数据进行分组的求和,求数,最大值,最小值,或者其它的自定义的统计功能,      * //            es对聚合有着不错的支持,需要注意的是,在对某字段进行聚合之后,需要开启这个字段的fielddata      * //            不要对text类型的数据进行分组,会失败      * @Author Mhh      * @Date 2021/12/22 14:29      */     @Test     public void aggregationBuilder() {         List<Teacher> teacherList = elasticsearchSelectController.aggregationBuilder(                 "tAddress",                 Teacher.class,                 "山东省");         teacherList.forEach(System.err::println);     }  }  8.20 SuggestBuilders查询文档 (completionSuggestion—搜索建议补全) SuggestBuilders.completionSuggestion(String fieldname).prefix(String prefix).skipDuplicates(boolean skipDuplicates)…                             fieldname : 使用fieldName入参进行标题联想(这里就是索引被@CompletionField注解标注的字段)                             prefix :   设置要为其提供补全的前缀。前缀由建议分析器进行分析。                             skipDuplicates :   是否应过滤重复项。默认为 { false} SuggestBuilders查询文档 (completionSuggestion—搜索建议补全) 测试  完成补全单词,输出如前半部分,补全整个单词 8.20.1 Controller层 import com.it.mhh.elasticsearch.service.ElasticsearchSelectService; import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.index.query.Operator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Sort; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;  import java.io.IOException; import java.util.List; import java.util.Map;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 查询类  */ @RestController @RequestMapping("/elasticSearch/select") public class ElasticsearchSelectController {      @Autowired     private ElasticsearchSelectService elasticsearchSelectService;      /**      * 速度快  输入的内容立即返回  对字段类型要求多节省存储空间  时间复杂度O(1),做建议不做纠错      * 感觉和prefixQuery 前缀查询 差不多.....      * <p>      * 搜索补全必须定义 这个属性(      * //                  @CompletionField(analyzer = "ik_smart", searchAnalyzer = "ik_smart", maxInputLength = 100)      * //                  private Completion completion;)      * //      给Completion属性赋值: new Completion(new String[]{"山东省泰安市岱岳区"}))      * //                  :里面的值就是被自动补全的值      *      * @param fieldName : 要用哪个字段进行标题联想(必须是这个@CompletionField注解所标注的类型为Completion的字段名)      * @param text      : 被补全的值(比如传的是山东 可能就能给补全为 山东省)      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @return java.util.List<java.lang.String>      * @explain : 搜索补全功能 比如在输入框输入(天上)下面就自动补全 (天上人间)(天上边的彩霞)(....)      * @Author Mhh      * @Date 2021/12/22 16:51      */      @PostMapping("completionSuggestion")     public List<String> completionSuggestion(String fieldName, String text, Class<?> classType) {         return elasticsearchSelectService.completionSuggestion(fieldName, text, classType);     }  }  8.20.2 service层  import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.index.query.Operator; import org.springframework.data.domain.Sort;  import java.io.IOException; import java.util.List; import java.util.Map;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 查询  */ public interface ElasticsearchSelectService {      /**      * 速度要快  输入的内容立即返回  对字段类型要求多节省存储空间  时间复杂度O(1),做建议不做纠错      * 感觉和prefixQuery 前缀查询 差不多.....      * <p>      * 搜索补全必须定义 这个属性(      * //                  @CompletionField(analyzer = "ik_smart", searchAnalyzer = "ik_smart", maxInputLength = 100)      * //                  private Completion completion;)      * //      给Completion属性赋值: new Completion(new String[]{"山东省泰安市岱岳区"}))      * //                  :里面的值就是被自动补全的值      *      * @param fieldName : 要用哪个字段进行标题联想(必须是这个@CompletionField注解所标注的类型为Completion的字段名)      * @param text      : 被补全的值(比如传的是山东 可能就能给补全为 山东省)      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @return java.util.List<java.lang.String>      * @explain : 搜索补全功能 比如在输入框输入(天上)下面就自动补全 (天上人间)(天上边的彩霞)(....)      * @Author Mhh      * @Date 2021/12/22 16:51      */     public List<String> completionSuggestion(String fieldName, String text, Class<?> classType);   }  8.20.3 serviceimpl层  import com.it.mhh.elasticsearch.service.ElasticsearchSelectService; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.indices.AnalyzeRequest; import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.index.query.*; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.elasticsearch.search.aggregations.metrics.*; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.suggest.*; import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder; import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder; import org.elasticsearch.search.suggest.term.TermSuggestionBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.stereotype.Service;  import java.io.IOException; import java.lang.reflect.Field; import java.util.*; import java.util.stream.Collectors;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @Service public class ElasticsearchSelectServiceImp implements ElasticsearchSelectService {      @Autowired     private ElasticsearchRestTemplate elasticsearchRestTemplate;       /**      * 速度要快  输入的内容立即返回  对字段类型要求多节省存储空间  时间复杂度O(1),做建议不做纠错      * 感觉和prefixQuery 前缀查询 差不多.....      * <p>      * 搜索补全必须定义 这个属性(      * //                  @CompletionField(analyzer = "ik_smart", searchAnalyzer = "ik_smart", maxInputLength = 100)      * //                  private Completion completion;)      * //      给Completion属性赋值: new Completion(new String[]{"山东省泰安市岱岳区"}))      * //                  :里面的值就是被自动补全的值      *      * @param fieldName : 要用哪个字段进行标题联想(必须是这个@CompletionField注解所标注的类型为Completion的字段名)      * @param text      : 被补全的值(比如传的是山东 可能就能给补全为 山东省)      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @return java.util.List<java.lang.String>      * @explain : 搜索补全功能 比如在输入框输入(天上)下面就自动补全 (天上人间)(天上边的彩霞)(....)      * @Author Mhh      * @Date 2021/12/22 16:51      */     public List<String> completionSuggestion(String fieldName, String text, Class<?> classType) {         //定义反参容器         List<String> stringList = new ArrayList<>();         //构建搜索建议补全对象         CompletionSuggestionBuilder completionSuggestionBuilder = SuggestBuilders.                 completionSuggestion(fieldName). // 使用fieldName入参进行标题联想(这里就是索引被@CompletionField注解标注的字段)                 prefix(text).       // 关键字(参数传此)                 skipDuplicates(true)// 重复过滤                 //.size(100)       // 匹配数量                 ;         //创建搜索提示对象 进行封装搜索补全         SuggestBuilder suggestBuilder = new SuggestBuilder();         //  completionSuggestionBuilder:随便起的搜索补全的名字         suggestBuilder.addSuggestion("completionSuggestionBuilder", completionSuggestionBuilder);         //查询es并反参         SearchResponse searchResponse = elasticsearchRestTemplate.suggest(suggestBuilder, elasticsearchRestTemplate.getIndexCoordinatesFor(classType));         //获取反参中的搜索补全结果         Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestionBuilder = searchResponse.getSuggest().getSuggestion("completionSuggestionBuilder");          // 处理返回         List<String> suggests = suggestionBuilder.getEntries().stream().map(x -> x.getOptions().stream().map(y -> y.getText().toString()).collect(Collectors.toList())).findFirst().get();         // 将搜索补全内容保存到容器返回         for (String suggest : suggests) {             stringList.add(suggest);             System.err.println("suggest = " + suggest);         }         return stringList;     }  }  SuggestBuilders查询文档 (completionSuggestion—搜索建议补全) 测试结果: 标题联想必须是被@CompletionField注解所标注的类型为Completion对象的属性。 定义对象关键词索引 要完成补全搜索,必须要用到特殊的数据类型completion。 建议补全的值都是在存储时保存在completion里的值。 import com.it.mhh.elasticsearch.controller.ElasticsearchSelectController; import com.it.mhh.elasticsearch.entity.Student; import com.it.mhh.elasticsearch.entity.Teacher; import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.index.query.Operator; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Sort; import org.springframework.test.context.junit4.SpringRunner;  import java.io.IOException; import java.util.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @SpringBootTest @RunWith(SpringRunner.class) public class ElasticsearchSelectControllerTest {      @Autowired     private ElasticsearchSelectController elasticsearchSelectController;//es的查询      /**      * @param name      : es里索引的域(字段名)      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @param values    : 一域多值, 查询的值      * @return java.util.List<T>      * @explain : 聚合对数据进行分组的求和,求数,最大值,最小值,或者其它的自定义的统计功能,      * //            es对聚合有着不错的支持,需要注意的是,在对某字段进行聚合之后,需要开启这个字段的fielddata      * //            不要对text类型的数据进行分组,会失败      * @Author Mhh      * @Date 2021/12/22 14:29      */     @Test     public void aggregationBuilder() {         List<Teacher> teacherList = elasticsearchSelectController.aggregationBuilder(                 "tAddress",                 Teacher.class,                 "山东省");         teacherList.forEach(System.err::println);     }  }      /**      * https://www.cnblogs.com/Alexephor/p/11408446.html(Elasticsearch之建议器suggester)写的很详细      * 词组建议器(phraseSuggestion)      *      * @param fieldName : 从fieldName字段中获取候选建议的字段。这是一个必需的选项,需要全局设置或根据建议设置。Keyword字段      * @param text      : 建议文本,建议文本是必需的选项,可以通过全局(多个建议器中查询相同的内容)或者按照单个建议器的格式来。      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @return java.util.List<java.lang.String>      * @explain : 词组建议器(phraseSuggestion)适合较长的字段,但是也不是万能的  做纠正(Keyword字段)      * @Author Mhh      * @Date 2021/12/23 15:40      */     @Test     public void phraseSuggestion() {         List<String> stringList = elasticsearchSelectController.phraseSuggestion(                 "tFamous",                 "吾之初心永世不完",                 Teacher.class);         stringList.forEach(System.err::println);     }  } ————————————————                  Q原文链接:https://blog.csdn.net/JAVA_MHH/article/details/122172506 
总条数:696 到第
上滑加载中