easy-query:Java 最强大的 ORM 子查询
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
// )
all 与 notEmptyAll 的区别:
| 方法 | 空集合 | 所有元素匹配 | 部分元素不匹配 |
|---|---|---|---|
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 M | 1 M |
| GROUP JOIN | 1 | 1 |
潜在提速: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‑query | MyBatis‑Plus | JPA/Hibernate | jOOQ |
|---|---|---|---|---|
隐式子查询 (any/count) | ✅ Lambda 语法 | ❌ 原始 SQL | ❌ JPQL/Criteria | ❌ 手动 |
| WHERE 中的子查询 | ✅ | ❌ | 部分支持 | ✅ 手动 |
| ORDER BY 中的子查询 | ✅ | ❌ | ❌ | ✅ 手动 |
| SELECT 中的子查询 | ✅ | ❌ | ❌ | ✅ 手动 |
| 子查询 → GROUP JOIN | ✅ 自动优化 | ❌ | ❌ | ❌ |
| 条件聚合合并 | ✅ 自动优化 | ❌ | ❌ | ❌ |
2.6 为什么是“最强大”?
- 最简语法 –
u.posts().count().gt(5L)读起来像自然语言。 - 自动优化 – 子查询 →
GROUP JOIN,条件聚合 →CASE WHEN。 - 全场景支持 – 在
WHERE、ORDER BY、SELECT中的子查询。 - 强类型 – 编译期检查,便于重构。
- 最佳性能 – 生成高性能 SQL,无需手动调优。
摘要
easy‑query 的子查询设计理念:
- 让复杂变简单 – 用简洁的 lambda 表达复杂的子查询逻辑。
- 让慢速变快速 – 自动将低效的 SQL 优化为高性能语句。
- 让手动变自动 – 开发者描述 what(需要做什么),框架决定 how(如何实现)。
如果你的应用大量依赖关系统计、存在性检查或聚合查询,试试 easy‑query 吧。
相关链接
- GitHub:
- Documentation: