欢迎光临
我们一直在努力

⚡ 持久层框架的不可能三角

我把三大主流的优点全收了,缺点全扔了

做 Java 后端开发快二十年,持久层框架换了一茬又一茬。从裸写 JDBC,到 Hibernate 一统天下,再到 MyBatis 成为国内行业标配,还有 Spring JDBC 不温不火当了二十年的「背景板」。

所有人都默认一个潜规则:持久层领域存在一个不可能三角——「单表对象化便捷」「100% SQL 能力可控」「扩展灵活低成本」,三者最多占两个,不可能全部兼得。

我偏不信这个邪。

花了三年时间打磨出 Spring JDBC Ultra(内核名 SimpleDAO),今天就跟大家聊聊:我是怎么把 Hibernate、MyBatis、Spring JDBC 三大主流的优点全部收归己有,把它们的历史通病全部抛弃,亲手打破这个持续了二十年的行业魔咒。


一、二十年困局:持久层的不可能三角

我们先把三大主流框架摆上台面,看看它们各自在三角里站了什么位置,又付出了什么代价。

1. Hibernate:拿了「单表便捷」,丢了「SQL自由」

Hibernate 当年能横扫业界,核心靠的就是单表对象化:实体加注解,继承接口,零代码获得 CRUD,开发效率拉满。 但它的代价是毁灭性的:

  • 为了对象化,强行搞出一对多、多对多关系映射,催生了 N+1、笛卡尔积、懒加载、脏检查等无数史诗级坑;
  • 用 HQL/Criteria 屏蔽原生 SQL,复杂联表、窗口函数、CTE 等高级语法支持极差,数据库 1/3 的能力都发挥不出来;
  • SQL 生成全黑盒,线上慢 SQL 优化极其困难,你永远不知道框架底层拼出了什么奇葩语句。

它用「SQL 完全失控」,换来了「单表开发效率」。

2. MyBatis:拿了「SQL自由」,戴了「一身枷锁」

MyBatis 的崛起,本质是对 Hibernate 黑盒的反噬——开发者终于能自己写 SQL 了,100% 掌控数据库能力。 但为了实现「动态 SQL」,它走上了另一条弯路:

  • 用 XML 模板 + OGNL 表达式模拟 Java 的 if/for 逻辑,弱语言模拟强语言,代码量只增不减;
  • Mapper 动态代理、ResultMap 映射、插件拦截器,一整套私有体系,学习成本极高;
  • 执行链路全黑盒,调试只能靠日志猜,扩展必须扒源码写拦截器,所谓「生态繁荣」全是补坑插件。

它用「一整套框架枷锁」,换来了「SQL 书写自由」。

3. Spring JDBC:拿了「白盒+低成本」,缺了「工程化封装」

Spring JDBC 是三大主流里底子最干净、架构最正确的一个:

  • 全链路白盒,SQL 就是普通字符串,断点随便打,排错秒定位;
  • 100% 复用 Spring 原生基建,事务、多数据源、AOP、监控零适配;
  • 扩展成本极低,分页、多租户、数据权限,本质就是字符串拼接,新手都能做。

但它有一个致命短板:太「裸」了,工程化能力几乎为零。 单表 CRUD 要手写 SQL,动态条件要自己判空拼接,分页要自己算 count、拼 limit,审计字段、逻辑删除全要团队自己重复造轮子。每个项目都要封装一套通用 DAO,重复劳动泛滥。

它用「开箱即用的便利性」,换来了「极致的干净与灵活」。


二十年了,行业一直在做选择题:

  • 想单表省事,选 Hibernate/JPA,忍 SQL 黑盒;
  • 想 SQL 自由,选 MyBatis,忍 XML 枷锁和插件地狱;
  • 想干净灵活,选 Spring JDBC,忍重复封装的苦力活。

