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进行了开发反向非常不错,期待您的使用。

easy-query隐式Group革命性OLAP优化JAVA下最强查询ORM没有之一子查询合并的更多相关文章

  1. MySQL 子查询(四)子查询的优化、将子查询重写为连接

    MySQL 5.7 ref ——13.2.10.10优化子查询 十.子查询的优化 开发正在进行中,因此从长远来看,没有什么优化建议是可靠的.以下列表提供了一些您可能想要使用的有趣技巧.See also ...

  2. Qt C++中的关键字explicit——防止隐式转换(也就是Java里的装箱),必须写清楚

    最近在复习QT,准备做项目了,QT Creator 默认生成的代码 explicit Dialog(QWidget *parent = 0)中,有这么一个关键字explicit,用来修饰构造函数.以前 ...

  3. mysql优化---in型子查询,exists子查询,from 型子查询

    in型子查询引出的陷阱:(扫更少的行,不要临时表,不要文件排序就快) 题: 在ecshop商城表中,查询6号栏目的商品, (注,6号是一个大栏目) 最直观的: mysql); 误区: 给我们的感觉是, ...

  4. MySQL解惑——GROUP BY隐式排序

    MySQL中GROUP BY隐式排序是什么概念呢? 主要是其它RDBMS没有这样的概念,如果没有认真了解过概念,对这个概念会感觉有点困惑,我们先来看看官方文档的介绍: 官方文档MySQL 5.7 Re ...

  5. MySQL解惑——GROUP BY隐式排序

    原文:MySQL解惑--GROUP BY隐式排序 MySQL中GROUP BY隐式排序是什么概念呢? 主要是其它RDBMS没有这样的概念,如果没有认真了解过概念,对这个概念会感觉有点困惑,我们先来看看 ...

  6. 显示转换explicit和隐式转换implicit

    用户自定义的显示转换和隐式转换 显式转换implicit关键字告诉编译器,在源代码中不必做显示的转型就可以产生调用转换操作符方法的代码. 隐式转换implicit关键字告诉编译器只有当源代码中指定了显 ...

  7. JSP的学习(6)——九大隐式对象及其out对象

    本篇将介绍JSP中的九大隐式对象,并重点介绍其中的out对象. 我们在之前的博客<JSP的学习(1)——基础知识与底层原理>一文中已经知道,JSP最终要被翻译和转换成Servlet,在转换 ...

  8. Java :构造器中的显式参数和this隐式参数

    1.构造器 写一个Java类,首先要先从构造器开始,构造器与类同名,在构造类的对象时会先从构造器开始. 构造器总是伴随着new操作符的执行而被调用. 构造器主要是用来初始化类的实例域. 构造器的特点: ...

  9. Scala学习之路 (八)Scala的隐式转换和隐式参数

    一.概念 Scala 2.10引入了一种叫做隐式类的新特性.隐式类指的是用implicit关键字修饰的类.在对应的作用域内,带有这个关键字的类的主构造函数可用于隐式转换. 隐式转换和隐式参数是Scal ...

  10. C++转换构造函数和隐式转换函数 ~ 转载

    原文地址: C++转换构造函数和隐式转换函数 用转换构造函数可以将一个指定类型的数据转换为类的对象.但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成doubl ...

随机推荐

  1. .Net类型 引用类型

    预定义类型引用类型 C#支持两种预定义的引用类型:object 和string 名称 .NET类型 说明 object System.Object 根类型,其他类型都是从它派生而来的(包括值类型) s ...

  2. Ceph的crush算法与一致性hash对比介绍

    本文分享自天翼云开发者社区<Ceph的crush算法与一致性hash对比介绍>,作者:l****n 首先,我们先回顾下一致性hash以及其在经典存储系统中的应用. 一致性hash的基本原理 ...

  3. Windows中安装和配置Maven

    1.下载 下载地址:https://maven.apache.org/download.cgi 下载文件:https://dlcdn.apache.org/maven/maven-3/3.9.6/bi ...

  4. flutter-double小数点相加,会出现小数点很多位

    在我们进行两个double运算时,例如:2..0-1.1 不是想象的输出0.9,而是0.89999999999999999.其主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确的表示分数1 ...

  5. 深入解析:Jupyter Notebook 中魔法命令的使用技巧与应用

    Jupyter Notebook 中的魔法命令为用户提供了诸多便利功能.魔法命令主要分为行魔法(Line magic)和单元魔法(Cell magic),行魔法前缀为"%",单元魔 ...

  6. Typecho头像被墙的解决方法

    首先下载最新开发版本的TYPECHO,然后,在config.inc.php自定义如下: /** 自定义gravatar url前缀 */ define('__TYPECHO_GRAVATAR_PREF ...

  7. Flink学习(十九) 容错机制

    主要内容: 一致性检查点(checkpoint) 从检查点恢复到状态 Flink检查点算法 保存点(savepoint) 一致性检查点(checkpoint) Flink故障恢复机制的核心,就是应用状 ...

  8. Flink学习(三) 批流版本的wordcount JAVA版本

    Flink 开发环境通常来讲,任何一门大数据框架在实际生产环境中都是以集群的形式运行,而我们调试代码大多数会在本地搭建一个模板工程,Flink 也不例外. Flink 一个以 Java 及 Scala ...

  9. P4118 [Ynoi2018] 末日时在做什么?有没有空?可以来拯救吗?

    YNOI 智慧题 EasyVer1 [Ynoi Easy Round 2015] 世上最幸福的女孩 EasyVer2 小白逛公园 先看 EasyVer2 单点修改 区间查询最大子段和 考虑在线段树维护 ...

  10. Windows 提权-PrintNightmare

    本文通过 Google 翻译 PrintNightmare – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充 ...