您现在的位置是:首页 > 文章详情

easy-query 隐式 Group 革命性 OLAP 优化 JAVA 下最强查询 ORM 没有之一子查询合并

日期:2025-04-20点击:11

easy-query JAVA下最强查询ORM没有之一的任意子查询合并革命性OLAP优化

前言

对于大部分OLTP而言的查询市面上常见的orm已经能很好的处理,只要建立好对象关系那么就可以非常简单的实现隐式join或者隐式子查询

easy-query一款将ORM的Relational在查询中表现得淋漓尽致,支持手动join、手动子查询也支持隐式join、隐式子查询并且支持手动和隐式混用,经过2年半时间的打磨努力从追赶.net orm到超越我敢说里面有着许多人的努力和众多框架作者的思想借鉴才能让easy-query在短短2年变得越来越完善功能也是越来越强大

  • 2023年如何实现更多的sql让orm在项目开发中变得可用做到零sql
  • 2024实现如何不以翻译sql为目标实现用户心中无sql编写表达式
  • 2025至今优化复杂sql在olap下的优化处理

当你在想怎么连表的时候、当你在想这个sql怎么翻译成表达式的时候其实你已经陷入了“自证”的环节,我们应该要考虑的是你希望实现的功能比如查询用户条件是银行卡有2张的,而不是写好一堆sql然后问orm作者这个怎么写,从面向sql结果向面向需求编写表达式的转变。

介绍

easy-query

文档地址 https://www.easy-query.com/easy-query-doc/

GITHUB地址 https://github.com/dromara/easy-query

GITEE地址 https://gitee.com/dromara/easy-query

对象关系

隐式join

常见的隐式join如下

查询银行卡要求是筛选条件是银行卡所属用户手机号包含1234并且所属银行是工商银行的

  List<SysBankCard> bankCards = easyEntityQuery.queryable(SysBankCard.class) .where(bank_card -> { bank_card.user().phone().like("1234"); bank_card.bank().name().eq("工商银行"); }).toList(); 

生成的sql为

  SELECT t.`id`, t.`uid`, t.`code`, t.`type`, t.`bank_id`, t.`open_time` FROM `t_bank_card` t LEFT JOIN `t_sys_user` t1 ON t1.`id` = t.`uid` INNER JOIN `t_bank` t2 ON t2.`id` = t.`bank_id` WHERE t1.`phone` LIKE '%1234%' AND t2.`name` = '工商银行' 

