-
【问题来源】【必填】南网电网【问题简要】【必填】resetSkill坐席签入后签入技能队列时,autoFlag传true默认签入所有的技能队列时,是否需要配置skillid签入技能队列号,该签入技能队列号该如何取值【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】resetSkill坐席签入后签入技能队列时,autoFlag传true默认签入所有的技能队列时,是否需要配置skillid签入技能队列号,该签入技能队列号该如何取值【日志或错误截图】【可选】无【附件】【可选】无
-
resetSkill坐席签入后签入技能队列时,autoFlag传true默认签入所有的技能队列时,是否需要配置skillid签入技能队列号,该签入技能队列号该如何取值
-
synchronized关键字是Java中用于控制多线程访问共享资源的一种机制,它确保同一时刻只有一个线程可以执行某个方法或代码块。其底层原理主要基于JVM(Java虚拟机)中的Monitor(监视器)机制来实现。以下是synchronized关键字的详细底层原理:1. Monitor机制Monitor(监视器):是Java虚拟机中的一种内置同步机制,每个Java对象都有一个与之关联的Monitor。Monitor通过内部的一些同步原语(如lock和unlock指令)来实现线程间的互斥访问和协调。锁的获取与释放:当一个线程尝试进入被synchronized修饰的代码块或方法时,它首先需要获取对象的Monitor。如果Monitor已被其他线程持有,则当前线程将被阻塞,直到Monitor变为可用状态。当线程完成执行后,它会释放Monitor,以便其他线程可以获取并继续执行。2. 锁的升级偏向锁(Biased Locking):Java 6及以后版本中引入的一种锁优化技术。如果某个对象锁在整个运行过程中只被一个线程持有,那么JVM会将该锁升级为偏向锁,以减少锁获取的开销。偏向锁通过CAS(Compare-And-Swap)操作将线程ID设置到对象的Mark Word中,以表示该对象锁已被当前线程占有。轻量级锁(Lightweight Locking):当存在多个线程竞争同一个锁时,偏向锁会升级为轻量级锁。轻量级锁通过CAS操作在JVM的栈帧中创建锁记录(Lock Record),并将锁对象的Mark Word指向该锁记录。如果锁记录中的Mark Word与锁对象的Mark Word一致,则当前线程获取锁成功;否则,表示存在锁竞争,可能需要升级到重量级锁。重量级锁(Heavyweight Locking):当轻量级锁无法满足需求时(如多个线程长时间竞争同一个锁),锁会升级为重量级锁。重量级锁依赖于操作系统层面的互斥量(Mutex)来实现,涉及到用户态和内核态的切换,成本较高。3. 锁的释放与等待/通知锁的释放:当线程完成synchronized代码块的执行后,它会通过unlock指令释放Monitor,并将Monitor返还给对象池,以便其他线程可以获取。等待/通知:在synchronized代码块中,线程可以通过调用对象的wait()方法进入等待状态,并释放Monitor。其他线程可以通过调用对象的notify()或notifyAll()方法唤醒等待的线程。这些操作都是通过操纵与对象关联的Monitor来实现的。4. 锁的获取流程线程尝试获取对象的Monitor。如果Monitor未被其他线程持有,则当前线程获取Monitor并继续执行。如果Monitor已被其他线程持有,则当前线程进入阻塞状态,等待Monitor变为可用。当Monitor变为可用时,当前线程重新尝试获取Monitor。5. 注意事项synchronized关键字是JVM层面的机制,其实现依赖于JVM的具体实现(如HotSpot)。锁的升级和降级是JVM自动进行的,无需开发者手动干预。在使用synchronized时,应尽量避免长时间持有锁,以减少线程阻塞和死锁的风险。综上所述,synchronized关键字的底层原理主要基于JVM中的Monitor机制,通过锁的获取、释放、升级和降级等机制来实现线程间的同步控制。
-
在Java中,锁是并发编程中用来控制多个线程对共享资源的访问的重要机制。Java的synchronized关键字和ReentrantLock等都是实现锁的方式。Java虚拟机(JVM)为了提高锁的性能,引入了偏向锁(Biased Locking)、轻量级锁(Lightweight Locking)和重量级锁(Heavyweight Locking)三种锁状态。这三种锁状态的转换主要是为了提高锁的性能,减少线程在获取锁时的开销。1. 偏向锁(Biased Locking)偏向锁是Java 6引入的一种锁优化,它假设大多数情况下,锁不存在多线程竞争,锁总是由同一线程多次获得。当一个线程访问同步块并获取锁时,锁会进入偏向模式,此时Mark Word里会存储锁偏向的线程ID,以后该线程进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下锁是否还指向当前线程ID即可。如果测试成功,表示线程已经获得了锁。如果失败,则尝试使用轻量级锁。偏向锁的优势在于,当锁只被一个线程访问时,可以极大地减少锁的获取和释放的开销。2. 轻量级锁(Lightweight Locking)轻量级锁是相对于使用操作系统互斥量(Mutex)来实现的传统锁而言的。它用于解决在没有多线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。当锁处于偏向锁状态,但发生线程竞争,或者锁一开始就不是偏向锁时,就会尝试使用轻量级锁。轻量级锁通过CAS(Compare-And-Swap)操作来尝试把锁对象的Mark Word更新为指向当前线程的栈帧中的Lock Record的指针。如果成功,当前线程就获得了锁;如果失败,表示有其他线程竞争锁,锁会膨胀为重量级锁。3. 重量级锁(Heavyweight Locking)重量级锁是传统意义上的锁,它依赖于底层操作系统的Mutex Lock来实现。当锁膨胀为重量级锁时,意味着线程竞争非常激烈,JVM会调用操作系统的互斥量来实现同步控制。线程阻塞和唤醒都需要操作系统的帮助,因此开销较大。总结偏向锁、轻量级锁和重量级锁是Java虚拟机为了提高锁的性能而引入的三种锁状态。它们之间的转换是JVM根据锁的竞争情况自动进行的。偏向锁适用于锁只被一个线程访问的场景;轻量级锁适用于锁被少量线程竞争的场景;重量级锁适用于锁被多个线程激烈竞争的场景。这三种锁状态的存在,使得Java的锁机制能够灵活应对不同场景下的并发需求,从而提高程序的性能。
-
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的性能问题。
-
【问题来源】 【南方电网】【问题简要】AICC-CMS鉴权失败【问题类别】 【可选问题分类:CMS开发】【AICC解决方案版本】 【AICC可选择版本:AICC 24.200】【期望解决时间】2024年9月1日【问题现象描述】 1、使用鉴权demo(支持8.15版本)生成Authorization在ApiPost中可以调通健康检查接口(URL:https://ip:port/rest/cmsapp/v1/ping)。 2、Java代码中使用文档提供示例代码报javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: Certificates do not conform to algorithm constraints。【日志或错误截图】调用方法:使用文档demo 健康检查接口报错内容:
-
探索高效能搜索引擎新境界: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
-
背景 目前公司的一个老项目,查询贼慢,需要想办法提升一下速度,于是就想到了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知识,准备重新对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(简称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
-
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模式下怎么进行简单的增删改查操作,这边增删改查操作有点类似于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
-
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
-
目录 一、什么是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 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
上滑加载中
推荐直播
-
华为云码道 × 仓颉编程:工程化AI编码探索2026/05/27 周三 19:00-21:00
刘俊杰-华为云仓颉语言专家/李炎-华为云码道技术专家/王智鹏-OpenCangjie开源社区发起人
本场直播围绕华为云仓颉语言与华为云码道的深度结合,展示华为云智能编程从零基础到高效落地的完整生态能力。以华为云码道为引擎,仓颉语言为载体,带给大家日常提效、趣味创新到极速量产的开发体验。
回顾中
热门标签