而我做 Spring JDBC Ultra 的初衷很简单:不做选择题,我全都要。 站在 Spring JDBC 的黄金底子上,补上工程化的短板,同时把 Hibernate 和 MyBatis 最有价值的部分拿过来——不带一点糟粕。

最终的结果就是:三角全拿,没有妥协。


二、五大独创设计:击穿行业二十年的思维惯性

这五个设计,是以往所有持久层框架都没有真正做好、甚至从未想过的,也是我们打破不可能三角的核心支柱。

核心独创点本框架设计亮点主流框架(JPA / MyBatis-Plus)痛点
1. 单表/联表同一套 API 采用统一的心智模型,单表与联表查询无缝切换,无需另起炉灶或推倒重来。 单表一套 API,联表必须换一套(如退回原生 SQL),心智严重割裂。
2. 单表/联表同一套条件类 条件类设计不区分单表与联表,完全复用,极大简化了代码编写。 单表与联表通常需要不同的条件构建方式,增加了学习和维护成本。
3. 多组参数合并(多条件合并) 原生支持多条件合并,在复杂的报表场景中特别实用且高效。 缺乏原生支持,通常需要开发者手动拼接条件,维护成本高。
4. 屏蔽第一层 if 彻底屏蔽了 90% 以上的第一层 if 判空逻辑,大幅简化代码。 动态条件拼接往往需要大量 if 判空,代码冗余且极易出错。
5. 2×2×2×2 正交完备 API 基于集合运算结果,抽象出 4 个象限 API(List/Columns/Row/Field),结合日志与参数类型进行正交重载,纯面向业务设计。 API 设计通常面向 SQL 抽象,缺乏这种数学上正交完备、不重叠不遗漏的极简设计。

1. 单表联表同一套 API + 同一套条件类:范式级的突破

这是最核心的设计,也是行业二十年从未解决的思维割裂问题。

以前的框架,单表是「对象思维」,联表是「SQL 思维」,两套体系完全割裂:

  • MyBatis-Plus 单表用 LambdaWrapper,联表退回 XML,条件不能复用;
  • JPA 单表用方法命名查询,联表写 JPQL,API 完全不通用。

开发者要在两套思维里反复横跳,同样的筛选条件,单表写一遍,联表再写一遍。

我们从根上就用 SQL-First 统一思维: 单表只是框架帮你生成了 SQL,联表只是你自己写了 SQL,上层 API 和条件类完全一致。 你学会一个 page() 方法,单表能用,五表联查也能用; 你定义一个 UserCond 条件类,单表查询用它,联表聚合也用它。

就这一点,开发心智负担直接砍掉一半。

2. 多组参数合并:报表场景的效率倍增器

这是专门针对复杂报表、多维度筛选场景的设计。

以往你要叠加公共条件、数据权限、租户过滤,要么侵入业务代码硬写,要么自己拆工具类拼来拼去,耦合严重、维护困难。 我们原生支持多条件动态合并,内置 extendCondition 标准扩展点,公共条件、权限条件、租户条件可以在业务层之外透明注入,业务代码完全无感知。

多维度报表开发,效率直接翻倍。

3. 屏蔽第一层 if:消灭 90% 的样板代码

90% 的动态条件,本质就是「参数非空就拼接」。 以前不管是 Java 里写 if (xx != null),还是 XML 里写 <if test="xx != null">,都是重复劳动,既啰嗦又容易漏判。

我们把这层判空全部屏蔽了:你只需要一行 and("字段 =", 值),空值自动跳过,不用写任何 if。 光这一点,就能消灭 DAO 层一半的样板代码。

4. 2×2×2×2 正交完备 API:面向业务,而非面向 SQL

绝大多数框架的 API 都是面向 SQL 设计的:queryForList、queryForObject、queryForMap……命名混乱,重载冗余,用之前还要翻文档猜哪个合适。

我们从业务结果出发,把查询抽象成四个正交维度:

  • 结果规模:列表 / 单行
  • 结果范围:全列实体 / 单列字段
  • 分页模式:普通查询 / 分页查询
  • 参数形式:条件类 / 可变参数

16 个方法全覆盖,不重叠、不遗漏,见名知意。分页本质就是 list + field(count),查询、更新、删除全支持条件类和可变参数,设计极度统一。

这不是 API 的简单堆砌,是面向业务语义的数学级抽象。


三、完整收益盘点:代码量直接砍到 1/3

空说设计太抽象,我们直接上代码对比。看看从原生 Spring JDBC 切换到 Spring JDBC Ultra,到底能省多少事。

收益 1:条件管理——从手工账本到声明式注册

Spring JDBC 原生写法:

StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1");
List<Object> params = new ArrayList<>();
if (name != null && !name.isEmpty()) {
sql.append(" AND name LIKE ?");
params.add("%" + name + "%");
}
if (ageMin != null) {
sql.append(" AND age >= ?");
params.add(ageMin);
}
// 8个条件后,sql和params很容易不同步
Object[] finalParams = params.toArray();

Spring JDBC Ultra 写法:

@Override
protected void addCondition() {
and("name LIKE", name, 3);
and("age >=", ageMin);
and("age <=", ageMax);
in("id", ids);
}

收益: 条件与参数绑定,不会错位;新增条件一行搞定;空值自动跳过,无需手写判空。

收益 2:模糊查询——自动处理 %

Spring JDBC:

params.add("%" + name + "%");

Spring JDBC Ultra:

and("name LIKE", name, 3); // 3=前后都加%

收益: 1=左匹配、2=右匹配、3=全匹配,省掉手动包装,代码更直观。

收益 3:IN 查询——数组自动展开

Spring JDBC:

if (ids != null && ids.length > 0) {
sql.append(" AND id IN (");
for (int i = 0; i < ids.length; i++) {
sql.append(i == 0 ? "?" : ",?");
}
sql.append(")");
params.addAll(Arrays.asList(ids));
}

Spring JDBC Ultra:

in("id", ids);

收益: 不再手动算占位符个数,不再手动循环拼接,不再手动 addAll。

收益 4:单表 CRUD——继承空类即用

Spring JDBC: 单表插入、更新全要手写,KeyHolder、PreparedStatement 回调,一套下来 25 行样板代码起步。

Spring JDBC Ultra:

@Repository
public class UserDao extends BaseDao<User> {
// 空类,什么都不用写
}

收益: 自动获得 save、update、delete、findById、list、page 全套能力,审计字段自动填充。

收益 5:审计字段——全自动填充

Spring JDBC: 每次插入更新都要手动 set 创建时间、创建人、更新时间。

user.setCreateTime(LocalDateTime.now());
user.setCreateBy(currentUserId());

Spring JDBC Ultra: save() 自动填充 createTime、createBy、dr=0;update() 自动填充 updateTime、updateBy。

收益 6:逻辑删除——自动判断

Spring JDBC: 有 dr 字段要写 UPDATE,没有要写 DELETE,每个表都要自己判断处理。

Spring JDBC Ultra:

userDao.delete(id);

收益: 有逻辑删除字段则自动更新标记,无则物理删除,框架自动识别处理。

收益 7:行锁——一行开启

Spring JDBC: 要单独写带 FOR UPDATE 的 SQL。

SELECT * FROM user WHERE id = ? FOR UPDATE

Spring JDBC Ultra:

User user = userDao.findById(id, true);

收益 8:更新空值控制——按场景选择

Spring JDBC: 要自己判断字段是否为 null,决定要不要放进 UPDATE 语句。

Spring JDBC Ultra:

userDao.update(user); // null 不参与更新(90%场景)
userDao.updateNull(user); // null 也参与更新

收益 9:单表联表同一套 API(独创)

Spring JDBC: 单表用自动映射,联表要么手写 RowMapper,要么手动 set 字段。

