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

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

文档地址 www.easy-query.com/easy-query-...

GITHUB地址 github.com/dromara/eas...

GITEE地址 gitee.com/dromara/eas...

对象关系

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

相关推荐
MaCa .BaKa4 分钟前
33-公交车司机管理系统
java·vue.js·spring boot·maven
洛小豆31 分钟前
一个场景搞明白Reachability Fence,它就像一道“结账前别走”的红外感应门
java·后端·面试
500佰33 分钟前
AI提示词(Prompt)设计优化方案 | 高效使用 AI 工具
java·人工智能·prompt·ai编程
摘星编程34 分钟前
并发设计模式实战系列(4):线程池
java·设计模式·并发编程
PGCCC1 小时前
【PGCCC】Postgres MVCC 内部:更新与插入的隐性成本
java·开发语言·数据库
诺亚凹凸曼1 小时前
Java基础系列-LinkedList源码解析
java·开发语言
Maỿbe1 小时前
手动实现LinkedList
java·开发语言
爱喝一杯白开水1 小时前
java基础从入门到上手(九):Java - List、Set、Map
java·list·set·map
掉鱼的猫1 小时前
MCP Server Java 开发框架的体验比较(spring ai mcp 和 solon ai mcp)
java·mcp
静独善水1 小时前
`ImadcnIdentifierGenerator` 深度解析
java