📚 8 集实战教程(从入门到精通)

集数 · 标题 本集目录 时长
01 · 单表 CRUD + 审计 + 逻辑删除 实体注解 · 空 DAO · 保存审计 · ID查询 · 分页 · 逻辑删除 约 6 min
02 · 联表查询 + 分页 联表 SQL · VO定义 · 条件类 · page调用 · 高性能COUNT 约 4 min
03 · 条件进阶:IN + 子查询 IN自动展开 · 子查询拼接 · add vs and · 三种动态边界 约 6 min
04 · 多表联查 + 复杂条件 行锁 · updateNull · 重复性校验 · 三表联查 · 时间范围 约 6 min
05 · 报表聚合:GROUP BY + 聚合函数 三表JOIN+聚合 · 条件类复用 · 独立判空 · 日志控制到方法 约 6 min
06 · mergeParams 多组条件合并 多条件类定义 · SQL多位置嵌入 · mergeParams合并 · 条件复用 约 5 min
07 · 多租户 + 数据权限 · AOP 破局 Filter + AOP链路 · extendCondition钩子 · 最小路径演示 约 7 min
08 · 脱敏 + 审计扩展 · 框架不设限 字段脱敏(VO getter)· 审计重写 · 逻辑删除调整 约 7 min

写在前面:一个“异类”的诞生

我写持久层框架开源之后经常收到一个问题:

“你这框架跟 MyBatis 有什么区别?跟 JPA 有什么区别?”

我的回答是:它们都在做“映射”,我在做“连接”。 它们面向数据库设计,我面向业务设计。

这个区别,看似很小,实则是一条分水岭。

我从 8 个 Demo 案例、2 组生产案例、16 个痛点场景一路验证下来,逐步构建起一套完整的理论体系——SQL-First 范式。今天这篇文章,就是把这套范式和它的实现框架 Spring JDBC Ultra(开源项目名:SimpleDAO),完整地介绍给你。

一、先看现实:三大主流框架,各有各的“死穴”

在 Spring JDBC Ultra 出现之前,Java 持久层生态由三大主流把持。我们心平气和地看看它们各自的优劣势:

1. Hibernate / JPA

维度 评价
✅ 单表 CRUD 极强,savefindById 非常方便
✅ 对象关系映射 @OneToMany@ManyToOne,适合简单主子表
❌ 复杂 SQL JPQL 能力有限,标量子查询、派生表、窗口函数几乎不可用
❌ 性能不可控 N+1 问题、懒加载陷阱、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% 白盒可控
❌ 单表对象化 无,saveupdatedelete 全部手写 SQL
❌ 条件拼接 手写 WHERE 拼字符串,? 占位符顺序要人工对齐
❌ 分页 手写 LIMIT / ROWNUM / OFFSET FETCH,换数据库要重写
❌ 审计字段 手写 createTimeupdateTime 赋值
❌ 日志 需要自己配 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 的“零代码”实现

@Repository
public class UserDao extends BaseDao<User> {
    // 空类,继承即获得所有 CRUD 能力
}

一个空类,你就拥有了:

  • save(T) / saveBatch(List<T>)
  • update(T) / updateNull(T)
  • delete(id...) / delete(Cond)
  • findById(id) / findOne(Cond)
  • list(Cond) / page(Cond) / count(Cond) / exists(Cond)

注解驱动

@Data
@Table("sys_user")
public class User {
    @Id  // 默认雪花算法,也支持 AUTO / UUID / CUSTOM
    private Long id;
    private String name;
    private Integer age;
    private LocalDateTime createTime;  // save 时自动填充
    private Long createBy;             // save 时自动填充
    private LocalDateTime updateTime;  // update 时自动填充
    private Long updateBy;             // update 时自动填充
    private Byte dr;                   // 逻辑删除字段
}

就这么简单。没有 XML,没有 @PrePersist,没有拦截器配置。

2. BaseSql —— 联表查询的“无限自由”

单表用 BaseDao,联表用 BaseSql。API 完全一致:

