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并且所属银行是工商银行的
java
List<SysBankCard> bankCards = easyEntityQuery.queryable(SysBankCard.class)
.where(bank_card -> {
bank_card.user().phone().like("1234");
bank_card.bank().name().eq("工商银行");
}).toList();
生成的sql为
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的关系是外键关系所以不可能存在有银行卡没有银行的情况
具体对象如下
java
@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张工商银行卡且还未在建设银行开户的用户
java
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为
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只需要一个配置就可以让你兼得鱼(可维护性)和熊掌(性能)
java
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
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
筛选用户条件为喜欢工商银行的(第一张开户的银行卡是工商银行的)
java
List<SysUser> list = easyEntityQuery.queryable(SysUser.class)
.where(user -> {
//用户的银行卡中第一个开户银行卡是工商银行的
user.bankCards().orderBy(x->x.openTime().asc()).firstElement().bank().name().eq("工商银行");
}).toList();
生成的sql
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,条件)或者更加直白的方式即可
筛选用户条件为喜欢工商银行的(第一张开户的银行卡是工商银行的)和用户姓名叫小明的,其中喜欢工商银行这个条件可以是动态的
java
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
sql
SELECT
`id`,
`name`,
`phone`,
`age`,
`create_time`
FROM
`t_sys_user`
WHERE
`name` LIKE '%小明%'
是的你没看错动态partition就是这么简单就是这么容易阅读和维护
select子查询
写了这么多where子查询的隐式Group那么再来写点select子查询相关的吧
查询用户返回用户姓名和用户开户的前两张银行卡类型逗号分割
java
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
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年前的银行中开户的,并且返回用户姓名和储蓄卡的所属银行名称逗号分割
java
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
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进行了开发反向非常不错,期待您的使用。