有些小伙伴就很奇怪为什么对于user而言是left join对于bank而言缺是inner join原因就是在创建对象关系的时候我们指定了SysBankCard和SysBank的关系是外键关系所以不可能存在有银行卡没有银行的情况 具体对象如下

  @Table("t_bank") @EntityProxy @Data @FieldNameConstants @EasyAlias("bank") public class SysBank implements ProxyEntityAvailable<SysBank, SysBankProxy> { @Column(primaryKey = true) private String id; /** * 银行名称 */ private String name; /** * 成立时间 */ private LocalDateTime createTime; /** * 拥有的银行卡 */ @Navigate(value = RelationTypeEnum.OneToMany, selfProperty = {"id"}, targetProperty = {"bankId"}) private List<SysBankCard> bankCards; } @Table("t_bank_card") @EntityProxy @Data @FieldNameConstants @EasyAlias("bank_card") public class SysBankCard implements ProxyEntityAvailable<SysBankCard , SysBankCardProxy> { @Column(primaryKey = true) private String id; private String uid; /** * 银行卡号 */ private String code; /** * 银行卡类型借记卡 储蓄卡 */ private String type; /** * 所属银行 */ private String bankId; /** * 用户开户时间 */ private LocalDateTime openTime; /** * 所属银行 */ @Navigate(value = RelationTypeEnum.ManyToOne, selfProperty = {"bankId"}, targetProperty = {"id"}) @ForeignKey//可以不加 加了就是InnerJoin处理更多细节查看注解篇章 private SysBank bank; /** * 所属用户 */ @Navigate(value = RelationTypeEnum.ManyToOne, selfProperty = {"uid"}, targetProperty = {"id"}) private SysUser user; } @Table("t_sys_user") @EntityProxy @Data @FieldNameConstants @EasyAlias("user") public class SysUser implements ProxyEntityAvailable<SysUser , SysUserProxy> { @Column(primaryKey = true) private String id; private String name; private String phone; private Integer age; private LocalDateTime createTime; /** * 用户拥有的银行卡数 */ @Navigate(value = RelationTypeEnum.OneToMany, selfProperty = {"id"}, targetProperty = {"uid"}) private List<SysBankCard> bankCards; /** * 用户拥有的书本 */ @Navigate(value = RelationTypeEnum.OneToMany, selfProperty = {"id"}, targetProperty = {"uid"}) private List<SysUserBook> userBooks; } @Table("t_sys_user_book") @EntityProxy @Data @FieldNameConstants @EasyAlias("user_book") public class SysUserBook implements ProxyEntityAvailable<SysUserBook , SysUserBookProxy> { private String id; private String name; private String uid; private BigDecimal price; } 

演示了隐式join后我在演示对应的隐式子查询

隐式子查询

筛选出用户拥有至少2张工商银行卡且还未在建设银行开户的用户

 List<SysUser> list = easyEntityQuery.queryable(SysUser.class) .where(user -> { user.bankCards().where(card -> { card.bank().name().eq("工商银行"); }).count().ge(2L); user.bankCards().none(card -> { card.bank().name().eq("建设银行"); }); }).toList(); 

生成的sql为

 SELECT t.`id`, t.`name`, t.`phone`, t.`age`, t.`create_time` FROM `t_sys_user` t WHERE ( SELECT COUNT(*) FROM `t_bank_card` t1 INNER JOIN `t_bank` t2 ON t2.`id` = t1.`bank_id` WHERE t1.`uid` = t.`id` AND t2.`name` = '工商银行' ) >= 2 AND NOT ( EXISTS (SELECT 1 FROM `t_bank_card` t3 INNER JOIN `t_bank` t4 ON t4.`id` = t3.`bank_id` WHERE t3.`uid` = t.`id` AND t4.`name` = '建设银行' LIMIT 1)) 

这里出现了两个子查询分别是[至少2张工商银行卡]和[还未在建设银行开户的用户]两个条件

那么到这里就是大部分ORM能做的事情了,甚至大部分ORM连这点都做不到,所以说如果你的ORM支持子查询那么你的ORM在面对复杂查询如果只能做到这么一点那么只能算是一个刚到及格线的ORM,因为如果出现多个子查询或者在多对多下这种处理是非常致命的,尤其是查询用户条件是菜单为/admin这种

正片开始

所谓鱼(可维护性)和熊掌(性能)不可兼得,那么easy-query是如何处理像这种情况下的呢,又是如何让你兼得鱼和熊掌的有请我们的本期主角【隐式Group】

隐式Group

筛选出用户拥有至少2张工商银行卡且还未在建设银行开户的用户

对于相同的条件easy-query只需要一个配置就可以让你兼得鱼(可维护性)和熊掌(性能)

 List<SysUser> list = easyEntityQuery.queryable(SysUser.class) //启用隐式group,无论实在where还是select里面用到的bankCards子查询都会复用同一个隐式Group .subQueryToGroupJoin(u->u.bankCards()) .where(user -> { //至少2张工商银行 user.bankCards().where(card -> { card.bank().name().eq("工商银行"); }).count().ge(2L); //没有建行卡 user.bankCards().none(card -> { card.bank().name().eq("建设银行"); }); }).toList(); 

我们再来看生成的sql

 SELECT t.`id`, t.`name`, t.`phone`, t.`age`, t.`create_time` FROM `t_sys_user` t LEFT JOIN ( SELECT t1.`uid` AS `uid`, COUNT((CASE WHEN t3.`name` = '工商银行' THEN 1 ELSE NULL END)) AS `__count2__`, (CASE WHEN COUNT((CASE WHEN t3.`name` = '建设银行' THEN 1 ELSE NULL END)) > 0 THEN false ELSE true END) AS `__none3__` FROM `t_bank_card` t1 INNER JOIN `t_bank` t3 ON t3.`id` = t1.`bank_id` GROUP BY t1.`uid` ) t2 ON t2.`uid` = t.`id` WHERE IFNULL(t2.`__count2__`,0) >= 2 AND IFNULL(t2.`__none3__`,true) = true 

有没有一种眼前一亮的感觉,我敢说目前来讲并没有多少ORM实现了隐式Group,当然对于OLAP而言easy-query的提供的功能远不止此

partition by

筛选用户条件为喜欢工商银行的(第一张开户的银行卡是工商银行的)

 List<SysUser> list = easyEntityQuery.queryable(SysUser.class) .where(user -> { //用户的银行卡中第一个开户银行卡是工商银行的 user.bankCards().orderBy(x->x.openTime().asc()).firstElement().bank().name().eq("工商银行"); }).toList(); 

生成的sql

 SELECT t.`id`, t.`name`, t.`phone`, t.`age`, t.`create_time` FROM `t_sys_user` t LEFT JOIN ( SELECT t2.`id` AS `id`, t2.`uid` AS `uid`, t2.`code` AS `code`, t2.`type` AS `type`, t2.`bank_id` AS `bank_id`, t2.`open_time` AS `open_time` FROM (SELECT t1.`id`, t1.`uid`, t1.`code`, t1.`type`, t1.`bank_id`, t1.`open_time`, (ROW_NUMBER() OVER (PARTITION BY t1.`uid` ORDER BY t1.`open_time` ASC)) AS `__row__` FROM `t_bank_card` t1) t2 WHERE t2.`__row__` = 1 ) t4 ON t4.`uid` = t.`id` INNER JOIN `t_bank` t5 ON t5.`id` = t4.`bank_id` WHERE t5.`name` = '工商银行' 

是的你没看错就是这么简单如果你需要动态那么只需要用where(true/false,条件)或者更加直白的方式即可

筛选用户条件为喜欢工商银行的(第一张开户的银行卡是工商银行的)和用户姓名叫小明的,其中喜欢工商银行这个条件可以是动态的

  boolean likeICBC = false; String name = "小明"; List<SysUser> list = easyEntityQuery.queryable(SysUser.class) .where(user -> { if(EasyStringUtil.isNotBlank(name)){ user.name().like(name); } if(likeICBC){ //用户的银行卡中第一个开户银行卡是工商银行的 user.bankCards().orderBy(x -> x.openTime().asc()).firstElement().bank().name().eq("工商银行"); } }).toList(); 

生成的sql

  SELECT `id`, `name`, `phone`, `age`, `create_time` FROM `t_sys_user` WHERE `name` LIKE '%小明%' 

是的你没看错动态partition就是这么简单就是这么容易阅读和维护

select子查询

写了这么多where子查询的隐式Group那么再来写点select子查询相关的吧

查询用户返回用户姓名和用户开户的前两张银行卡类型逗号分割

 List<Draft2<String, String>> list = easyEntityQuery.queryable(SysUser.class) .where(user -> { user.name().like("小明"); }).select(user -> Select.DRAFT.of( user.name(), //用户的银行卡中前两个开户银行卡类型 user.bankCards().orderBy(x -> x.openTime().asc()).elements(0, 1).joining(x -> x.type(),",") )).toList(); 

生成的sql

 SELECT t.`name` AS `value1`, (SELECT GROUP_CONCAT(t1.`type` SEPARATOR ',') FROM `t_bank_card` t1 WHERE t1.`uid` = t.`id` ORDER BY t1.`open_time` ASC LIMIT 2) AS `value2` FROM `t_sys_user` t WHERE t.`name` LIKE '%小明%' 

超级无敌究极子查询转group🔥🔥🔥

筛选用户条件为姓名包含小明,并且用户的所有储蓄卡中前三张银行卡都不是在2000年前的银行中开户的,并且返回用户姓名和储蓄卡的所属银行名称逗号分割

 List<Draft2<String, String>> list = easyEntityQuery.queryable(SysUser.class) .subQueryToGroupJoin(x -> x.bankCards()) .where(user -> { user.name().like("小明"); user.bankCards() .where(x -> x.type().eq("储蓄卡")) .orderBy(x -> x.openTime().asc()).elements(0, 2) .none(x -> x.bank().createTime().ge(LocalDateTime.of(2000,1,1,0,0))); }).select(user -> Select.DRAFT.of( user.name(), user.bankCards() .where(x -> x.type().eq("储蓄卡")) .orderBy(x -> x.openTime().asc()) .elements(0, 2).joining(x -> x.bank().name(),",") )).toList(); 

生成的sql

 SELECT t.`name` AS `value1`, t3.`__joining3__` AS `value2` FROM `t_sys_user` t LEFT JOIN ( SELECT t2.`uid` AS `uid`, (CASE WHEN COUNT((CASE WHEN t4.`create_time` >= '2000-01-01 00:00' THEN 1 ELSE NULL END)) > 0 THEN false ELSE true END) AS `__none2__`, GROUP_CONCAT(t4.`name` SEPARATOR ',') AS `__joining3__` FROM (SELECT t1.`id`, t1.`uid`, t1.`code`, t1.`type`, t1.`bank_id`, t1.`open_time` FROM `t_bank_card` t1 WHERE t1.`type` = '储蓄卡' ORDER BY t1.`open_time` ASC LIMIT 3) t2 INNER JOIN `t_bank` t4 ON t4.`id` = t2.`bank_id` GROUP BY t2.`uid`) t3 ON t3.`uid` = t.`id` WHERE t.`name` LIKE '%小明%' AND IFNULL(t3.`__none2__`,true) = true 

是的你没看错这就是easy-query在2023年诞生到现在2年时间的努力,是否有一种惊艳到你的冲动

最后

可能有很多小伙伴会推荐我jpa或者jooq我想说如果我没能力那么我可能会选择他们,如果他们支持国产数据库我可能会选择他们,但是你我更愿意推荐easy-query因为我会聆听开发者的声音起码你叫的动我,我是一个在crud混的菜鸟开发,crud的困难,orm的困难必须是一个混迹在业务开发的程序员才能开发出来的好框架,在没开发出这个api的时候已经有很多小伙伴使用lambda的api进行了开发反向非常不错,期待您的使用。

原文链接:https://www.oschina.net/news/345645
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章