Spring JDBC Ultra:

// 单表分页查询
Page<User> page = userDao.page(cond);

// 联表分页查询(同样是 page 方法,多传一个SQL)
Page<UserVO> page = userDao.page(JOIN_SQL, cond, UserVO.class);

收益: 学会一个 page(),单表联表通用,心智模型完全统一。

收益 10:单表联表同一套条件(独创)

Spring JDBC: 单表条件和联表条件两套写法,参数要重新管理。

Spring JDBC Ultra:

UserCond cond = UserCond.builder().name("张").build();

// 单表用这套条件
userDao.page(cond);

// 联表也用这套条件
userDao.page(JOIN_SQL, cond, UserVO.class);

收益: 条件定义一次,全场景复用。条件层不关心你查几张表。

收益 11:分页——一行搞定

Spring JDBC: 自己写 COUNT SQL、自己算总条数、自己拼接 LIMIT/OFFSET,一套下来 8 行起步。

String countSql = "SELECT COUNT(*) FROM user" + where;
Integer total = jdbc.queryForObject(countSql, Integer.class, params);
String pageSql = sql + where + " LIMIT ? OFFSET ?";

Spring JDBC Ultra:

Page<User> page = userDao.page(cond);

收益: COUNT SQL 自动生成,分页参数自动计算,方言自动适配(MySQL、PostgreSQL、Oracle、SQL Server 全支持)。

收益 12:批量操作——自动填充审计

Spring JDBC: 自己拼 batchArgs 二维数组,自己循环填充审计字段。

Spring JDBC Ultra:

userDao.saveBatch(list);

收益: 框架自动生成批量 INSERT SQL,每条记录独立填充审计字段,高性能批处理开箱即用。

收益 13:存在性校验——一行完成

Spring JDBC:

Integer count = jdbc.queryForObject("SELECT COUNT(*) FROM user WHERE id = ?", Integer.class, id);
boolean exists = count > 0;

Spring JDBC Ultra:

boolean exists = userDao.exists(cond);

收益 14:运行时注入的参数化动态条件(独创)

MyBatis 的「动态 SQL」是编译期决定的——XML 里的 <if> 要预判所有分支,运行时想追加条件只能去拦截器里改 BoundSql,参数映射全靠手搓。

Spring JDBC Ultra:

// 在 AOP 切面中运行时注入
cond.addDynamic(" AND school_id = ?", currentUser.getSchoolId());

收益: 走 PreparedStatement 占位符,和原生条件同样安全。数据权限、多租户、行级过滤,全部通过 AOP 切面零侵入完成,不用写任何拦截器。

收益 15:执行层正交完备——16 个方法全覆盖

4 种查询形态 × 2 种参数类型 × 有无日志重载,所有场景精准覆盖:

方法返回类型用途
list() List<T> 多行多列
row() T 单行多列(行数不唯一抛异常)
columns() List<T> 多行单列
field() T 单行单列

参数形式支持 cond 条件类,也支持手动 Object… 可变参数;每个方法都有 boolean showSql 重载。

收益 16:完整 SQL 日志,复制即执行

日志打印的是参数已替换的完整 SQL,复制出来就能直接在数据库客户端执行验证。 不需要手动把 ? 一个个替换成参数值,排错效率翻倍。

收益 17:异常就是 JDBC 异常,没有中间商

完全继承 Spring JDBC 的异常体系,不额外包装、不造专属异常。 你看到的异常就是数据库报的错,没有 MyBatis 那 31 类框架自造异常的层层嵌套。

收益 18:配置项极少——四五个,开箱即用

simple-dao:
logic-delete.field: dr # 逻辑删除字段
show-sql: true # 是否打印带参SQL
worker-id: 0 # 雪花ID工作节点
data-center-id: 0 # 雪花ID数据中心
dialect: mysql # 数据库方言(自动检测)


