easy-query:Java 最强大的 ORM 子查询

发布: (2025年12月28日 GMT+8 10:27)
9 min read
原文: Dev.to

I’m happy to translate the article for you, but I need the text you’d like translated. Could you please paste the content (or the portion you want translated) after the source line? Once I have the article text, I’ll provide a Simplified‑Chinese translation while preserving the original formatting, markdown, and code blocks.

为什么子查询如此重要?

在实际业务开发中,子查询随处可见:

  • 查询 “拥有订单的用户”
  • 查询 “拥有超过10篇文章的作者”
  • 查询 “在最近30天内有购买的会员”
  • 统计 “每个用户的订单、评论和收藏”

传统的 ORM 要么不支持,要么需要手写 SQL,或者生成性能不佳的 SQL。

easy‑query 的目标: 让子查询像普通查询一样简单,同时生成高性能的 SQL.

Source:

1. 隐式子查询:写代码像说话一样

1.1 存在性检查:any / none

// 查询拥有帖子(posts)的用户
List users = easyEntityQuery.queryable(User.class)
    .where(u -> u.posts().any())
    .toList();

// 生成的 SQL:
// SELECT * FROM t_user t
// WHERE EXISTS (SELECT 1 FROM t_post t1 WHERE t1.user_id = t.id)
// 查询没有帖子(posts)的用户
List users = easyEntityQuery.queryable(User.class)
    .where(u -> u.posts().none())
    .toList();

// 生成的 SQL:
// SELECT * FROM t_user t
// WHERE NOT EXISTS (SELECT 1 FROM t_post t1 WHERE t1.user_id = t.id)

1.2 条件性存在性检查

// 查询发布了帖子的用户
List users = easyEntityQuery.queryable(User.class)
    .where(u -> u.posts()
        .where(p -> p.status().eq(1))
        .any())
    .toList();

// 生成的 SQL:
// SELECT * FROM t_user t
// WHERE EXISTS (
//     SELECT 1 FROM t_post t1 
//     WHERE t1.user_id = t.id AND t1.status = 1
// )

1.3 全称量词:all / notEmptyAll

// 查询所有银行卡都是储蓄卡且卡号以 "622" 开头的用户
List users = easyEntityQuery.queryable(User.class)
    .where(u -> u.bankCards()
        .where(bc -> bc.type().eq("savings"))
        .all(bc -> bc.code().startsWith("622")))
    .toList();

// 生成的 SQL(使用 NOT EXISTS + NOT 逻辑):
// SELECT * FROM t_user t
// WHERE NOT EXISTS (
//     SELECT 1 FROM t_bank_card t1 
//     WHERE t1.user_id = t.id AND t1.type = 'savings' 
//       AND NOT (t1.code LIKE '622%')
//     LIMIT 1
// )

allnotEmptyAll 的区别:

方法空集合所有元素匹配部分元素不匹配
all✅ 通过✅ 通过❌ 失败
notEmptyAll❌ 失败✅ 通过❌ 失败
// notEmptyAll:至少存在一张储蓄卡,且所有储蓄卡的卡号都以 "622" 开头
List users = easyEntityQuery.queryable(User.class)
    .where(u -> u.bankCards()
        .where(bc -> bc.type().eq("savings"))
        .notEmptyAll(bc -> bc.code().startsWith("622")))
    .toList();

// notEmptyAll = any() + all(),即:存在且全部匹配

1.4 聚合子查询:count / sum / avg / max / min

// 查询帖子数大于 5 的用户
List users = easyEntityQuery.queryable(User.class)
    .where(u -> u.posts().count().gt(5L))
    .toList();

// 生成的 SQL:
// SELECT * FROM t_user t
// WHERE (SELECT COUNT(*) FROM t_post t1 WHERE t1.user_id = t.id) > 5
// 查询订单总金额超过 10000 的用户
List users = easyEntityQuery.queryable(User.class)
    .where(u -> u.orders()
        .sum(o -> o.amount())
        .gt(new BigDecimal("10000")))
    .toList();

// 生成的 SQL:
// SELECT * FROM t_user t
// WHERE (SELECT SUM(amount) FROM t_order t1 WHERE t1.user_id = t.id) > 10000

1.5 ORDER BY 中的子查询

// 按帖子数量排序
List users = easyEntityQuery.queryable(User.class)
    .orderBy(u -> u.posts().count().desc())
    .toList();

// 生成的 SQL:
// SELECT * FROM t_user t
// ORDER BY (SELECT COUNT(*) FROM t_post t1 WHERE t1.user_id = t.id) DESC

1.6 SELECT 中的子查询

// 查询用户及其帖子数量
List users = easyEntityQuery.queryable(User.class)
    .select(u -> new UserDTOProxy()
        .id().set(u.id())
        .username().set(u.username())
        .postCount().set(u.posts().count()))
    .toList();

// 生成的 SQL:
// SELECT t.id, t.username, 
//        (SELECT COUNT(*) FROM t_post t1 WHERE t1.user_id = t.id) AS post_count
// FROM t_user t

Source:

2. 子查询合并优化:从 N 次扫描到 1 次扫描

这是 easy‑query 子查询处理功能中最强大的特性。

2.1 问题:多个子查询导致的性能灾难

假设我们想查询每个用户的帖子数 以及 评论数:

// 标准写法
List users = easyEntityQuery.queryable(User.class)
    .select(u -> new UserDTOProxy()
        .id().set(u.id())
        .postCount().set(u.posts().count())
        .commentCount().set(u.comments().count()))
    .toList();

典型的 ORM 会为每个聚合生成单独的子查询,导致对子表进行多次扫描。

问题描述

在大数据集(例如 100 万用户500 万帖子1000 万评论)的情况下,使用对每个用户单独子查询的朴素做法会导致 200 万次 子表扫描。

SELECT t.id,
       (SELECT COUNT(*) FROM t_post WHERE user_id = t.id) AS post_count,
       (SELECT COUNT(*) FROM t_comment WHERE user_id = t.id) AS comment_count
FROM t_user t;

2.2 easy‑query 优化

子查询 → GROUP JOIN

easy‑query 会自动将多个子查询合并为单个 GROUP BY 连接。

SELECT t.id,
       IFNULL(t1.post_count, 0)      AS post_count,
       IFNULL(t2.comment_count, 0)   AS comment_count
FROM t_user t
LEFT JOIN (
    SELECT user_id, COUNT(*) AS post_count
    FROM t_post
    GROUP BY user_id
) t1 ON t.id = t1.user_id
LEFT JOIN (
    SELECT user_id, COUNT(*) AS comment_count
    FROM t_comment
    GROUP BY user_id
) t2 ON t.id = t2.user_id;

性能对比

方法t_post 扫描次数t_comment 扫描次数
子查询1 M1 M
GROUP JOIN11

潜在提速:100× – 1000×。

条件聚合 → CASE WHEN

Java (easy‑query)

// 统计每个用户已发布和草稿状态的帖子数
List users = easyEntityQuery.queryable(User.class)
    .select(u -> new UserDTOProxy()
        .id().set(u.id())
        .publishedCount().set(u.posts()
            .where(p -> p.status().eq(1)).count())
        .draftCount().set(u.posts()
            .where(p -> p.status().eq(0)).count()))
    .toList();

典型 ORM 输出(两个子查询):

SELECT t.id,
       (SELECT COUNT(*) FROM t_post WHERE user_id = t.id AND status = 1),
       (SELECT COUNT(*) FROM t_post WHERE user_id = t.id AND status = 0)
FROM t_user t;

easy‑query 优化后的 SQL(单个 GROUP BY + CASE WHEN):

SELECT t.id,
       SUM(CASE WHEN t1.status = 1 THEN 1 ELSE 0 END) AS published_count,
       SUM(CASE WHEN t1.status = 0 THEN 1 ELSE 0 END) AS draft_count
FROM t_user t
LEFT JOIN t_post t1 ON t.id = t1.user_id
GROUP BY t.id;

对同一表的多个条件聚合会合并为一次连接,并使用多个 CASE WHEN 表达式。

2.3 多层嵌套子查询

嵌套导航属性

// 有带点赞评论的帖子用户
List users = easyEntityQuery.queryable(User.class)
    .where(u -> u.posts()
        .any(p -> p.comments()
            .any(c -> c.likes().any())))
    .toList();

跨层级聚合

// 帖子总评论数 > 100 的用户
List users = easyEntityQuery.queryable(User.class)
    .where(u -> u.posts()
        .flatElement()
        .comments()
        .count()
        .gt(100L))
    .toList();

2.4 将子查询与显式 Join 结合

// 订单金额 > 1000 且有评论的用户
List users = easyEntityQuery.queryable(User.class)
    .innerJoin(Order.class, (u, o) -> u.id().eq(o.userId()))
    .where((u, o) -> {
        o.amount().gt(new BigDecimal("1000"));
        u.comments().any();               // 子查询与 Join 结合
    })
    .select((u, o) -> u)
    .distinct()
    .toList();

2.5 与其他 ORM 的功能对比

功能easy‑query其他 ORM A其他 ORM B
子查询自动合并为 GROUP JOIN
条件聚合自动转 CASE WHEN
多层嵌套子查询支持部分支持
与显式 Join 混用部分支持
特性easy‑queryMyBatis‑PlusJPA/HibernatejOOQ
隐式子查询 (any/count)✅ Lambda 语法❌ 原始 SQL❌ JPQL/Criteria❌ 手动
WHERE 中的子查询部分支持✅ 手动
ORDER BY 中的子查询✅ 手动
SELECT 中的子查询✅ 手动
子查询 → GROUP JOIN✅ 自动优化
条件聚合合并✅ 自动优化

2.6 为什么是“最强大”?

  • 最简语法u.posts().count().gt(5L) 读起来像自然语言。
  • 自动优化 – 子查询 → GROUP JOIN,条件聚合 → CASE WHEN
  • 全场景支持 – 在 WHEREORDER BYSELECT 中的子查询。
  • 强类型 – 编译期检查,便于重构。
  • 最佳性能 – 生成高性能 SQL,无需手动调优。

摘要

easy‑query 的子查询设计理念:

  1. 让复杂变简单 – 用简洁的 lambda 表达复杂的子查询逻辑。
  2. 让慢速变快速 – 自动将低效的 SQL 优化为高性能语句。
  3. 让手动变自动 – 开发者描述 what(需要做什么),框架决定 how(如何实现)。

如果你的应用大量依赖关系统计、存在性检查或聚合查询,试试 easy‑query 吧。

相关链接

  • GitHub:
  • Documentation:
Back to Blog

相关文章

阅读更多 »

Spring Data JPA 关系

介绍 新年快乐!在我 full‑stack 之旅的过去十天里,我在加入后立刻开始着手项目。起初,我在 Re...