diff --git "a/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/010.\347\256\200\344\273\213.md" "b/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/010.\347\256\200\344\273\213.md" index 0a44c47..c1bc936 100644 --- "a/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/010.\347\256\200\344\273\213.md" +++ "b/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/010.\347\256\200\344\273\213.md" @@ -20,6 +20,7 @@ Easy-Es(简称EE)是一款基于ElasticSearch(简称Es)官方提供的RestHi - **屏蔽语言差异:** 开发者只需要会MySQL语法即可使用Es,真正做到一通百通,无需学习枯燥易忘的Es语法,Es使用相对MySQL较低频,学了长期不用也会忘,没必要浪费这时间.开发就应该专注于业务,省下的时间去撸铁,去陪女朋友陪家人,不做资本家的韭菜 - **代码量极少:** 与直接使用RestHighLevelClient相比,相同的查询平均可以节省3-80倍左右的代码量 - **零魔法值:** 字段名称直接从实体中获取,无需输入字段名称字符串这种魔法值,提高代码可读性,杜绝因字段名称修改而代码漏改带来的Bug +- **拓展性强:** 框架内置混合查询,原生查询,无论是索引层面还是CRUD层面,都拥有>=原生API的拓展性,无需担心引入后影响开发,只要是ES本身能支持的功能使用Easy-ES都可以搞定 - **零额外学习成本:** 开发者只要会国内最受欢迎的Mybatis-Plus语法,即可无缝迁移至EE,EE采用和前者相同的语法,消除使用者额外学习成本,直接上手,爽 - **降低开发者门槛:** Es通常需要中高级开发者才能驾驭,但通过接入EE,即便是只了解ES基础的初学者也可以轻松驾驭ES完成绝大多数需求的开发,可以提高人员利用率,降低企业成本 @@ -46,7 +47,7 @@ Easy-Es(简称EE)是一款基于ElasticSearch(简称Es)官方提供的RestHi ## 参与贡献 -尽管目前Easy-Es诞生不算太久,但由于站在巨人的肩膀上(RestHighLevelClient和Mybatis-Plus),这是一款出道即巅峰的框架,这么说并不是说它写得有多好,而是它融合了两款目前非常优秀框架的优点,以及MP的用户习惯和用户生态,这决定了它起点的高度,未来可期,所以在此欢迎各路好汉一起来参与完善 Easy-Es,我们期待你的 PR! +在Easy-Es出道前,由于ES本身API的高复杂性,市面上除了SpringData一家在做ES-ORM,Easy-Es的出现填补了国内ES-ORM十多年的空缺,并且MP的设计理念天然优于SpringData,目前经过长期不懈的努力,我们在国内ES-ORM这片市场已经稳居第一,但我们并不满足于此,我们的目标是成为全球最受欢迎的ES-ORM,所以在此欢迎各路好汉一起来参与完善 Easy-Es,我们期待你的 PR! - 贡献代码:代码地址 [Easy-ES](https://gitee.com/dromara/easy-es),欢迎提交 Issue 或者 Pull Requests - 维护文档:文档地址 [Easy-ES](https://github.com/xpc1024/easy-es-home-pages),欢迎参与翻译和修订 diff --git "a/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/030.\351\241\276\350\231\221\347\262\211\347\242\216.md" "b/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/030.\351\241\276\350\231\221\347\262\211\347\242\216.md" index 965fb8d..0cc05fa 100644 --- "a/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/030.\351\241\276\350\231\221\347\262\211\347\242\216.md" +++ "b/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/030.\351\241\276\350\231\221\347\262\211\347\242\216.md" @@ -17,7 +17,7 @@ permalink: /pages/98acf6/ ![1](https://iknow.hs.net/a057bb79-2f27-4785-b177-87f3056fe920.jpg) -我们先来看EE在整个查询过程中做了什么事? 其核心事宜总结起来就2件: +我们先来看EE在整个查询过程中做了什么事? 其核心事宜总结起来就2件(当然只是为了抽象后方便您理解,实际复杂度只有您读完源码或用ES原生API开发后才会有所体会): 1. 把用户输入的MySQL语法(Mybatis-Plus语法)转换成RestHighLevel语法,然后调用RestHighLevelClient执行本次查询 diff --git "a/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/080.\346\263\250\350\247\243.md" "b/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/080.\346\263\250\350\247\243.md" index d9ae88f..36e10d2 100644 --- "a/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/080.\346\263\250\350\247\243.md" +++ "b/docs/10.v2.x\346\226\207\346\241\243/010.\345\277\253\351\200\237\345\205\245\351\227\250/080.\346\263\250\350\247\243.md" @@ -29,6 +29,31 @@ public class Application{ ::: +## [@Settings](https://gitee.com/dromara/easy-es/blob/master/easy-es-annotation/src/main/java/org/dromara/easyes/annotation/Settings.java) + +- 描述:索引Settings信息注解,可设置ES索引中的Settings信息 + +- 使用位置:实体类 + +```java +@Settings(shardsNum = 3, replicasNum = 2, settingsProvider = MySettingsProvider.class) +public class Document { + // 省略其它字段 +} +``` +| 属性| 类型 | 必须指定| 默认值 | 描述 | +|---|--------|---|-------|-----------| +|shardsNum| Int |否| 1 | 索引分片数,默认值为1 | +|replicasNum| Int |否| 1 | 索引副本数,默认值为1 | +|maxResultWindow| Int |否| 10000 | 默认最大返回数 | +|refreshInterval| String |否| "" | 索引的刷新间隔 es默认值为1s ms:表示毫秒 s:表示秒 m:表示分钟 | +|settingsProvider| Class |否| DefaultSettingsProvider.class | 自定义settings提供类 默认为DefaultSettingsProvider空实现 如需自定义,可继承此类并覆写getSettings方法 将settings信息以Map返回 | + +:::tip 温馨提示 +- maxResultWindow:默认最大返回数,默认值为10000,超过此值推荐使用searchAfter或滚动查询等方式,性能更好,详见拓展功能章节. 当此值调整至大于1W后,需要更新索引并同步开启配置文件中的enable-track-total-hits=true方可生效 +- settingsProvider:自定义settings提供类,当小部分场景下,框架内置的这些参数不足以满足您对索引Settings的设置时,您可以自定义类并继承DefaultSettingsProvider,覆写其中的getSettings方法,将您自定义的settings信息以Map返回,通过此方式可以支持所有ES能够支持的Settings + + ## [@IndexName](https://gitee.com/dromara/easy-es/blob/master/easy-es-annotation/src/main/java/org/dromara/easyes/annotation/IndexName.java) - 描述:索引名注解,标识实体类对应的索引 对应MP的@TableName注解,在v0.9.40之前此注解为@TableName. @@ -45,14 +70,9 @@ public class Document { | 属性| 类型| 必须指定|默认值 |描述 | |---|---|---|---|---| |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作用的路由| |refreshPolicy|Enum|否|NONE|索引数据刷新策略,默认为不刷新,其取值参考RefreshPolicy枚举类,一共有3种刷新策略| @@ -67,7 +87,6 @@ public class Document { - keepGlobalPrefix选项,(0.9.4+版本才支持)默认值为false,是否保持使用全局的 indexPrefix 的值: - 既配置了全局tablePrefix,@TableName注解又指定了value值时,此注解选项才会生效,如果其值为true,则框架最终使用的索引名称为:全局indexPrefix+此注解的value,例如:dev_document - 此注解选项用法和MP中保持一致. -- 其中shardNum为分片数,replicasNum为副本数,如果不指定,默认值均为1 - RefreshPolicy 数据刷新策略 - None 不刷新,es默认的策略,此策略下数据变更后有一定延迟才会生效,但写入性能最好,通常情况下使用此默认策略即可 - IMMEDIATE 立即刷新,适用于对数据延迟敏感的业务场景,但消耗的系统资源最大 @@ -295,6 +314,79 @@ public class Document { | value | String |否|""| 数据源名称,与配置文件中指定的一致 | +## [@Join](https://gitee.com/dromara/easy-es/blob/master/easy-es-annotation/src/main/java/org/dromara/easyes/annotation/Join.java) + +- 描述:Join父子类型注解,用于父子类型索引关系表达 + +- 使用位置:父子类型根节点Root类 + +```java +/** + * Document根文档有子文档Author(作者)和Comment(评论),其中Author还有个子文档Contact(联系方式) + * Join父子类型结构如下所示 + * Document + * / \ + * Comment Author + * \ + * Contact + * 上述结构可用@Join注解和@Node注解来表达,可参考下面案例 + **/ +@Join(nodes = {@Node(parentClass = Document.class, childClasses = {Author.class, Comment.class}), @Node(parentClass = Author.class, childClasses = Contact.class)}) +public class Document { + // 省略其它无关字段 +} +``` + +| 属性| 类型 | 必须指定 |默认值 | 描述 | +|---|--------------|------|---|-----------------------------------------| +|joinField| String | 否 |""| 索引中的join字段名称 默认为joinField(推荐) | +|rootAlias| String | 否 |""| 根节点别名 不指定则默认使用加了当前注解的根类的名称小写作为根节点别名(推荐) | +|nodes| Annotation[] | 是 |""| 非根节点列表 | + +:::tip 温馨提示 + +更多关于此注解的使用可以参考Join父子类型章节文档 + +::: + +## [@Node](https://gitee.com/dromara/easy-es/blob/master/easy-es-annotation/src/main/java/org/dromara/easyes/annotation/Node.java) + +- 描述:Join父子类型注解,用于父子类型索引关系表达 + +- 使用位置:父子类型根节点Root类 + +```java +/** + * Document根文档有子文档Author(作者)和Comment(评论),其中Author还有个子文档Contact(联系方式) + * Join父子类型结构如下所示 + * Document + * / \ + * Comment Author + * \ + * Contact + * 上述结构可用@Join注解和@Node注解来表达,可参考下面案例 + **/ +@Join(nodes = {@Node(parentClass = Document.class, childClasses = {Author.class, Comment.class}), @Node(parentClass = Author.class, childClasses = Contact.class)}) +public class Document { + // 省略其它无关字段 +} +``` + +| 属性| 类型 | 必须指定 | 默认值 | 描述 | +|---|--------|------|-----|-----------------------------------------| +|parentAlias| String | 否 | "" | 父文档别名 非必填,不指定时默认值为parentClass类名小写(推荐) | +|parentClass| Class | 是 | - | 父文档实体类,必填项 | +|childAliases| String | 否 | "" | 子文档别名列表,不指定则为子文档类名小写列表(推荐) 若要自定义必须与childClasses数量和顺序一致 | +|childClasses| Class | 是 | - | 子文档实体类列表,必填项 | + + +:::tip 温馨提示 + +更多关于此注解的使用可以参考Join父子类型章节文档 + +::: + + ## 其它注解 除了上面这几个高频注解,项目中偶尔还会用到一些其它注解,比如比如拦截器注解@Intercepts等注解,我们会在后面具体的章节详细介绍,此处仅列出几个必须掌握的注解,其它注解按需学习即可。 \ No newline at end of file diff --git "a/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/010.\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250/020.\347\264\242\345\274\225\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250.md" "b/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/010.\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250/020.\347\264\242\345\274\225\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250.md" index d160974..6bda90e 100644 --- "a/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/010.\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250/020.\347\264\242\345\274\225\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250.md" +++ "b/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/010.\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250/020.\347\264\242\345\274\225\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250.md" @@ -5,7 +5,7 @@ permalink: /pages/060052/ --- ## LambdaEsIndexWrapper :::tip -索引的CRUD中的相关条件依托于此构造器进行封装 +索引的CRUD中的相关条件依托于此构造器进行封装,此方式为框架最早提供的一种索引创建方式,现阶段已不推荐,推荐使用自定义注解+mapper.createIndex()方式一键创建,更简单,更便捷,可参考索引托管章节文档. ::: ### API说明 diff --git "a/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/020.\347\264\242\345\274\225CRUD/010.\347\264\242\345\274\225\346\211\230\347\256\241\346\250\241\345\274\217.md" "b/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/020.\347\264\242\345\274\225CRUD/010.\347\264\242\345\274\225\346\211\230\347\256\241\346\250\241\345\274\217.md" index af30606..5169b75 100644 --- "a/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/020.\347\264\242\345\274\225CRUD/010.\347\264\242\345\274\225\346\211\230\347\256\241\346\250\241\345\274\217.md" +++ "b/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/020.\347\264\242\345\274\225CRUD/010.\347\264\242\345\274\225\346\211\230\347\256\241\346\250\241\345\274\217.md" @@ -4,118 +4,14 @@ date: 2023-03-18 10:00:00 permalink: /pages/cc15ba/ --- :::tip 前言 -ES难用,索引首当其冲,索引的创建和更新不仅复杂,而且难于维护,一旦索引有变动,就必须面对索引重建带来的服务停机和数据丢失等问题... 尽管ES官方提供了索引别名机制来解决问题,但门槛依旧很高,步骤繁琐,在生产环境中由人工操作非常容易出现失误带来严重的问题. 为了解决这些痛点,Easy-Es提供了多种策略,将用户彻底从索引的维护中解放出来,我们提供了多种索引处理策略,来满足不同用户的个性化需求. 通过对索引的初体验,相信您也可以更深体会到EE的成熟度和易用性. 其中全自动平滑模式,首次采用全球领先的"哥哥你不用动,EE我全自动"的模式,索引的创建,更新,数据迁移等所有全生命周期均无需用户介入,由EE全自动完成,过程零停机,连索引类型都可智能自动推断,一条龙服务,包您满意.是全球开源首创,充分借鉴了JVM垃圾回收算法思想,史无前例,尽管网上已有平滑过渡方案,但并非全自动,过程依旧靠人工介入,我为EE代言,请放心将索引托管给EE,索引只有在彻底迁移成功才会删除旧索引,否则均不会对原有索引和数据造成影响,发生任何意外均能保留原索引和数据,所以安全系数很高. +ES难用,索引首当其冲,索引的创建和更新不仅复杂,而且难于维护,一旦索引有变动,就必须面对索引重建带来的服务停机和数据丢失等问题... 尽管ES官方提供了索引别名机制来解决问题,但门槛依旧很高,步骤繁琐,在生产环境中由人工操作非常容易出现失误带来严重的问题. +为了解决这些痛点,Easy-Es提供了多种策略,将用户彻底从索引的维护中解放出来,我们提供了多种索引处理策略,来满足不同用户的个性化需求. 通过对索引的初体验,相信您也可以更深体会到EE的成熟度和易用性. +其中全自动平滑模式,首次采用全球领先的"哥哥你不用动,EE我全自动"的模式,索引的创建,更新,数据迁移等所有全生命周期均无需用户介入,由EE全自动完成,过程零停机,连索引类型都可智能自动推断,一条龙服务,包您满意.是全球开源首创,充分借鉴了JVM垃圾回收算法思想,史无前例,尽管网上已有平滑过渡方案,但并非全自动,过程依旧靠人工介入,使用Easy-Es后则无这些烦恼. ::: -> **温馨提示:** 新手上路可尽量选择手动挡一键模式,老司机(熟悉每种模式的原理及源码)您请随意~ 追求生产环境稳定性,建议您采用手动挡模式,我们手动挡也提供了非常友好的一键创建功能,使用起来也是甜甜的. 自动挡模式,建议您在充分了解其运作原理和源码后再上生产,否则不少小白在没弄明白原理和如何正确配置就无脑上生产,容易被自己坑到,弄明白了请随便,我们的平滑模式实际上也是非常安全的. +> **温馨提示:** 新手上路可尽量选择手动挡一键模式,老司机(熟悉每种模式的原理及源码)或开发环境您请随意~ 追求生产环境稳定性,建议您采用手动挡模式,我们手动挡也提供了非常友好的一键创建功能,使用起来也是甜甜的. 自动挡模式(不建议上生产,因为大多数开发者无法权衡好迁移时间,致使最大重试次数和重试间隔配置不理想导致迁移失败),建议您在充分了解其运作原理和源码后再上生产,否则不少小白在没弄明白原理和如何正确配置就无脑上生产,容易被自己坑到,弄明白了请随便. -## 模式一:自动托管之平滑模式(自动挡-雪地模式) 默认开启此模式 - -> 在此模式下,索引的创建更新数据迁移等全生命周期用户均不需要任何操作即可完成,过程零停机,用户无感知,可实现在生产环境的平滑过渡,类似汽车的自动档-雪地模式,平稳舒适,彻底解放用户,尽情享受自动驾驶的乐趣! -> 需要值得特别注意的是,在自动托管模式下,系统会自动生成一条名为ee-distribute-lock的索引,该索引为框架内部使用,用户可忽略,若不幸因断电等其它因素极小概率下发生死锁,可删除该索引即可.另外,在使用时如碰到索引变更,原索引名称可能会被追加后缀_s0或_s1,不必慌张,这是全自动平滑迁移零停机的必经之路,索引后缀不影响使用,框架会自动激活该新索引.关于_s0和_s1后缀,在此模式下无法避免,因为要保留原索引数据迁移,又不能同时存在两个同名索引,凡是都是要付出代价的,如果您不认可此种处理方式,可继续往下看,总有一种适合您。 - -其核心处理流程梳理如下图所示,不妨结合源码看,更容易理解: - - -![平滑模式.png](https://iknow.hs.net/fbf59164-dd62-4b88-9483-b222a3c3b52b.png) - - -## 模式二:自动托管之非平滑模式(自动挡-运动模式) - -在此模式下,索引额创建及更新由EE全自动异步完成,但不处理数据迁移工作,速度极快类似汽车的自动挡-运动模式,简单粗暴,弹射起步! 适合在开发及测试环境使用,当然如果您使用logstash等其它工具来同步数据,亦可在生产环境开启此模式,在此模式下不会出现_s0和_s1后缀,索引会保持原名称. - -![非平滑模式.png](https://iknow.hs.net/0b1b4d41-cac5-410f-bae1-9a0b3557da75.png) - -:::tip -以上两种自动模式中,索引信息主要依托于实体类,如果用户未对该实体类进行任何配置,EE依然能够根据字段类型智能推断出该字段在ES中的存储类型,此举可进一步减轻开发者负担,对刚接触ES的小白更是福音. - -当然,仅靠框架自动推断是不够的,我们仍然建议您在使用中尽量进行详细的配置,以便框架能自动创建出生产级的索引.举个例子,例如String类型字段,框架无法推断出您实际查询中对该字段是精确查询还是分词查询,所以它无法推断出该字段到底用keyword类型还是text类型,倘若是text类型,用户期望的分词器是什么? 这些都需要用户通过配置告诉框架,否则框架只能按默认值进行创建,届时将不能很好地完成您的期望. - -自动推断类型的优先级 < 用户通过注解指定的类型优先级 -::: - -自动推断映射表: - -| 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 | -| ... | ... | - - ->"自动挡"模式下的最佳实践示例: - -```java -@Data -@IndexName(shardsNum = 3,replicasNum = 2) // 可指定分片数,副本数,若缺省则默认均为1 -public class Document { - /** - * es中的唯一id,如果你想自定义es中的id为你提供的id,比如MySQL中的id,请将注解中的type指定为customize,如此id便支持任意数据类型) - */ - @IndexId(type = IdType.CUSTOMIZE) - private Long id; - /** - * 文档标题,不指定类型默认被创建为keyword_text类型,可进行精确查询 - */ - private String title; - /** - * 文档内容,指定了类型及存储/查询分词器 - */ - @HighLight(mappingField="highlightContent") - @IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_MAX_WORD) - private String content; - /** - * 作者 加@TableField注解,并指明strategy = FieldStrategy.NOT_EMPTY 表示更新的时候的策略为 创建者不为空字符串时才更新 - */ - @IndexField(strategy = FieldStrategy.NOT_EMPTY) - private String creator; - /** - * 创建时间 - */ - @IndexField(fieldType = FieldType.DATE, dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis") - private String gmtCreate; - /** - * es中实际不存在的字段,但模型中加了,为了不和es映射,可以在此类型字段上加上 注解@TableField,并指明exist=false - */ - @IndexField(exist = false) - private String notExistsField; - /** - * 地理位置经纬度坐标 例如: "40.13933715136454,116.63441990026217" - */ - @IndexField(fieldType = FieldType.GEO_POINT) - private String location; - /** - * 图形(例如圆心,矩形) - */ - @IndexField(fieldType = FieldType.GEO_SHAPE) - private String geoLocation; - /** - * 自定义字段名称 - */ - @IndexField(value = "wu-la") - private String customField; - - /** - * 高亮返回值被映射的字段 - */ - private String highlightContent; -} -``` - - -## 模式三:手动模式(手动挡) +## 模式一:手动模式(手动挡,默认开启此模式,生产环境推荐) 在此模式下,索引的所有维护工作EE框架均不介入,由用户自行处理,EE提供了开箱即用的索引CRUD相关API,您可以选择使用该API手动维护索引,由于API高度完善,尽管是手动挡,但使用起来依旧简单到爆,一行代码搞定索引创建.当然您亦可通过es-head等工具来维护索引,总之在此模式下,您拥有更高的自由度,比较适合那些质疑EE框架的保守用户或追求极致灵活度的用户使用,类似汽车的手动挡 @@ -152,7 +48,8 @@ public class Document { * 实体类信息 **/ @Data -@IndexName(shardsNum = 3, replicasNum = 2, keepGlobalPrefix = true) +@Settings(shardsNum = 3, replicasNum = 2) +@IndexName(value = "easyes_document") public class Document { /** * es中的唯一id,如果你想自定义es中的id为你提供的id,比如MySQL中的id,请将注解中的type指定为customize或直接在全局配置文件中指定,如此id便支持任意数据类型) @@ -174,7 +71,7 @@ public class Document { ``` ```java - @Test + @Test public void testCreateIndexByEntity() { // 然后通过该实体类的mapper直接一键创建,非常傻瓜级 documentMapper.createIndex(); @@ -185,10 +82,12 @@ public class Document { 实体类中的注解用法可参考注解章节,整体比较傻瓜级,和MP中的注解用法高度相似. ::: --方式二:通过api创建,每个需要被索引的字段都需要处理,比较繁琐,但灵活性最好,支持所有es能支持的所有索引创建,供0.01%场景使用(不推荐) + +-方式二:通过api创建,不推荐,此方式是框架诞生最早提供的方案,目前已过时,每个需要被索引的字段都需要处理,比较繁琐 ```java @Test + @Deprecated public void testCreatIndex() { LambdaEsIndexWrapper wrapper = new LambdaEsIndexWrapper<>(); // 此处简单起见 索引名称须保持和实体类名称一致,字母小写 后面章节会教大家更如何灵活配置和使用索引 @@ -224,21 +123,127 @@ public class Document { 由于ES索引改动自动重建的特性,因此本接口设计时将创建索引所需的mapping,settings,alias信息三合一了,尽管其中每一项配置都可缺省,但我们仍建议您在创建索引前提前规划好以上信息,可以规避后续修改带来的不必要麻烦,若后续确有修改,您仍可以通过别名迁移的方式(推荐,可平滑过渡),或删除原索引重新创建的方式进行修改. ::: + +## 模式二:自动托管之平滑模式(自动挡-雪地模式) 开发环境数据量不大的情况下推荐使用,省心 + +> 在此模式下,索引的创建更新数据迁移等全生命周期用户均不需要任何操作即可完成,过程零停机,用户无感知,可实现在生产环境的平滑过渡,类似汽车的自动档-雪地模式,平稳舒适,彻底解放用户,尽情享受自动驾驶的乐趣! +> 需要值得特别注意的是,在自动托管模式下,系统会自动生成一条名为ee-distribute-lock的索引,该索引为框架内部使用,用户可忽略,若不幸因断电等其它因素极小概率下发生死锁,可删除该索引即可.另外,在使用时如碰到索引变更,原索引名称可能会被追加后缀_s0或_s1,不必慌张,这是全自动平滑迁移零停机的必经之路,索引后缀不影响使用,框架会自动激活该新索引.关于_s0和_s1后缀,在此模式下无法避免,因为要保留原索引数据迁移,又不能同时存在两个同名索引,凡是都是要付出代价的,如果您不认可此种处理方式,可继续往下看,总有一种适合您。 + +其核心处理流程梳理如下图所示,不妨结合源码看,更容易理解: + + +![平滑模式.png](https://iknow.hs.net/fbf59164-dd62-4b88-9483-b222a3c3b52b.png) + + +## 模式二:自动托管之非平滑模式(自动挡-运动模式) + +在此模式下,索引额创建及更新由EE全自动异步完成,但不处理数据迁移工作,速度极快类似汽车的自动挡-运动模式,简单粗暴,弹射起步! 适合在开发及测试环境使用,当然如果您使用logstash等其它工具来同步数据,亦可在生产环境开启此模式,在此模式下不会出现_s0和_s1后缀,索引会保持原名称. + +![非平滑模式.png](https://iknow.hs.net/0b1b4d41-cac5-410f-bae1-9a0b3557da75.png) + +:::tip +以上两种自动模式中,索引信息主要依托于实体类,如果用户未对该实体类进行任何配置,EE依然能够根据字段类型智能推断出该字段在ES中的存储类型,此举可进一步减轻开发者负担,对刚接触ES的小白更是福音. + +当然,仅靠框架自动推断是不够的,我们仍然建议您在使用中尽量进行详细的配置,以便框架能自动创建出生产级的索引.举个例子,例如String类型字段,框架无法推断出您实际查询中对该字段是精确查询还是分词查询,所以它无法推断出该字段到底用keyword类型还是text类型,倘若是text类型,用户期望的分词器是什么? 这些都需要用户通过配置告诉框架,否则框架只能按默认值进行创建,届时将不能很好地完成您的期望. + +自动推断类型的优先级 < 用户通过注解指定的类型优先级 +::: + +自动推断映射表: + +| 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 | +| ... | ... | + + +>"自动挡"模式下的最佳实践示例: + +```java +@Data +@IndexName(shardsNum = 3,replicasNum = 2) // 可指定分片数,副本数,若缺省则默认均为1 +public class Document { + /** + * es中的唯一id,如果你想自定义es中的id为你提供的id,比如MySQL中的id,请将注解中的type指定为customize,如此id便支持任意数据类型) + */ + @IndexId(type = IdType.CUSTOMIZE) + private Long id; + /** + * 文档标题,不指定类型默认被创建为keyword_text类型,可进行精确查询 + */ + private String title; + /** + * 文档内容,指定了类型及存储/查询分词器 + */ + @HighLight(mappingField="highlightContent") + @IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_MAX_WORD) + private String content; + /** + * 作者 加@TableField注解,并指明strategy = FieldStrategy.NOT_EMPTY 表示更新的时候的策略为 创建者不为空字符串时才更新 + */ + @IndexField(strategy = FieldStrategy.NOT_EMPTY) + private String creator; + /** + * 创建时间 + */ + @IndexField(fieldType = FieldType.DATE, dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis") + private String gmtCreate; + /** + * es中实际不存在的字段,但模型中加了,为了不和es映射,可以在此类型字段上加上 注解@TableField,并指明exist=false + */ + @IndexField(exist = false) + private String notExistsField; + /** + * 地理位置经纬度坐标 例如: "40.13933715136454,116.63441990026217" + */ + @IndexField(fieldType = FieldType.GEO_POINT) + private String location; + /** + * 图形(例如圆心,矩形) + */ + @IndexField(fieldType = FieldType.GEO_SHAPE) + private String geoLocation; + /** + * 自定义字段名称 + */ + @IndexField(value = "wu-la") + private String customField; + + /** + * 高亮返回值被映射的字段 + */ + private String highlightContent; +} +``` + + ## 配置启用模式 -以上三种模式的配置,您只需要在您项目的配置文件application.properties或application.yml中加入一行配置即可: +以上三种模式的配置,您只需要在您项目的配置文件application.properties或application.yml中加入一行配置process_index_mode即可: ```yaml easy-es: - socketTimeout: 600000 # 请求通信超时时间 单位:ms 默认值600000ms 在平滑模式下,由于要迁移数据,用户可根据数据量大小调整此参数值大小,否则请求容易超时导致索引托管失败,建议您尽量给大不给小,跟那玩意一样,大点没事,太小你懂的! global-config: - process_index_mode: smoothly #smoothly:平滑模式, not_smoothly:非平滑模式, manual:手动模式 + process_index_mode: smoothly #smoothly:平滑模式, not_smoothly:非平滑模式, manual:手动模式(默认) async-process-index-blocking: true # 异步处理索引是否阻塞主线程 默认阻塞 distributed: false # 项目是否分布式环境部署,默认为true, 如果是单机运行可填false,将不加分布式锁,效率更高. reindexTimeOutHours: 72 # 重建索引超时时间 单位小时,默认72H 根据迁移索引数据量大小灵活指定 ``` -若缺省此行配置,则默认开启平滑模式. +若缺省此行配置,则默认开启手动挡模式. :::tip 温馨提示 - 自动挡模式下,如果索引托管成功,则会在控制台打印 Congratulations auto process index by Easy-Es is done ! diff --git "a/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/030.\346\225\260\346\215\256CRUD/020.\346\225\260\346\215\256CRUD.md" "b/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/030.\346\225\260\346\215\256CRUD/020.\346\225\260\346\215\256CRUD.md" index 61c63c0..41bec48 100644 --- "a/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/030.\346\225\260\346\215\256CRUD/020.\346\225\260\346\215\256CRUD.md" +++ "b/docs/10.v2.x\346\226\207\346\241\243/020.\346\240\270\345\277\203\345\212\237\350\203\275/030.\346\225\260\346\215\256CRUD/020.\346\225\260\346\215\256CRUD.md" @@ -20,19 +20,38 @@ permalink: /pages/f3ee10/ ```java // 插入一条记录,默认插入至当前mapper对应的索引 Integer insert(T entity); -// 插入一条记录 可指定具体插入的索引,多个用逗号隔开 +// 插入一条记录 可指定具体插入的路由 +Integer insert(String routing, T entity); +// 父子类型 插入一条记录 可指定路由, 父id +Integer insert(String routing, String parentId, T entity); +// 插入数据 可指定具体插入的索引,多个用逗号隔开 Integer insert(T entity, String... indexNames); +// 插入数据,可指定路由及多索引插入 +Integer insert(String routing, T entity, String... indexNames); +// 父子类型 插入数据,可指定路由,父id及多索引插入 +Integer insert(String routing, String parentId, T entity, String... indexNames); // 批量插入多条记录 Integer insertBatch(Collection entityList) +// 批量插入 可指定路由 +Integer insertBatch(String routing, Collection entityList); +// 父子类型 批量插入 可指定路由, 父id +Integer insertBatch(String routing, String parentId, Collection entityList); + // 批量插入多条记录 可指定具体插入的索引,多个用逗号隔开 Integer insertBatch(Collection entityList, String... indexNames); +// 批量插入 可指定路由及多索引 +Integer insertBatch(String routing, Collection entityList, String... indexNames); +// 父子类型 批量插入 可指定路由,父id及多索引 +Integer insertBatch(String routing, String parentId, Collection entityList, String... indexNames); ``` ##### 参数说明 -| 类型 | 参数名 | 描述 | -| --- | --- | --- | -| `T` | entity | 实体对象 | +| 类型 | 参数名 | 描述 | +|-----------------| -- | --- | +| `String` | routing | 路由 | +| `String` | indexNames | 索引列表 | +| `T` | entity | 实体对象 | | `Collection` | entityList | 实体对象集合 | :::tip 特别注意 @@ -44,22 +63,33 @@ Integer insertBatch(Collection entityList, String... indexNames); ### Delete ```java - // 根据 ID 删除 +// 根据 ID 删除 Integer deleteById(Serializable id); +// 根据 ID 删除 可指定路由 +Integer deleteById(String routing, Serializable id); // 根据 ID 删除 可指定具体的索引,多个用逗号隔开 Integer deleteById(Serializable id, String... indexNames); +// 根据 ID 删除 可指定路由及多索引 +Integer deleteById(String routing, Serializable id, String... indexNames); // 根据 entity 条件,删除记录 Integer delete(LambdaEsQueryWrapper wrapper); // 删除(根据ID 批量删除) Integer deleteBatchIds(Collection idList); +// 删除(根据ID 批量删除)可指定路由 +Integer deleteBatchIds(String routing, Collection idList); // 删除(根据ID 批量删除)可指定具体的索引,多个用逗号隔开 Integer deleteBatchIds(Collection idList, String... indexNames); +// 删除(根据ID 批量删除) 可指定路由及多索引 +Integer deleteBatchIds(String routing, Collection idList, String... indexNames); + ``` ##### 参数说明 | 类型 | 参数名 | 描述 | | --- | --- | --- | +| `String` | routing | 路由 | +| `String` | indexNames | 索引列表 | | `Wrapper` | queryWrapper | 实体包装类 QueryWrapper | | `Serializable` | id | 主键ID | | `Collection` | idList | 主键ID列表 | @@ -69,13 +99,23 @@ Integer deleteBatchIds(Collection idList, String... inde ```java //根据 ID 更新 Integer updateById(T entity); -//根据 ID 更新 可指定具体的索引,多个用逗号隔开 +// 根据 ID 更新 可指定路由 +Integer updateById(String routing, T entity); +// 根据 ID 更新 可指定具体的索引,多个用逗号隔开 Integer updateById(T entity, String... indexNames); +// 根据 ID 更新 可指定路由和多索引 +Integer updateById(String routing, T entity, String... indexNames); + // 根据ID 批量更新 Integer updateBatchByIds(Collection entityList); +// 根据ID 批量更新 可指定路由 +Integer updateBatchByIds(String routing, Collection entityList); + //根据 ID 批量更新 可指定具体的索引,多个用逗号隔开 Integer updateBatchByIds(Collection entityList, String... indexNames); +// 根据ID 批量更新 可指定路由及多索引 +Integer updateBatchByIds(String routing, Collection entityList, String... indexNames); // 根据动态条件 更新记录 Integer update(T entity, LambdaEsUpdateWrapper updateWrapper); @@ -84,6 +124,8 @@ Integer update(T entity, LambdaEsUpdateWrapper updateWrapper); ##### 参数说明 | 类型 | 参数名 | 描述 | | --- | --- | --- | +| `String` | routing | 路由 | +| `String` | indexNames | 索引列表 | | `T` | entity | 实体对象 | | `Wrapper` | updateWrapper | 实体对象封装操作类 UpdateWrapper | | `Collection` | entityList | 实体对象集合 | @@ -98,13 +140,25 @@ Integer update(T entity, LambdaEsUpdateWrapper updateWrapper); // 根据 ID 查询 T selectById(Serializable id); + // 根据 ID 查询 可指定路由 + T selectById(String routing, Serializable id); + // 根据 ID 查询 可指定具体的索引,多个用逗号隔开 T selectById(Serializable id, String... indexNames); - // 查询(根据ID 批量查询) + // 根据 ID 查询 可指定路由及多索引 + T selectById(String routing, Serializable id, String... indexNames); + + // 查询(根据ID 批量查询) List selectBatchIds(Collection idList); + // 查询(根据ID 批量查询) 可指定路由 + List selectBatchIds(String routing, Collection idList); + // 查询(根据ID 批量查询)可指定具体的索引,多个用逗号隔开 List selectBatchIds(Collection idList, String... indexNames); - // 根据动态查询条件,查询一条记录 若存在多条记录 会报错 + // 查询(根据ID 批量查询) 可指定路由及多索引 + List selectBatchIds(String routing, Collection idList, String... indexNames); + + // 根据动态查询条件,查询一条记录 若存在多条记录 会报错 T selectOne(LambdaEsQueryWrapper wrapper); // 根据动态查询条件,查询全部记录 List selectList(LambdaEsQueryWrapper wrapper); @@ -113,6 +167,8 @@ Integer update(T entity, LambdaEsUpdateWrapper updateWrapper); ##### 参数说明 | 类型 | 参数名 | 描述 | | --- | --- | --- | +| `String` | routing | 路由 | +| `String` | indexNames | 索引列表 | | `Wrapper` | queryWrapper | 实体包装类 QueryWrapper | | `Serializable` | id | 主键ID | | `Collection` | idList | 主键ID列表 | @@ -121,6 +177,7 @@ Integer update(T entity, LambdaEsUpdateWrapper updateWrapper); - CRUD接口用法基本与MP一致 - 用户需要继承的Mapper为BaseEsMapper,而非BaseMapper - EE没有提供Service层,而是把MP中一些Service层的方法直接下沉到Mapper层了,用户用起来会更方便 +- 路由参数routing如果对应方法有wrapper,则可直接通过wrapper.routing(String routing)来指定路由,若无wrapper的方法,则可通过方法重载选择有routing入参的方法 ::: diff --git "a/docs/10.v2.x\346\226\207\346\241\243/030.\346\213\223\345\261\225\345\212\237\350\203\275/040.\345\265\214\345\245\227\347\261\273\345\236\213.md" "b/docs/10.v2.x\346\226\207\346\241\243/030.\346\213\223\345\261\225\345\212\237\350\203\275/040.\345\265\214\345\245\227\347\261\273\345\236\213.md" index f0da0ce..5f01b27 100644 --- "a/docs/10.v2.x\346\226\207\346\241\243/030.\346\213\223\345\261\225\345\212\237\350\203\275/040.\345\265\214\345\245\227\347\261\273\345\236\213.md" +++ "b/docs/10.v2.x\346\226\207\346\241\243/030.\346\213\223\345\261\225\345\212\237\350\203\275/040.\345\265\214\345\245\227\347\261\273\345\236\213.md" @@ -6,11 +6,11 @@ permalink: /pages/0b25c9/ ## 前言 -在MySQL以及其它关系型数据库中,表与表之间相互关联可以用JOIN来实现,但ES中并没有JOIN,所以想要处理这种关联关系就需要了解嵌套类型和大宽表. +在MySQL以及其它关系型数据库中,表与表之间相互关联可以用JOIN来实现,但ES中并没有JOIN,所以想要处理这种关联关系就需要了解嵌套类型父子类型和大宽表. ES底层是Lucene,由于Lucene实际上是不支持嵌套类型的,所有文档都是以扁平的结构存储在Lucene中,ES对嵌套文档的支持,实际上也是采取了一种投机取巧的方式实现的. -嵌套的文档均以独立的文档存入,然后添加关联关系,这就会导致,一条嵌套类型的文档,底层实际上存储了N条数据,而且更新时会株连九族式更新,导致效率低下. +嵌套的文档均以独立的文档存入,然后添加关联关系,这就会导致,一条嵌套类型的文档,底层实际上存储了N条数据,而且更新时会株连九族式更新,导致效率低下(如果您的业务场景是写多于读,那么建议您采用父子类型,如果读多于写,则用此嵌套类型). 对于嵌套类型, 我们并不建议您使用,除非万不得已,因为引入嵌套类型后,您后续的CRUD都会变得非常复杂,如果有嵌套+聚合的需求,其编码复杂度会让你怀疑人生. diff --git "a/docs/10.v2.x\346\226\207\346\241\243/030.\346\213\223\345\261\225\345\212\237\350\203\275/050.Join\347\210\266\345\255\220\347\261\273\345\236\213.md" "b/docs/10.v2.x\346\226\207\346\241\243/030.\346\213\223\345\261\225\345\212\237\350\203\275/050.Join\347\210\266\345\255\220\347\261\273\345\236\213.md" index b4f4e3c..9cb05ba 100644 --- "a/docs/10.v2.x\346\226\207\346\241\243/030.\346\213\223\345\261\225\345\212\237\350\203\275/050.Join\347\210\266\345\255\220\347\261\273\345\236\213.md" +++ "b/docs/10.v2.x\346\226\207\346\241\243/030.\346\213\223\345\261\225\345\212\237\350\203\275/050.Join\347\210\266\345\255\220\347\261\273\345\236\213.md" @@ -17,54 +17,35 @@ ES本身更适合"大宽表"模式,不要带着传统关系型数据库那种思 ## 父子类型创建索引 -- 自动挡模式: +- 步骤一 添加注解,指定父子关系: ```java - /** - * 父文档 - */ - @IndexName(childClass = Comment.class) - public class Document{ - // 省略其它字段... - /** - * 须通过注解在父文档及子文档的实体类中指明其类型为Join,及其父名称和子名称,这里的JoinField类框架已内置,无需重复造轮子 - * JoinField类全路径为cn.easyes.common.params.JoinField,如果你非要自己造轮子,也支持,那么需要在@TableField注解中指明joinFieldClass=你造的轮子 - */ - @IndexField(fieldType = FieldType.JOIN, parentName = "document", childName = "comment") - private JoinField joinField; - } - /** - * 子文档 - */ - @IndexName(child = true) - public class Comment { - // 省略其它字段... - /** - * 父子关系字段 须通过注解在父文档及子文档的实体类中指明其类型为Join,子文档中的父子关系可省略 - */ - @IndexField(fieldType = FieldType.JOIN) - private JoinField joinField; - } +/** + * Document根文档有子文档Author(作者)和Comment(评论),其中Author还有个子文档Contact(联系方式) + * Join父子类型结构如下所示 + * Document + * / \ + * Comment Author + * \ + * Contact + * 上述结构可用@Join注解和@Node注解来表达,可参考下面案例 + **/ +@Join(nodes = {@Node(parentClass = Document.class, childClasses = {Author.class, Comment.class}), @Node(parentClass = Author.class, childClasses = Contact.class)}) +public class Document { + // 省略其它无关字段 +} ``` -> **注意:** 务必像上面示例一样,在父文档的类上加注解@TableName指明其子文档类,在子文档的类上加注解@TableName,指明child=true,并在JoinField类的@TableField注解中指定类型为fieldType=JOIN及其parentName,childName,否则会导致框架无法正常工作 +> **注意:** 务必像上面示例一样,在父文档的类上加注解@Join配合@Node注解表达出父子结构,子文档的类上无需重复添加此注解,仅需要在Root根节点上加上此注解即可 -- 手动挡模式 - - 方式一: -按照自动挡模式,配置好注解,然后直接调用一键生成API生成索引 +- 步骤二 调用api完成索引创建(自动挡无需调用,项目启动则框架自动创建) ```java +// 手动挡通过根mapper一键创建 documentMapper.createIndex(); -``` - - 方式二: -纯手工打造,所有字段自己安排一遍,不推荐,麻烦得很 -```java - LambdaEsIndexWrapper wrapper = new LambdaEsIndexWrapper<>(); - // 省略其它代码 - wrapper.join("joinField", "document", "comment"); ``` -::: tip 注意 -在手动挡模式下,主类上的注解依然不能少,框架运行时需要用到父子关系,在方式二还需要通过wrapper指定该嵌套字段,然后完成索引创建/更新 -::: +创建完成后,父子类型索引结构如图: +![index.png](https://iknow.hs.net/fa806956-3a52-44d3-a724-5ec9c778ca73.png) + ## 父子类型 CRUD @@ -73,89 +54,145 @@ documentMapper.createIndex(); CRUD示例: ```java @Test - public void testInsert() { - // 测试新增父子文档,此处开启自动挡模式,父子类型索引已被自动处理 + public void testInsert() throws InterruptedException { // 新新增父文档,然后再插入子文档 - Document document = new Document(); - document.setId("1"); - document.setTitle("父文档的标题"); - document.setContent("父文档的内容"); - JoinField joinField = new JoinField(); - joinField.setName("document"); - document.setJoinField(joinField); - documentMapper.insert(document); - - // 插入子文档 - Comment comment = new Comment(); - comment.setId("2"); - comment.setCommentContent("文档的评论1"); - - // 这里特别注意,子文档必须指定其父文档的id,否则找不到父文档别怪我没提醒 - joinField.setParent("1"); - joinField.setName("comment"); - comment.setJoinField(joinField); - commentMapper.insert(comment); + String parentId = "doc-1"; + Document root = new Document(); + root.setEsId(parentId); + root.setTitle("我是父文档的标题"); + root.setContent("father doc"); + documentMapper.insert(FIXED_ROUTING, root); + Thread.sleep(2000); + + + // 插入子文档1 + Comment nodeA1 = new Comment(); + nodeA1.setId("comment-1"); + nodeA1.setCommentContent("test1"); + // 这里特别注意,子文档必须指定其路由和父亲文档相同,否则傻儿子找不到爹别怪我没提醒 (es语法如此,非框架限制) + commentMapper.insert(FIXED_ROUTING, parentId, nodeA1); // 插入子文档2 - Comment comment1 = new Comment(); - comment1.setId("3"); - comment1.setCommentContent("文档的评论2"); - comment1.setJoinField(joinField); - commentMapper.insert(comment1); - } - - @Test - public void testSelect() { - // 温馨提示,下面wrapper中的type实际上就是JoinField字段注解@TableField中指定的parentName和childName,与原生语法是一致的 - // case1: hasChild查询,返回的是相关的父文档 所以查询用父文档实体及其mapper - LambdaEsQueryWrapper documentWrapper = new LambdaEsQueryWrapper<>(); - documentWrapper.hasChild("comment", FieldUtils.val(Comment::getCommentContent), "评论"); - List documents = documentMapper.selectList(documentWrapper); - System.out.println(documents); - - // case2: hasParent查询,返回的是相关的子文档 所以查询用子文档实体及其mapper - LambdaEsQueryWrapper commentWrapper = new LambdaEsQueryWrapper<>(); - // 字段名称你也可以不用FieldUtils.val,直接传入字符串也行 - commentWrapper.hasParent("document", "content", "内容"); - List comments = commentMapper.selectList(commentWrapper); - System.out.println(comments); - - // case3: parentId查询,返回的是相关的子文档,与case2类似,所以查询用子文档实体及其mapper - commentWrapper = new LambdaEsQueryWrapper<>(); - commentWrapper.parentId("1", "comment"); - List commentList = commentMapper.selectList(commentWrapper); - System.out.println(commentList); - } + Comment nodeA2 = new Comment(); + nodeA2.setId("comment-2"); + nodeA2.setCommentContent("test2"); + commentMapper.insert(FIXED_ROUTING, parentId, nodeA2); + + // 插入子文档3 + Author nodeB1 = new Author(); + nodeB1.setAuthorId("author-1"); + nodeB1.setAuthorName("tom"); + authorMapper.insert(FIXED_ROUTING, parentId, nodeB1); + + // 插入子文档4 + Author nodeB2 = new Author(); + nodeB2.setAuthorId("author-2"); + nodeB2.setAuthorName("cat"); + authorMapper.insert(FIXED_ROUTING, parentId, nodeB2); + Thread.sleep(2000); + + // 插入孙子文档1(把孙子1挂在子文档3上) + Contact child1 = new Contact(); + child1.setContactId("contact-1"); + child1.setAddress("zhejiang province"); + contactMapper.insert(FIXED_ROUTING, nodeB1.getAuthorId(), child1); + + // 插入孙子文档2(把孙子2挂在子文档3上) + Contact child2 = new Contact(); + child2.setContactId("contact-2"); + child2.setAddress("hangzhou city"); + contactMapper.insert(FIXED_ROUTING, nodeB1.getAuthorId(), child2); + + // 插入孙子文档3(把孙子3挂在子文档4上) + Contact child3 = new Contact(); + child3.setContactId("contact-3"); + child3.setAddress("binjiang region"); + contactMapper.insert(FIXED_ROUTING, nodeB2.getAuthorId(), child3); + + // es写入数据有延迟 适当休眠 保证后续查询结果正确 + Thread.sleep(2000); + } + + @Test + public void testSelect() { + // 温馨提示,下面wrapper中的type实际上就是索引JoinField中指定的父子名称,与原生语法是一致的 + // case1: hasChild查询,返回的是相关的父文档 所以查询用父文档实体及其mapper + LambdaEsQueryWrapper documentWrapper = new LambdaEsQueryWrapper<>(); + documentWrapper.hasChild("comment", w -> w.eq(FieldUtils.val(Comment::getCommentContent), "test1")); + List documents = documentMapper.selectList(documentWrapper); + System.out.println(documents); + + LambdaEsQueryWrapper authorWrapper = new LambdaEsQueryWrapper<>(); + authorWrapper.hasChild("contact", w -> w.match(FieldUtils.val(Contact::getAddress), "city")); + List authors = authorMapper.selectList(authorWrapper); + System.out.println(authors); + + // case2: hasParent查询,返回的是相关的子文档 所以查询用子文档实体及其mapper + LambdaEsQueryWrapper commentWrapper = new LambdaEsQueryWrapper<>(); + commentWrapper.like(Comment::getCommentContent, "test"); + // 字段名称你也可以不用FieldUtils.val,直接传入字符串也行 + commentWrapper.hasParent("document", w -> w.match("content", "father")); + List comments = commentMapper.selectList(commentWrapper); + System.out.println(comments); + + // case2.1: 孙子查爹的情况 + LambdaEsQueryWrapper contactWrapper = new LambdaEsQueryWrapper<>(); + contactWrapper.hasParent("author", w -> w.eq(FieldUtils.val(Author::getAuthorName), "cat")); + List contacts = contactMapper.selectList(contactWrapper); + System.out.println(contacts); + + // case2.2: 2.1的简写 + LambdaEsQueryWrapper contactWrapper1 = new LambdaEsQueryWrapper<>(); + // hasParent之所以可以不指定parentType简写是因为框架可以通过@Join注解中指定的父子关系自动推断出其父type,因此用户可以不指定父type直接查询,但hasChild不能简写,因为一个父亲可能有多个孩子,但一个孩子只能有一个亲爹 + contactWrapper1.hasParent(w -> w.eq(FieldUtils.val(Author::getAuthorName), "cat")); + List contacts1 = contactMapper.selectList(contactWrapper1); + System.out.println(contacts1); + + // case3: parentId查询,返回的是相关的子文档,与case2类似,所以查询用子文档实体及其mapper + commentWrapper = new LambdaEsQueryWrapper<>(); + commentWrapper.parentId("doc-1", "comment"); + List commentList = commentMapper.selectList(commentWrapper); + System.out.println(commentList); + + contactWrapper = new LambdaEsQueryWrapper<>(); + contactWrapper.parentId("author-2", "contact"); + List contactList = contactMapper.selectList(contactWrapper); + System.out.println(contactList); + } + + @Test + public void testUpdate() { + // case1: 父文档/子文档 根据各自的id更新 + Document document = new Document(); + document.setEsId("doc-1"); + document.setTitle("我是隔壁老王标题"); + documentMapper.updateById(FIXED_ROUTING, document); + + Contact contact = new Contact(); + contact.setContactId("contact-2"); + contact.setAddress("update address"); + contactMapper.updateById(FIXED_ROUTING, contact); + + // case2: 父文档/子文档 根据各自条件更新 + Comment comment = new Comment(); + comment.setCommentContent("update comment content"); + LambdaEsUpdateWrapper wrapper = new LambdaEsUpdateWrapper<>(); + wrapper.eq(Comment::getCommentContent, "test1"); + wrapper.routing(FIXED_ROUTING); + commentMapper.update(comment, wrapper); + } + + @Test + public void testDelete() { + // case1: 父文档/子文档 根据各自的id删除 + documentMapper.deleteById(FIXED_ROUTING, "doc-1"); + + //case2: 父文档/子文档 根据各自条件删除 + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + wrapper.like(Comment::getCommentContent, "test") + .routing(FIXED_ROUTING); + commentMapper.delete(wrapper); + } - @Test - public void testUpdate() { - // case1: 父文档/子文档 根据各自的id更新 - Document document = new Document(); - document.setId("1"); - document.setTitle("父标题"); - documentMapper.updateById(document); - - // case2: 父文档/子文档 根据各自条件更新 - Comment comment = new Comment(); - comment.setCommentContent("更新后的评论"); - LambdaEsUpdateWrapper wrapper = new LambdaEsUpdateWrapper<>(); - wrapper.match(Comment::getCommentContent, "评论"); - commentMapper.update(comment, wrapper); - } - - @Test - public void testDelete() { - // case1: 父文档/子文档 根据各自的id删除 - documentMapper.deleteById("1"); - - //case2: 父文档/子文档 根据各自条件删除 - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); - wrapper.match(Comment::getCommentContent, "评论"); - commentMapper.delete(wrapper); - } ``` -相关demo可参考源码的test模块->test目录->join包 - -## 坏味道 - -目前父子类型的文档自动创建索引/手动挡一键模式尚不支持一父多子的场景,如果您有此类场景,须通过手动挡自定义模式或者通过其它工具创建索引,由于父子类型用的人非常少,此类功能优先级较低,我们将在后续迭代陆续支持. \ No newline at end of file +相关demo可参考源码的test模块->test目录->join包 \ No newline at end of file diff --git "a/docs/10.v2.x\346\226\207\346\241\243/040.\351\253\230\351\230\266\350\257\255\346\263\225/030.\350\201\232\345\220\210\346\237\245\350\257\242.md" "b/docs/10.v2.x\346\226\207\346\241\243/040.\351\253\230\351\230\266\350\257\255\346\263\225/030.\350\201\232\345\220\210\346\237\245\350\257\242.md" index e1e4e7d..b1ef2e0 100644 --- "a/docs/10.v2.x\346\226\207\346\241\243/040.\351\253\230\351\230\266\350\257\255\346\263\225/030.\350\201\232\345\220\210\346\237\245\350\257\242.md" +++ "b/docs/10.v2.x\346\226\207\346\241\243/040.\351\253\230\351\230\266\350\257\255\346\263\225/030.\350\201\232\345\220\210\346\237\245\350\257\242.md" @@ -24,6 +24,22 @@ permalink: /pages/ce1922/ } ``` +设置聚合桶大小及桶内排序 + +```java + @Test + @Order(6) + public void testConditionGroupBy() { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + wrapper.match(Document::getContent, "测试") + .size(20) // 设置聚合桶大小 + .bucketOrder(BucketOrder.count(Boolean.TRUE)) // 设置桶排序规则 + .groupBy(Document::getStarNum); + SearchResponse response = documentMapper.search(wrapper); + System.out.println(response); + } +``` + :::tip 温馨提示 尽管语法与MP一致,但实际上,ES的聚合结果是放在单独的对象中的,格式如下所示,因此我们高阶语法均需要用SearchResponse来接收返回结果,这点需要区别于MP和MySQL. ::: diff --git a/docs/index.md b/docs/index.md index b3ae5d3..bd72518 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,7 +26,7 @@ postList: none notices: # 可选的 - id: Easy-Es-2.0.0-beta title: 🚀 Easy-Es v2.0.0-betaX 发布! - content: '

2024-01

  • v2.0.0-beta1
  • v2.0.0-beta2
  • v2.0.0-beta...
  • v2.0.0-beta5

查看详情

' + content: '

2024-01

  • v2.0.0-beta1
  • v2.0.0-beta2
  • v2.0.0-beta...
  • v2.0.0-beta6

查看详情

' isHtmlContent: true --- diff --git a/docs/whats new/090.whats new in v2.0.0-beta.md b/docs/whats new/090.whats new in v2.0.0-beta.md index 287ef0a..839b6f0 100644 --- a/docs/whats new/090.whats new in v2.0.0-beta.md +++ b/docs/whats new/090.whats new in v2.0.0-beta.md @@ -56,6 +56,15 @@ permalink: /pages/2934a3/ - 优化GEO相关文档中,经纬度"倒反天罡"问题,调整为纬度在前,经度在后 - 新特性相关使用文档同步更新 +## v2.0.0-beta6 +- 新增父子类型对一父多子以及一父多子多孙场景的索引自动化支持(重大特性) +- 增加对父子类型所有查询功能支持(重大特性) +- 提供全新自定义注解@Settings,支持便捷预设与灵活自定义功能,可支持ES索引中所有Settings的便捷处理(重大特性) +- 所有CRUD方法支持方法粒度的自定义路由功能 +- DSL语句打印中新增对路由的打印 +- javadoc及注释和部分代码细节优化 + + ## 何时发布2.0.0正式版? 计划在今年(2023年度)完成2.0正式版发布,因为本次2.0版本改动确实非常大,核心代码几乎改了一遍, 且2.x相较1.x带来了海量的新特性, @@ -64,7 +73,7 @@ permalink: /pages/2934a3/ - v2.0.0-beta1 - v2.0.0-beta2 - v2.0.0-beta... -- 承诺在beta10以内完成所有特性和测试及生产验证环节 +- v2.0.0-beta7 - v2.0.0 推送正式版 我们正在奋力前行,不断完善,也随时欢迎每位有志之士加入... diff --git "a/docs/\345\215\207\347\272\247\345\210\2602.X\350\257\264\346\230\216.md" "b/docs/\345\215\207\347\272\247\345\210\2602.X\350\257\264\346\230\216.md" index 90cd772..59058a6 100644 --- "a/docs/\345\215\207\347\272\247\345\210\2602.X\350\257\264\346\230\216.md" +++ "b/docs/\345\215\207\347\272\247\345\210\2602.X\350\257\264\346\230\216.md" @@ -13,6 +13,9 @@ permalink: /pages/dfd970/ * 原来所有用到表示取反的方法需要使用not重新改写,比如wrapper.notMatch(Document::getContent,"推*")--->wrapper.not().match(Document::getContent,"推*"),关于[四大嵌套查询](/pages/17ea0a/)可以点击查看详情. * 在beta2中将包名由cn.easyes打头调整为org.dromara,同时maven中的groupId也由cn.easy-es调整为org.dromara.easy-es * 在beta5中将索引刷新的refresh()方法及refresh(String...indexNames)方法返回值由布尔类型调整为Integer类型,返回值为实际成功的分片数 +* 在beta6中调整CRUD方法中的路由参数位置,路由统一调整到第一个参数的位置,若您有使用路由,可调整参数位置后使用,若未使用路由,则无影响 +* 在beta6中废弃早期父子类型版本,涉及父子类型功能可使用新的Join父子类型注解代替,具体可参考文档[Join父子类型](/pages/23a033/) + :::tip 尽管本次升级底层改动非常大,但带给用户的改动并不多,我们也建议您尽快升级至功能更为强大的2.x版本,相比改动带来的少许改造,2.X所带来的收益是值得的! ::: \ No newline at end of file