公司动态

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

📅 2026/7/18 18:46:11
⚡ 持久层框架的不可能三角
我把三大主流的优点全收了缺点全扔了做 Java 后端开发快二十年持久层框架换了一茬又一茬。从裸写 JDBC到 Hibernate 一统天下再到 MyBatis 成为国内行业标配还有 Spring JDBC 不温不火当了二十年的「背景板」。所有人都默认一个潜规则持久层领域存在一个不可能三角——「单表对象化便捷」「100% SQL 能力可控」「扩展灵活低成本」三者最多占两个不可能全部兼得。我偏不信这个邪。花了三年时间打磨出Spring JDBC Ultra内核名 SimpleDAO今天就跟大家聊聊我是怎么把 Hibernate、MyBatis、Spring JDBC 三大主流的优点全部收归己有把它们的历史通病全部抛弃亲手打破这个持续了二十年的行业魔咒。一、二十年困局持久层的不可能三角我们先把三大主流框架摆上台面看看它们各自在三角里站了什么位置又付出了什么代价。1. Hibernate拿了「单表便捷」丢了「SQL自由」Hibernate 当年能横扫业界核心靠的就是单表对象化实体加注解继承接口零代码获得 CRUD开发效率拉满。但它的代价是毁灭性的为了对象化强行搞出一对多、多对多关系映射催生了 N1、笛卡尔积、懒加载、脏检查等无数史诗级坑用 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 个象限 APIList/Columns/Row/Field结合日志与参数类型进行正交重载纯面向业务设计。API 设计通常面向 SQL 抽象缺乏这种数学上正交完备、不重叠不遗漏的极简设计。1. 单表联表同一套 API 同一套条件类范式级的突破这是最核心的设计也是行业二十年从未解决的思维割裂问题。以前的框架单表是「对象思维」联表是「SQL 思维」两套体系完全割裂MyBatis-Plus 单表用 LambdaWrapper联表退回 XML条件不能复用JPA 单表用方法命名查询联表写 JPQLAPI 完全不通用。开发者要在两套思维里反复横跳同样的筛选条件单表写一遍联表再写一遍。我们从根上就用SQL-First 统一思维单表只是框架帮你生成了 SQL联表只是你自己写了 SQL上层 API 和条件类完全一致。你学会一个page()方法单表能用五表联查也能用你定义一个UserCond条件类单表查询用它联表聚合也用它。就这一点开发心智负担直接砍掉一半。2. 多组参数合并报表场景的效率倍增器这是专门针对复杂报表、多维度筛选场景的设计。以往你要叠加公共条件、数据权限、租户过滤要么侵入业务代码硬写要么自己拆工具类拼来拼去耦合严重、维护困难。我们原生支持多条件动态合并内置extendCondition标准扩展点公共条件、权限条件、租户条件可以在业务层之外透明注入业务代码完全无感知。多维度报表开发效率直接翻倍。3. 屏蔽第一层 if消灭 90% 的样板代码90% 的动态条件本质就是「参数非空就拼接」。以前不管是 Java 里写if (xx ! null)还是 XML 里写if testxx ! null都是重复劳动既啰嗦又容易漏判。我们把这层判空全部屏蔽了你只需要一行and(字段 , 值)空值自动跳过不用写任何if。光这一点就能消灭 DAO 层一半的样板代码。4. 2×2×2×2 正交完备 API面向业务而非面向 SQL绝大多数框架的 API 都是面向 SQL 设计的queryForList、queryForObject、queryForMap……命名混乱重载冗余用之前还要翻文档猜哪个合适。我们从业务结果出发把查询抽象成四个正交维度结果规模列表 / 单行结果范围全列实体 / 单列字段分页模式普通查询 / 分页查询参数形式条件类 / 可变参数16 个方法全覆盖不重叠、不遗漏见名知意。分页本质就是listfield(count)查询、更新、删除全支持条件类和可变参数设计极度统一。这不是 API 的简单堆砌是面向业务语义的数学级抽象。三、完整收益盘点代码量直接砍到 1/3空说设计太抽象我们直接上代码对比。看看从原生 Spring JDBC 切换到 Spring JDBC Ultra到底能省多少事。收益 1条件管理——从手工账本到声明式注册Spring JDBC 原生写法StringBuilder sql new StringBuilder(SELECT * FROM user WHERE 11); ListObject 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 JDBCparams.add(% name %);Spring JDBC Ultraand(name LIKE, name, 3); // 3前后都加%收益1左匹配、2右匹配、3全匹配省掉手动包装代码更直观。收益 3IN 查询——数组自动展开Spring JDBCif (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 Ultrain(id, ids);收益不再手动算占位符个数不再手动循环拼接不再手动addAll。收益 4单表 CRUD——继承空类即用Spring JDBC单表插入、更新全要手写KeyHolder、PreparedStatement回调一套下来 25 行样板代码起步。Spring JDBC UltraRepository public class UserDao extends BaseDaoUser { // 空类什么都不用写 }收益自动获得save、update、delete、findById、list、page全套能力审计字段自动填充。收益 5审计字段——全自动填充Spring JDBC每次插入更新都要手动 set 创建时间、创建人、更新时间。user.setCreateTime(LocalDateTime.now()); user.setCreateBy(currentUserId());Spring JDBC Ultrasave()自动填充createTime、createBy、dr0update()自动填充updateTime、updateBy。收益 6逻辑删除——自动判断Spring JDBC有dr字段要写UPDATE没有要写DELETE每个表都要自己判断处理。Spring JDBC UltrauserDao.delete(id);收益有逻辑删除字段则自动更新标记无则物理删除框架自动识别处理。收益 7行锁——一行开启Spring JDBC要单独写带FOR UPDATE的 SQL。SELECT * FROM user WHERE id ? FOR UPDATESpring JDBC UltraUser user userDao.findById(id, true);收益 8更新空值控制——按场景选择Spring JDBC要自己判断字段是否为 null决定要不要放进 UPDATE 语句。Spring JDBC UltrauserDao.update(user); // null 不参与更新90%场景 userDao.updateNull(user); // null 也参与更新收益 9单表联表同一套 API独创Spring JDBC单表用自动映射联表要么手写 RowMapper要么手动 set 字段。Spring JDBC Ultra// 单表分页查询 PageUser page userDao.page(cond); // 联表分页查询同样是 page 方法多传一个SQL PageUserVO page userDao.page(JOIN_SQL, cond, UserVO.class);收益学会一个page()单表联表通用心智模型完全统一。收益 10单表联表同一套条件独创Spring JDBC单表条件和联表条件两套写法参数要重新管理。Spring JDBC UltraUserCond 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 UltraPageUser page userDao.page(cond);收益COUNT SQL 自动生成分页参数自动计算方言自动适配MySQL、PostgreSQL、Oracle、SQL Server 全支持。收益 12批量操作——自动填充审计Spring JDBC自己拼batchArgs二维数组自己循环填充审计字段。Spring JDBC UltrauserDao.saveBatch(list);收益框架自动生成批量 INSERT SQL每条记录独立填充审计字段高性能批处理开箱即用。收益 13存在性校验——一行完成Spring JDBCInteger count jdbc.queryForObject(SELECT COUNT(*) FROM user WHERE id ?, Integer.class, id); boolean exists count 0;Spring JDBC Ultraboolean 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()ListT多行多列row()T单行多列行数不唯一抛异常columns()ListT多行单列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工作节点 >代码量对比直观的降维打击以一个「带 5 个条件 分页的联表查询」为例环节Spring JDBC 手写行数Spring JDBC Ultra 行数条件拼接 参数管理15-20 行5 行addCondition分页 COUNT SQL3-5 行0 行分页 LIMIT / OFFSET2-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-daoSpring JDBC Ultra 核心内核系统底座simple-dao-starter内置 RBAC 权限、用户/部门/字典/菜单管理开箱即用代码生成器simple-dao-coder全栈代码生成四套模板覆盖常规业务实战案例simple-dao-demo8 个可运行 Demo从入门到高阶全覆盖Spring JDBC Ultra 已在生产环境稳定运行 3 年支撑日均百万级请求服务十余家企业客户。如果你厌倦了复杂框架的折磨相信简单的力量欢迎来试试。技术可以更简单开发可以更愉快程序员可以早下班。