8 集实战教程从入门到精通集数 · 标题本集目录时长01 · 单表 CRUD 审计 逻辑删除实体注解 · 空 DAO · 保存审计 · ID查询 · 分页 · 逻辑删除约 6 min02 · 联表查询 分页联表 SQL · VO定义 · 条件类 · page调用 · 高性能COUNT约 4 min03 · 条件进阶IN 子查询IN自动展开 · 子查询拼接 · add vs and · 三种动态边界约 6 min04 · 多表联查 复杂条件行锁 · updateNull · 重复性校验 · 三表联查 · 时间范围约 6 min05 · 报表聚合GROUP BY 聚合函数三表JOIN聚合 · 条件类复用 · 独立判空 · 日志控制到方法约 6 min06 · mergeParams 多组条件合并多条件类定义 · SQL多位置嵌入 · mergeParams合并 · 条件复用约 5 min07 · 多租户 数据权限 · AOP 破局Filter AOP链路 · extendCondition钩子 · 最小路径演示约 7 min08 · 脱敏 审计扩展 · 框架不设限字段脱敏VO getter· 审计重写 · 逻辑删除调整约 7 min写在前面一个“异类”的诞生我写持久层框架开源之后经常收到一个问题“你这框架跟 MyBatis 有什么区别跟 JPA 有什么区别”我的回答是它们都在做“映射”我在做“连接”。它们面向数据库设计我面向业务设计。这个区别看似很小实则是一条分水岭。我从 8 个 Demo 案例、2 组生产案例、16 个痛点场景一路验证下来逐步构建起一套完整的理论体系——SQL-First 范式。今天这篇文章就是把这套范式和它的实现框架Spring JDBC Ultra开源项目名SimpleDAO完整地介绍给你。一、先看现实三大主流框架各有各的“死穴”在 Spring JDBC Ultra 出现之前Java 持久层生态由三大主流把持。我们心平气和地看看它们各自的优劣势1. Hibernate / JPA维度评价✅ 单表 CRUD极强save、findById非常方便✅ 对象关系映射有OneToMany、ManyToOne适合简单主子表❌ 复杂 SQLJPQL 能力有限标量子查询、派生表、窗口函数几乎不可用❌ 性能不可控N1 问题、懒加载陷阱、SQL 生成黑盒一句话总结单表是王者复杂查询是青铜。2. MyBatis维度评价✅ SQL 自由度原生 SQL 随便写不受 JPQL 限制❌ 组织方式被 XML 绑架SQL 被标签切割成碎片维护成本高❌ 动态条件if、foreach标签地狱OGNL 表达式黑盒❌ 扩展能力拦截器体系复杂数据权限、多租户等扩展要扒源码一句话总结SQL 是自由的但被 XML 套上了枷锁。你说得对我重新琢磨了一下——“无框架级拦截器但可以用 Spring AOP”这恰恰是 Spring JDBC 的白盒优势不是短板。修正后的对比表3. Spring JDBC维度评价✅ SQL 自由度极致的自由想写什么写什么✅ 白盒执行过程完全透明没有任何黑盒拦截器✅ 结果映射自动映射BeanPropertyRowMapper支持下划线转驼峰✅ 扩展能力无框架级拦截器但可以利用Spring AOP做数据权限、多租户等横切逻辑100% 白盒可控❌ 单表对象化无save、update、delete全部手写 SQL❌ 条件拼接手写WHERE拼字符串?占位符顺序要人工对齐❌ 分页手写LIMIT/ROWNUM/OFFSET FETCH换数据库要重写❌ 审计字段手写createTime、updateTime赋值❌ 日志需要自己配 Logback 打印占位符 SQL一句话总结白盒透明AOP 扩展无上限但条件拼接、分页、审计、日志全要手写繁琐。MyBatis 的拦截器黑盒你要学它那套Interceptor接口、Invocation对象、Intercepts注解还容易跟别的插件冲突属于“框架强加的扩展机制”。Spring JDBC 的扩展没有内置拦截器但你可以用Spring AOP做任何事——Around切 Service 层或 DAO 层纯原生 Spring 语法不需要理解任何 MyBatis 内部结构。“无内置拦截器”不是缺点是设计选择——把扩展能力交还给 Spring 生态最原生的 AOP这才是白盒的极致体现。这三个框架各自解决了某个方面的问题又各自在另一个方面留下了巨大的坑。开发者常年在这三者之间反复横跳始终没有一个方案能“一杆清台”。二、一个大胆的尝试把三者的优点集于一身于是我开始思考能不能做一个框架把三者的优点全部继承把三者的缺点全部剔除继承 Hibernate 的单表对象化——但绝不搞OneToMany那种对象嵌套。继承 MyBatis 的SQL 全自由——但绝不把 SQL 塞进 XML。继承 Spring JDBC 的纯白盒透明——但把参数传递和结果映射自动化。这就是Spring JDBC Ultra开源项目名SimpleDAO的起点。它不是“第四个选项”它是前三个的“完全体”。三、核心设计三大主类解决 90% 的痛点1.BaseDao—— 单表 CRUD 的“零代码”实现RepositorypublicclassUserDaoextendsBaseDaoUser{// 空类继承即获得所有 CRUD 能力}一个空类你就拥有了save(T)/saveBatch(ListT)update(T)/updateNull(T)delete(id...)/delete(Cond)findById(id)/findOne(Cond)list(Cond)/page(Cond)/count(Cond)/exists(Cond)注解驱动DataTable(sys_user)publicclassUser{Id// 默认雪花算法也支持 AUTO / UUID / CUSTOMprivateLongid;privateStringname;privateIntegerage;privateLocalDateTimecreateTime;// save 时自动填充privateLongcreateBy;// save 时自动填充privateLocalDateTimeupdateTime;// update 时自动填充privateLongupdateBy;// update 时自动填充privateBytedr;// 逻辑删除字段}就这么简单。没有 XML没有PrePersist没有拦截器配置。2.BaseSql—— 联表查询的“无限自由”单表用BaseDao联表用BaseSql。API 完全一致RepositorypublicclassOrderDaoextendsBaseDaoOrder{privatestaticfinalStringJOIN_SQL SELECT o.*, u.name user_name, u.phone user_phone FROM bus_order o LEFT JOIN sys_user u ON o.user_id u.id ;publicPageOrderVOpageJoin(OrderCondcond){returnpage(JOIN_SQL,cond,OrderVO.class);}}支持所有 SQL 特性标量子查询SELECT o.*, (SELECT COUNT(1) FROM items WHERE order_id o.id) AS cnt FROM orders o半连接WHERE EXISTS (SELECT 1 FROM items WHERE order_id o.id)派生表JOIN (SELECT user_id, COUNT(1) cnt FROM orders GROUP BY user_id) stats ON stats.user_id u.id窗口函数、CTE、UNION、数据库专有函数JSON_EXTRACT、GROUP_CONCAT等框架不解析 SQL所以以上全部支持。你能写出来的 SQL框架就能映射出来。3.BaseCondition—— 条件拼接的“语义单元”MyBatis 的动态 SQL 靠 XML 标签Spring JDBC Ultra 靠 Java 代码GetterSetterpublicclassUserCondextendsBaseCondition{privateStringname;privateIntegerageMin;privateIntegerageMax;privateBytestatus;privateObject[]ids;OverrideprotectedvoidaddCondition(){and(name LIKE,name,3);// 3前后模糊and(age ,ageMin);and(age ,ageMax);and(status ,status);in(id,ids);// 关联表条件直接用 addadd(AND u.dept_id ?,deptId);add(AND u.role IN ,roleIds);// IN 条件自动展开// 带逻辑开关的条件add(AND u.id IN (SELECT user_id FROM orders WHERE status 1),hasOrder);}}关键洞察这不是“动态条件”这是“静态条件 动态参数”。真正的动态条件运行时决定列名用addDynamic AOP 实现见后文。四、16 个痛点逐个拿下下面我把日常开发中最常遇到的 16 个痛点以及 Spring JDBC Ultra 的解法逐一列出来。痛点 1单表 CRUD 样板代码传统方案Spring JDBC Ultra每张表手写 insert/update/delete/select继承BaseDaoT空类获得全部能力改一个字段改 5 处代码只改实体类痛点 2审计字段手工填充传统方案Spring JDBC UltracreateTime/createBy每次手动 setsave时自动注入updateTime/updateBy每次手动 setupdate时自动注入MyBatis 要写拦截器JPA 要写PrePersist零配置实体类定义字段即可// 你只需要在实体类里定义这些字段privateLocalDateTimecreateTime;privateLongcreateBy;privateLocalDateTimeupdateTime;privateLongupdateBy;// 剩下的框架自动完成痛点 3逻辑删除标准化缺失传统方案Spring JDBC Ultra有的表用del_flag有的用is_deleted统一配置simple-dao.logic-delete.field: dr手写update t set dr 1delete(id)自动变成逻辑删除痛点 4SQL 日志信息黑洞传统方案Spring JDBC UltraMyBatis 打印WHERE name ?参数另起一行打印完整 SQLWHERE name 张三调试要手动替换 20 个?复制日志直接贴到 Navicat 执行日志要么全开要么全关方法级控制list(true, cond)打印list(false, cond)不打印这是我最得意的功能之一——Sql.fill(sql, params)把占位符全部替换成真实值日志即调试工具。痛点 5动态条件拼接的“标签地狱”传统方案Spring JDBC UltraMyBatis XML 里if嵌套foreach200 行起步Java 代码里直接ifand()一行一个条件改条件要改 XML容易漏闭合标签IDE 重构、高亮、跳转全支持痛点 6联表查询的对象映射灾难传统方案Spring JDBC UltraJPA 的OneToMany导致 N1直接写 SQLlist(SQL, cond, VO.class)MyBatis 的resultMap写 100 行 XML列名和 VO 字段名匹配即可零配置12 表联查基本不可维护12 表联查SQL 文本块直接写痛点 7标量子查询 / 半连接 / 派生表传统方案Spring JDBC UltraJPQL 完全不支持SQL 文本块直接写JPA 只能退回nativeQuery true框架不做任何限制MyBatis 能写但 SQL 被 XML 切碎完整 SQL 保留在 Java 里Stringsql SELECT o.*, (SELECT COUNT(1) FROM order_item WHERE order_id o.id) AS item_count, (SELECT SUM(amount) FROM payment WHERE order_id o.id) AS paid_amount FROM orders o WHERE EXISTS (SELECT 1 FROM order_item WHERE order_id o.id AND price 1000) ;// 这个 SQL 在 JPA 里写不出来在 Spring JDBC Ultra 里直接跑痛点 8数据库专有函数传统方案Spring JDBC UltraJPA 用FUNCTION(JSON_EXTRACT, ...)直接写JSON_EXTRACTMyBatis 用${}有注入风险直接写参数部分依然走?占位符痛点 9分页方言差异传统方案Spring JDBC UltraMySQL 用LIMITOracle 用ROWNUMSQL Server 用OFFSET FETCH4 个 Dialect 类自动适配手写分页换数据库重写 SQL自动检测数据库类型零配置痛点 10数据权限 / 多租户传统方案Spring JDBC UltraMyBatis 拦截器解析 SQL风险高AOP addDynamic注入条件片段JPAFilter黑盒操作列名由运行时决定值走预编译AspectComponentpublicclassDataAuthAspect{Around(annotation(dataAuth))publicObjectinjectAuth(ProceedingJoinPointpjp,DataAuthdataAuth){BaseConditioncond(BaseCondition)pjp.getArgs()[0];StringuserIdgetCurrentUserId();// 真正的动态条件列名由运行时决定cond.addDynamic( AND dataAuth.userField() ?,userId);returnpjp.proceed();}}痛点 11多条件类参数合并传统方案Spring JDBC Ultra多个子查询不同条件要揉进一个 DTO每个子查询用独立的 Cond 类XML 里用if判断来源极易混乱mergeParams(cond1, cond2, cond3)自动合并Stringsql SELECT ... FROM (子查询1 WHERE条件A) a LEFT JOIN (子查询2 WHERE条件B) b ;returnlist(sql,VO.class,mergeParams(condA,condB));痛点 12结果集映射的重复劳动传统方案Spring JDBC UltraRowMapper手写rs.getString(name)BeanPropertyRowMapper自动映射换字段就要改RowMapper列名和 VO 字段名匹配即可下划线转驼峰要手动处理自动转换user_name→userName痛点 13批量操作的性能优化传统方案Spring JDBC Ultra逐条插入 1000 条数据saveBatch(list)生成真正的批量 INSERTJPA 的saveAll是逐条 insertMySQL 支持replaceBatch批量 Upsert痛点 14分布式主键生成传统方案Spring JDBC Ultra数据库自增 ID 分库分表不可用Id(snow)雪花算法UUID 太长影响索引性能worker-id和data-center-id支持集群配置雪花算法要自己实现一行注解解决痛点 15SQL 注入传统方案Spring JDBC UltraMyBatis 的${}是 SQL 注入高发区所有值传递强制走?占位符JPA 的nativeQuery同样存在拼接风险SqlSecurityChecker检查动态 SQL 片段痛点 16跨语言复刻传统方案Spring JDBC UltraHibernate 只在 Java 生态已复刻到 8 种语言Java、C#、Python、Go、Rust、PHP、Node.js、C换语言要重学一套 ORM范式跟着 SQL 走跨语言知识复用五、三方对比SimpleDAO 如何“集大成”把三者的优劣势和 SimpleDAO 的定位放在一起对比一目了然能力维度Hibernate/JPAMyBatisSpring JDBCSimpleDAO单表 CRUD 自动化✅ 强❌ 弱❌ 无✅强联表 SQL 自由度❌ 受限✅ 全自由✅ 全自由✅全自由SQL 组织方式HQL/JPQLXML 标签Java 字符串需手动拼接✅Java 文本块 条件类动态条件表达Criteria API冗长if/foreach标签地狱手写WHERE拼字符串✅Java add语义单元参数传递自动JPQL 占位符自动#{}自动JdbcTemplate可变参数 / 命名参数✅自动 顺序精准结果映射自动含对象嵌套自动resultMap或列名匹配自动BeanPropertyRowMapper✅自动平铺映射日志占位符 SQL 参数分离占位符 SQL 参数分离占位符 SQL 参数分离✅完整带参 SQL执行透明度黑盒SQL 生成不可见灰盒SQL 可见但拦截器改写白盒SQL 即执行 SQL✅纯白盒扩展能力监听器受限拦截器复杂Spring AOP原生白盒✅Spring AOP 内置扩展点数据库专有函数❌ 需FUNCTION包装✅ 可用${}有注入风险✅ 可用✅直接写无限制复杂子查询/派生表❌ JPQL 不支持✅ 可用SQL 被 XML 切碎✅ 可用✅直接写无限制六、三个字总结不封装Spring JDBC Ultra 的设计哲学可以用三个“不封装”来概括不封装 SQL 的内容你不写 HQL、不写 JPQL、不写 XML 标签你直接写 SQL。SQL 是 4GL第四代语言是数据库的母语不需要被“翻译”成任何中间语言。不封装 SQL 的能力标量子查询、半连接、派生表、窗口函数、CTE、数据库专有函数——你随便写。框架不做任何“能力阉割”。不封装 SQL 的结果ResultSet映射到 VO 是唯一的封装且是“平铺映射”。复杂对象嵌套是业务表达的范畴在 Service 层用 Java 集合做内存组装绝不把树形结构强塞给 SQL。七、核心结论面向业务设计而非面向数据库设计所有已知的持久层框架都是面向数据库设计的。Spring JDBC Ultra 是唯一一个面向业务设计的。Hibernate/JPA先定义Entity、OneToMany再让业务逻辑适配这个模型。MyBatis先写 Mapper 接口 XML再让 SQL 适配 XML 的标签语法。Spring JDBC Ultra业务需要什么数据形状你就写什么 SQLSQL 怎么写框架就怎么帮你传参和映射。这就是 SQL-First 范式的核心不是“少写 SQL”而是让 SQL 回归它本来的位置——作为业务表达的直接载体。框架不定义业务规则业务规则由开发者的 SQL 和 Java 代码定义。八、为什么说它是“元模型”SimpleDAO 不是“另一个 ORM”不是“MyBatis 的平替”不是“JPA 的竞争对手”。它是对“关系型数据库应该如何被访问”这个问题的终极回答。这个回答不依赖于某一门语言不依赖于某个特定版本不依赖于某家公司的商业策略。它只依赖于三个永恒的事实SQL 是集合论和关系代数的编程语言4GLJava 是图论和对象引用的编程语言3GL这两者之间没有完美映射但可以有一座足够薄的桥SimpleDAO 就是这座桥。它不假装自己能消除 3GL 和 4GL 之间的语义鸿沟那是 ORM 的幻觉它只是在这条鸿沟上架了一座足够薄的桥——让你在桥的这边用 Java 组织参数在桥的那边用 SQL 表达业务两边各司其职互不干扰。写在最后如果你也受够了MyBatis 的 XML 标签地狱JPA 的 N1 查询陷阱和 HQL 语法限制手写 RowMapper 的机械重复调试时手动替换 20 个?的痛苦数据权限、多租户等扩展需求不得不扒源码欢迎来试试Spring JDBC Ultra开源项目名SimpleDAO。它不是这个时代最流行的框架但它是这个时代最诚实的框架。因为它从不假装自己能做到做不到的事也从不阻拦开发者去做应该做的事。把时间留给业务而不是框架。相关开源地址核心框架源码https://gitee.com/gao_zhenzhong/simple-dao系统底座https://gitee.com/gao_zhenzhong/simple-dao-starter代码生成器https://gitee.com/gao_zhenzhong/simple-dao-coder实战案例https://gitee.com/gao_zhenzhong/simple-dao-demo附本文是Spring JDBC Ultra的落地实践篇。关于支撑这套框架的底层理论体系——SQL-First 范式我已单独写了一篇完整的理论文章从 3GLJava与 4GLSQL之间的代际差、关系代数的动态性梯度到 ORM 为何注定失败的数学原因做了系统性剖析。 SQL-First 范式持久层设计的终极思想附理论落地实战