代码量对比:直观的降维打击

以一个「带 5 个条件 + 分页的联表查询」为例:

环节Spring JDBC 手写行数Spring JDBC Ultra 行数
条件拼接 + 参数管理 15-20 行 5 行(addCondition)
分页 COUNT SQL 3-5 行 0 行
分页 LIMIT / OFFSET 2-3 行 0 行
参数数组导出 1 行 0 行
联表结果映射 10-20 行 0 行
单表 CRUD 方法 20-30 行/表 0 行
审计字段填充 3-5 行/方法 0 行
逻辑删除处理 3-5 行/表 0 行

结论:同等业务下,代码量压缩至 MyBatis 的 1/3 ~ 1/4,原生 Spring JDBC 的 1/4 ~ 1/5。


四、最关键的底线:我们没有引入任何新问题

很多框架做增强,都是按下葫芦浮起瓢:解决了老问题,带来了新坑。 而 Spring JDBC Ultra 最核心的设计原则是:只做加法,不做改变。

  • 我们没有替换执行层,最终 SQL 还是走 JdbcTemplate 执行,性能和原生 Spring JDBC 几乎无差;
  • 我们没有造新的异常体系,报错就是数据库原生错误,没有中间商赚差价;
  • 我们没有破坏 Spring 生态,事务、多数据源、缓存、AOP、监控全部拿来就用;
  • 我们没有搞黑盒魔法,所有生成的 SQL 都能打印,所有逻辑都在 Java 代码里,断点随便打。

它不是一个「另起炉灶」的新持久层框架,它就是 Spring JDBC 的语法糖,是工程化增强版。 你本来就要用 Spring JDBC,用了它,只是少写了很多样板代码,多了很多标准化能力,其他一切不变。

甚至存量 MyBatis 项目都不用迁移,直接引入依赖就能共存:老代码不动,新业务用 Ultra 写。零成本切换,没有任何风险。


五、一个全新的生态位:集三家之长,无一家之短

回到开头的「不可能三角」。 现在我们可以很笃定地说:这个持续了二十年的行业魔咒,被打破了。

  • ✅ 我们有 Hibernate 级别的单表便捷性:继承空类就有全套 CRUD,审计、逻辑删除自动处理;
  • ✅ 我们有 MyBatis 级别的 SQL 自由度:甚至更自由,没有 XML 限制,没有标签束缚,100% 发挥数据库能力;
  • ✅ 我们有 Spring JDBC 级别的白盒透明度:AOP 就能做所有业务增强,扩展成本低到新手都能上手。

更重要的是:

  • ❌ 我们没有 Hibernate 的关系映射坑、SQL 黑盒坑、缓存脏读坑;
  • ❌ 我们没有 MyBatis 的 XML 枷锁、Mapper 代理、拦截器地狱、补丁生态;
  • ❌ 我们没有原生 Spring JDBC 的样板代码泛滥、工程化缺失、重复造轮子。

二十年了,终于有一个框架,不用让开发者做选择题。


六、配套工具链与生产验证

我们不是只做了一个核心框架,而是一整套完整的生产级解决方案:

项目地址说明
核心框架 simple-dao Spring JDBC Ultra 核心内核
系统底座 simple-dao-starter 内置 RBAC 权限、用户/部门/字典/菜单管理,开箱即用
代码生成器 simple-dao-coder 全栈代码生成,四套模板覆盖常规业务
实战案例 simple-dao-demo 8 个可运行 Demo,从入门到高阶全覆盖

Spring JDBC Ultra 已在生产环境稳定运行 3 年+,支撑日均百万级请求,服务十余家企业客户。

如果你厌倦了复杂框架的折磨,相信简单的力量,欢迎来试试。

技术可以更简单,开发可以更愉快,程序员可以早下班。

赞(0)
未经允许不得转载:171主机测评 » ⚡ 持久层框架的不可能三角
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址