@Repository
public class OrderDao extends BaseDao<Order> {
    private static final String JOIN_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
        """;

    public Page<OrderVO> pageJoin(OrderCond cond) {
        return page(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_EXTRACTGROUP_CONCAT 等)

框架不解析 SQL,所以以上全部支持。你能写出来的 SQL,框架就能映射出来。

3. BaseCondition —— 条件拼接的“语义单元”

MyBatis 的动态 SQL 靠 XML 标签,Spring JDBC Ultra 靠 Java 代码:

@Getter
@Setter
public class UserCond extends BaseCondition {
    private String name;
    private Integer ageMin;
    private Integer ageMax;
    private Byte status;
    private Object[] ids;

    @Override
    protected void addCondition() {
        and("name LIKE", name, 3);        // 3=前后模糊
        and("age >=", ageMin);
        and("age <=", ageMax);
        and("status =", status);
        in("id", ids);
        // 关联表条件直接用 add
        add("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 继承 BaseDao<T>,空类获得全部能力
改一个字段改 5 处代码 只改实体类

痛点 2:审计字段手工填充

传统方案 Spring JDBC Ultra
createTime/createBy 每次手动 set save 时自动注入
updateTime/updateBy 每次手动 set update 时自动注入
MyBatis 要写拦截器,JPA 要写 @PrePersist 零配置,实体类定义字段即可
// 你只需要在实体类里定义这些字段
private LocalDateTime createTime;
private Long createBy;
private LocalDateTime updateTime;
private Long updateBy;
// 剩下的框架自动完成

痛点 3:逻辑删除标准化缺失

传统方案 Spring JDBC Ultra
有的表用 del_flag,有的用 is_deleted 统一配置 simple-dao.logic-delete.field: dr
手写 update t set dr = 1 delete(id) 自动变成逻辑删除

痛点 4:SQL 日志信息黑洞

传统方案 Spring JDBC Ultra
MyBatis 打印 WHERE name = ?,参数另起一行 打印完整 SQL:WHERE name = '张三'
调试要手动替换 20 个 ? 复制日志直接贴到 Navicat 执行
日志要么全开要么全关 方法级控制:list(true, cond) 打印,list(false, cond) 不打印

这是我最得意的功能之一——Sql.fill(sql, params) 把占位符全部替换成真实值,日志即调试工具。

痛点 5:动态条件拼接的“标签地狱”

传统方案 Spring JDBC Ultra
MyBatis XML 里 <if> 嵌套 <foreach>,200 行起步 Java 代码里直接 if + and(),一行一个条件
改条件要改 XML,容易漏闭合标签 IDE 重构、高亮、跳转全支持

痛点 6:联表查询的对象映射灾难

传统方案 Spring JDBC Ultra
JPA 的 @OneToMany 导致 N+1 直接写 SQL,list(SQL, cond, VO.class)
MyBatis 的 resultMap 写 100 行 XML 列名和 VO 字段名匹配即可,零配置
12 表联查基本不可维护 12 表联查,SQL 文本块直接写

痛点 7:标量子查询 / 半连接 / 派生表

传统方案 Spring JDBC Ultra
JPQL 完全不支持 SQL 文本块直接写
JPA 只能退回 nativeQuery = true 框架不做任何限制
MyBatis 能写但 SQL 被 XML 切碎 完整 SQL 保留在 Java 里
String sql = """
    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 Ultra
JPA 用 FUNCTION('JSON_EXTRACT', ...) 直接写 JSON_EXTRACT
MyBatis 用 ${} 有注入风险 直接写,参数部分依然走 ? 占位符

痛点 9:分页方言差异

传统方案 Spring JDBC Ultra
MySQL 用 LIMIT,Oracle 用 ROWNUM,SQL Server 用 OFFSET FETCH 4 个 Dialect 类自动适配
手写分页,换数据库重写 SQL 自动检测数据库类型,零配置

痛点 10:数据权限 / 多租户

传统方案 Spring JDBC Ultra
MyBatis 拦截器解析 SQL,风险高 AOP + addDynamic,注入条件片段
JPA @Filter 黑盒操作 列名由运行时决定,值走预编译
@Aspect
@Component
public class DataAuthAspect {
    @Around("@annotation(dataAuth)")
    public Object injectAuth(ProceedingJoinPoint pjp, DataAuth dataAuth) {
        BaseCondition cond = (BaseCondition) pjp.getArgs()[0];
        String userId = getCurrentUserId();
        // 真正的动态条件:列名由运行时决定
        cond.addDynamic(" AND " + dataAuth.userField() + " = ?", userId);
        return pjp.proceed();
    }
}

痛点 11:多条件类参数合并

传统方案 Spring JDBC Ultra
多个子查询不同条件,要揉进一个 DTO 每个子查询用独立的 Cond 类
XML 里用 <if> 判断来源,极易混乱 mergeParams(cond1, cond2, cond3) 自动合并
String sql = """
    SELECT ...
    FROM (子查询1 WHERE条件A) a
    LEFT JOIN (子查询2 WHERE条件B) b
    """;
return list(sql, VO.class, mergeParams(condA, condB));

痛点 12:结果集映射的重复劳动

传统方案 Spring JDBC Ultra
RowMapper 手写 rs.getString("name") BeanPropertyRowMapper 自动映射
换字段就要改 RowMapper 列名和 VO 字段名匹配即可
下划线转驼峰要手动处理 自动转换:user_nameuserName

痛点 13:批量操作的性能优化

传统方案 Spring JDBC Ultra
逐条插入 1000 条数据 saveBatch(list) 生成真正的批量 INSERT
JPA 的 saveAll 是逐条 insert MySQL 支持 replaceBatch 批量 Upsert

痛点 14:分布式主键生成

传统方案 Spring JDBC Ultra
数据库自增 ID 分库分表不可用 @Id("snow") 雪花算法
UUID 太长影响索引性能 worker-iddata-center-id 支持集群配置
雪花算法要自己实现 一行注解解决

痛点 15:SQL 注入

传统方案 Spring JDBC Ultra
MyBatis 的 ${} 是 SQL 注入高发区 所有值传递强制走 ? 占位符
JPA 的 nativeQuery 同样存在拼接风险 SqlSecurityChecker 检查动态 SQL 片段

痛点 16:跨语言复刻

传统方案 Spring JDBC Ultra
Hibernate 只在 Java 生态 已复刻到 8 种语言(Java、C#、Python、Go、Rust、PHP、Node.js、C++)
换语言要重学一套 ORM 范式跟着 SQL 走,跨语言知识复用

五、三方对比:SimpleDAO 如何“集大成”

把三者的优劣势和 SimpleDAO 的定位放在一起对比,一目了然:

能力维度 Hibernate/JPA MyBatis Spring JDBC SimpleDAO
单表 CRUD 自动化 ✅ 强 ❌ 弱 ❌ 无
联表 SQL 自由度 ❌ 受限 ✅ 全自由 ✅ 全自由 全自由
SQL 组织方式 HQL/JPQL XML 标签 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 的设计哲学,可以用三个“不封装”来概括:

  1. 不封装 SQL 的内容:你不写 HQL、不写 JPQL、不写 XML 标签,你直接写 SQL。SQL 是 4GL(第四代语言),是数据库的母语,不需要被“翻译”成任何中间语言。

  2. 不封装 SQL 的能力:标量子查询、半连接、派生表、窗口函数、CTE、数据库专有函数——你随便写。框架不做任何“能力阉割”。

  3. 不封装 SQL 的结果ResultSet 映射到 VO 是唯一的封装,且是“平铺映射”。复杂对象嵌套是业务表达的范畴,在 Service 层用 Java 集合做内存组装,绝不把树形结构强塞给 SQL。

七、核心结论:面向业务设计,而非面向数据库设计

所有已知的持久层框架,都是面向数据库设计的。Spring JDBC Ultra 是唯一一个面向业务设计的。

  • Hibernate/JPA:先定义 @Entity@OneToMany,再让业务逻辑适配这个模型。
  • MyBatis:先写 Mapper 接口 + XML,再让 SQL 适配 XML 的标签语法。
  • Spring JDBC Ultra:业务需要什么数据形状,你就写什么 SQL;SQL 怎么写,框架就怎么帮你传参和映射。

这就是 SQL-First 范式的核心:不是“少写 SQL”,而是让 SQL 回归它本来的位置——作为业务表达的直接载体。 框架不定义业务规则,业务规则由开发者的 SQL 和 Java 代码定义。

八、为什么说它是“元模型”?

SimpleDAO 不是“另一个 ORM”,不是“MyBatis 的平替”,不是“JPA 的竞争对手”。

它是对“关系型数据库应该如何被访问”这个问题的终极回答。

这个回答不依赖于某一门语言,不依赖于某个特定版本,不依赖于某家公司的商业策略。它只依赖于三个永恒的事实:

  1. SQL 是集合论和关系代数的编程语言(4GL)
  2. Java 是图论和对象引用的编程语言(3GL)
  3. 这两者之间没有完美映射,但可以有一座足够薄的桥

SimpleDAO 就是这座桥。

它不假装自己能消除 3GL 和 4GL 之间的语义鸿沟(那是 ORM 的幻觉),它只是在这条鸿沟上架了一座足够薄的桥——让你在桥的这边用 Java 组织参数,在桥的那边用 SQL 表达业务,两边各司其职,互不干扰。

写在最后

如果你也受够了:

  • MyBatis 的 XML 标签地狱
  • JPA 的 N+1 查询陷阱和 HQL 语法限制
  • 手写 RowMapper 的机械重复
  • 调试时手动替换 20 个 ? 的痛苦
  • 数据权限、多租户等扩展需求不得不扒源码

欢迎来试试 Spring JDBC Ultra(开源项目名:SimpleDAO)。

它不是这个时代最流行的框架,但它是这个时代最诚实的框架。因为它从不假装自己能做到做不到的事,也从不阻拦开发者去做应该做的事。

把时间留给业务,而不是框架。

相关开源地址

  1. 核心框架源码:https://gitee.com/gao_zhenzhong/simple-dao
  2. 系统底座:https://gitee.com/gao_zhenzhong/simple-dao-starter
  3. 代码生成器:https://gitee.com/gao_zhenzhong/simple-dao-coder
  4. 实战案例:https://gitee.com/gao_zhenzhong/simple-dao-demo

:本文是 Spring JDBC Ultra 的落地实践篇。关于支撑这套框架的底层理论体系——SQL-First 范式,我已单独写了一篇完整的理论文章,从 3GL(Java)与 4GL(SQL)之间的代际差、关系代数的动态性梯度,到 ORM 为何注定失败的数学原因,做了系统性剖析。

👉 SQL-First 范式:持久层设计的终极思想(附理论+落地实战)

Logo

一站式 AI 云服务平台

更多推荐