面向业务设计持久层框架 Spring Jdbc Ultra

📅 2026/7/3 11:42:33 👁️ 阅读次数 📝 编程学习
面向业务设计持久层框架 Spring Jdbc Ultra

持久层的设计哲学:从 MyBatis 的系统性缺陷到 Spring Jdbc Ultra 的范式革命

作者注:本文不是框架使用教程,是一次关于持久层设计范式的深度反思。基于十余年企业级开发实践,十余家企业、四五年的生产验证,以及一套名为 SimpleDAO(Spring Jdbc Ultra)的框架,我们来讨论一个被长期忽视的问题:持久层框架到底应该面向数据库设计,还是面向业务设计?


一、问题的提出:MyBatis 为什么流行?

MyBatis 是国内 Java 生态中最流行的持久层框架之一。但流行不等于正确。我们需要追问:它的流行是技术选择的结果,还是认知失调、历史包袱和惯性使然?

1.1 MyBatis 的"优势"说辞

互联网上为 MyBatis 辩护的常见论点:

  1. SQL 与代码分离,便于维护
  2. 动态 SQL 强大灵活
  3. 结果自动映射
  4. 缓存机制
  5. 插件扩展机制
  6. Mapper 接口,IDE 有代码提示
  7. XML 语法高亮
  8. 批量操作更优雅
  9. 存储过程调用支持更好
  10. 与 Spring 集成后事务管理更方便

1.2 逐一检验:站在 Spring JDBC 的参照系上

说辞检验结论
SQL 与代码分离伪优势— 分离的是文件,不是关注点。XML 里 1/3 表达式、1/3 标签、1/3 SQL,三种语言耦合
动态 SQLJava 更优— Java 字符串操作能力远强于 XML 标签
自动映射Spring JDBC 更优BeanPropertyRowMapper零配置,MyBatis 的resultMap是手工配置假装自动
缓存机制鸡肋— 用户第一件事就是关闭缓存
插件扩展Spring AOP 更优— 白盒扩展 vs 黑盒拦截器
Mapper 接口伪抽象— 无多实现场景,纯为框架代理机制服务
XML 语法高亮无关— Java Text Block 同样有 SQL 高亮
批量操作Spring JDBC 更直接
存储过程平手
Spring 事务平手— 两者都是 Spring 事务

结论:站在 Spring JDBC 的参照系上,MyBatis 没有任何站得住脚的优势。


二、MyBatis 的系统性缺陷

MyBatis 不是"有一点毛病",是全身都是毛病。这些毛病不是设计失误,是范式层面的错误

2.1 缺陷一:XML 拼字符串 — 用残缺的标签语言做 Java 原生就擅长的事

“你撕开 XML 那层皮,里面到底是什么?不还是一坨低级的字符串拼接吗?”

Java 里有replaceformat、正则、流式处理,能把字符串玩出花来。XML 有什么?XML 连个像样的循环和字符串截取都写得费劲。

动态 SQL 的灵魂在于"逻辑",不在于"标签"。为什么换个文件,用一堆又臭又长的标签去干 Java 一行代码搞定的事,就叫高级了?

SimpleDAO 的做法:

@Setter@GetterpublicclassOrderCondextendsBaseCondition{privateStringname;privateIntegerage;privateInteger[]inAges;privateBooleansubQuery;privateStringorderNo;privateString[]orderNos;privateByteorderStatus;@OverrideprotectedvoidaddCondition(){// 关联表:无语法糖,自己写别名add("AND o.order_status = ?",orderStatus);add("AND o.order_no LIKE ?",orderNo,3);add("AND o.order_no IN ",orderNos);// IN 条件(主表同样适用)add("AND t.age IN ",inAges);// 静态子查询add("AND t.id IN (SELECT user_id FROM bus_order WHERE dr=0)");// 动态子查询add("AND t.id IN (SELECT user_id FROM bus_order WHERE dr=0)",subQuery);}}

2.2 缺陷二:Mapper 接口 — 为了接口而接口

接口的价值在于多态和抽象。但 MyBatis 的 Mapper 接口:

  • 永远只对应一个 XML/注解实现
  • 方法签名和 SQL 是 1:1 硬绑定
  • 增加的是命名空间匹配、方法名同步、IDE 跳转断裂的成本

这不是"面向接口编程",是框架实现细节的外溢

SimpleDAO 的做法:直接继承BaseDao<T>,零代码,无伪抽象。

2.3 缺陷三:SQL 编写白盒,SQL 执行黑盒

MyBatis 把 SQL 编写设计成白盒(你能看到 XML),但 SQL 执行设计成黑盒(拦截器、代理链、缓存层)。

想加个数据权限?写拦截器。想加个脱敏?写拦截器。那拦截器是普通人能搞定的吗?80% 的程序员写 20 行代码,连业务的边都摸不着,全在跟框架内部对象搏斗。

SimpleDAO 的做法:全是白盒。AOP 直接拦截参数和返回值,10 分钟搞定。

2.4 缺陷四:补坑生态 — 受害者联盟

“传统框架生态繁荣?那是补坑生态!”

为什么 Spring JDBC 没人讨论?因为它没坑,白盒到底。传统框架呢?SQL 编写白盒,SQL 执行黑盒。它自己制造了坑,然后让开发者写插件来填坑。你在为框架的设计缺陷买单。

SimpleDAO 的做法:无坑,无需生态。

2.5 缺陷五:单表思维 — 背离 RDBMS 的本质

RDBMS,第一个单词就是Relation(关系)。企业级开发中,单表场景占比极低,越庞大的系统比例越小。MyBatis-Plus 所谓的"单表解决 80% 业务",是行业最大的谎言之一

真实的企业开发,核心是多表联动。MyBatis-Plus 的单表 Wrapper,到了联表场景完全失能,只能退回 XML。

SimpleDAO 的做法:联表是常态,单表是特例。BaseDao只是BaseSql的预配置,底层完全统一。

2.6 缺陷六:31 类自造异常 — 中间商制造错误

Spring JDBC 只有 2-3 类异常。MyBatis 有 31 类。你花时间学的不是"数据库出了什么问题",而是"框架的哪一层又炸了"。

SimpleDAO 的做法:基于 Spring JDBC,异常就是数据库真实的异常,没有中间商赚差价,更没有中间商制造错误。

2.7 缺陷七:七层执行链路 — 性能损耗

业界公认的性能排名:原生 JDBC 100%,Spring JDBC 99%,MyBatis 95%。那 5% 差在哪?就差在那多出来的四五层抽象、动态代理、反射调用上。

SimpleDAO 的做法:三层到底,直通数据库,性能 ≈ Spring JDBC。


三、SimpleDAO 的设计哲学

SimpleDAO 不是"另一个 ORM",是对 ORM 范式的根本否定。它的设计哲学可以概括为一句话:

面向业务设计,而非面向数据库设计。

3.1 核心抽象:SQL 结构的动态梯度

SimpleDAO 对 SQL 结构做了关键抽象:不同位置的动态程度不同

SQL 位置动态程度SimpleDAO 设计
WHERE高度动态BaseCondition— 核心抽象
分页 (LIMIT/OFFSET)高度动态page()/page0()— 自动处理
JOIN / ON中度动态BaseCondition嵌入任意位置
ORDER BY中度动态BaseCondition.orders
GROUP BY / HAVING中度动态BaseCondition嵌入 HAVING
SELECT 字段高度稳定手写 SQL,框架不干预
FROM 表名高度稳定手写 SQL,框架不干预

三个主类的分工完全对应这个梯度:

  • BaseCondition— 负责"高度动态"部分
  • BaseSql— 负责"中度动态"部分(执行、分页)
  • BaseDao— 负责"高度稳定"部分的自动化(单表 CRUD)

好的,这段建议插在"3.1 核心抽象:SQL 结构的动态梯度"之后,作为"3.2 单表对象化:零代码 CRUD":


3.2 单表对象化:零代码 CRUD

ORM 的理想是"单表对象化"——把数据库表映射成 Java 对象,CRUD 操作像操作对象一样自然。JPA/Hibernate 为此付出了沉重的代价(复杂的实体状态管理、HQL、缓存、N+1 问题),而 MyBatis 连这个理想都没真正实现——它的单表 CRUD 仍然需要写 Mapper 接口和 XML/注解。

SimpleDAO 实现了真正的单表对象化,而且代价趋近于零:

@RepositorypublicclassUserDaoextendsBaseDao<User>{// 空类,获得全部 CRUD 能力}

一个空类,零代码,获得:

  • save(User)— 自动填充审计字段、雪花主键、逻辑删除标记
  • update(User)— 非空字段更新,自动填充updateTime/updateBy
  • delete(id...)— 自动判断逻辑删除或物理删除
  • findById(id)— 按主键查询
  • list(cond)— 按条件查询列表
  • page(cond)— 按条件分页查询

这不是"代码生成器生成的模板代码",是框架运行时自动推断

  • @Table("sys_user")→ 表名
  • @Id→ 主键名和类型
  • 字段名(驼峰)→ 自动转下划线
  • createTime/createBy/updateTime/updateBy→ 自动审计
  • dr→ 自动逻辑删除

单表是联表的特例,不是独立的世界

BaseDao的底层就是BaseSql

public<CextendsBaseCondition>Page<T>page(booleanshow,finalCc){Stringsql=Sql.builder().select().fields(fields).from().table(table).as().sql();returnpage(show,sql,c,clazz);}

自动生成单表 SQL,然后调用BaseSql.page()。单表和联表共享同一套执行机制,没有"单表用一套 API、联表退回另一套"的认知割裂。

零代码不是魔法,是元数据缓存

启动时反射解析一次实体类的@Table@Id、字段列表,缓存到BaseDao实例中。运行时零反射,性能无损。

这是"单表对象化"的正确实现方式:编译期零侵入(只有注解),运行期零反射(启动时缓存),开发期零代码(空类继承)


3.3 四象限正交分解:关系代数的必然

任何查询结果都是二维表(行集合 × 列集合),必定属于四象限之一:

单列多列
单行field()— 聚合函数row()— 报表行
多行columns()— ID 列表list()— 实体列表

4 × 2 × 2 = 16 个方法,但只需要记4 个概念(行 × 列),日志开关和参数形式是上下文自然选择。

这是数学意义上的完备性——笛卡尔积覆盖所有组合,无遗漏、无冗余。


3.4 条件类:极简的动态条件构造

SimpleDAO 屏蔽了占比 90%+ 的第一层if

传统框架(MyBatis XML、JPA Criteria、MyBatis-Plus Wrapper)中,动态条件的核心痛点不是"拼字符串",是无处不在的if判断

// MyBatis XML<iftest="name != null and name != ''">ANDnameLIKECONCAT('%',#{name},'%')</if>// JPA Criteriaif(name!=null&&!name.isEmpty()){predicates.add(cb.like(root.get("name"),"%"+name+"%"));}// MyBatis-Plus Wrapperif(StringUtils.isNotBlank(name)){wrapper.like("name",name);}

每一个条件字段,都需要显式写一层if。10 个条件字段,就是 10 层if。这是模板代码的瘟疫

SimpleDAO 的add()方法内部做了空值判断:

protectedfinalvoidadd(finalStringsql,finalObjectvalue){if(Objects.nonNull(value)&&StringUtils.hasText(value.toString())){condition.append(BLANK).append(sql);paramList.add(value);}}

开发者只需要写:

add("AND t.name LIKE ?",name,3);// 空值自动跳过add("AND t.age >= ?",ageMin);// 空值自动跳过add("AND t.status = ?",status);// 空值自动跳过

不需要写if

这个设计的收益是系统性的:

  • 代码量减少 60%— 10 个条件从 30 行 XML/Wrapper 变成 10 行add()
  • 认知负担归零— 不需要判断"这个字段要不要加if",框架已经处理了
  • Null 安全— 不会因为忘记判空而导致 SQL 语法错误
  • 意图清晰— 每行代码只说"这个条件是什么",不说"这个条件要不要加"

这 90%+ 的if不是"被优化掉了",是被框架吸收了。开发者只关注那 10% 真正需要业务逻辑控制的动态条件,其余全部交给框架的默认行为。

极简不是"功能少",是"需要决策的点少"。

3.5 条件类:不区分单表/联表,位置自由

这是 SimpleDAO 的独特设计,业界没有先例:

// 同一个条件类,既能用于单表,也能用于联表// 既能用在 WHERE 后,也能用在 ON 后、HAVING 后、甚至子查询里// WHERE 后"SELECT ... FROM a WHERE 1=1"+cond.where()// ON 后"SELECT ... FROM a JOIN b ON a.id = b.a_id "+cond.and()// 子查询里"SELECT ... FROM a WHERE id IN (SELECT id FROM b WHERE "+cond.where()+")"// HAVING 后"SELECT ... GROUP BY ... HAVING 1=1 "+cond.and()

3.6 真动态条件:运行时构造,不限于 WHERE

MyBatis 的"动态 SQL"是静态模板 + 条件分支选择。SimpleDAO 的addDynamic()运行时完全动态生成 SQL 片段

// 数据权限 AOP 切面@Before("@annotation(auth)")publicvoidbeforeQuery(JoinPointpoint,DataAuthauth){BaseConditioncond=(BaseCondition)point.getArgs()[0];StringuserId=request.getHeader(Const.USER_ID);cond.addDynamic(" AND "+auth.userField()+" IN",newObject[]{0,userId});}

字段名和值都是运行时确定的,这是 MyBatis XML 模板做不到的真动态

3.7 API 业务语义化:意图即代码

方法业务语义
list()查列表
page()分页查
row()查单行
field()查单个值
count()查数量
exists()查存在性

不是"技术操作",是业务动词。参数和返回值都是业务对象,不是框架对象。

3.8 上下文精准命名:克制即优雅

不追求长方法名自包含,而是类名 + 方法名 + 泛型 + 返回值组合出完整语义

// UserDao.list(UserCond) → "查用户列表"// OrderDao.page(SQL, OrderCond, OrderVO.class) → "查订单分页"// ReportDao.field(SQL, ReportCond, Integer.class) → "查报表数值"

命名克制,字符数少,语义同样精确。

3.9 性能白盒:优化不触发 API 变化

大宽表只取必要字段:

// ❌ 不要:SELECT *userDao.list(cond);// ✅ 应该:只取需要的字段Stringsql="SELECT id, name FROM user";baseSql.list(sql,cond,UserMiniVo.class);

同一个list方法,SQL 字符串决定性能边界。不需要换 API,不需要学新方法。


四、SimpleDAO 与 Spring JDBC 的关系

4.1 “Spring Jdbc Ultra” 的定位

SimpleDAO 不是"又一个框架",是Spring JDBC 的原生功能延伸与增强

  • 连接池?Spring 的
  • 事务?Spring 的@Transactional
  • 参数化查询?JdbcTemplate/NamedParameterJdbcTemplate
  • 结果映射?BeanPropertyRowMapper
  • 批量操作?NamedParameterJdbcTemplate.batchUpdate
  • AOP 扩展?Spring 的标准切面

SimpleDAO 自己做的只有三件事:

  1. 启动时反射解析元数据(@Table@Id、字段映射)并缓存
  2. 条件拼接(BaseCondition的字符串操作)
  3. SQL 组装(BaseDao的单表 SQL 生成)

4.2 全是收益,零成本

站在 Spring JDBC 的基准线上,SimpleDAO 的每一个增强都是纯收益:

Spring JDBC 原生SimpleDAO 增强收益
手写INSERT/UPDATE/DELETEsave(t)/update(t)/delete(id)零代码单表 CRUD
手动处理null字段update()/updateNull()区分语义明确
手写WHERE条件BaseCondition.add()类型安全,参数化
手写COUNT+LIMITpage()一行代码,智能 COUNT
手动设置审计字段自动填充不侵入业务
手动处理逻辑删除delete()自动判断统一策略

4.3 能力上限 = SQL 的上限 = Spring 的上限

  • SQL 能力无上限:窗口函数、CTE、递归查询、存储过程……只要数据库支持,SimpleDAO 就能执行
  • Spring 生态扩展无上限:事务、多数据源、缓存、AOP、监控……100% 原生
  • 性能 ≈ Spring JDBC:启动时反射一次并缓存,运行时零反射,联表完全不读元数据

五、与 MyBatis 的终极对比

维度MyBatisSimpleDAO
设计哲学面向数据库面向业务
SQL 控制权半遮半掩(XML 白盒,执行黑盒)完全白盒
动态 SQLXML 标签 + OGNLJava 原生if
联表查询标签地狱,SQL 切碎完整 SQL 直写
结果映射resultMap,写两遍BeanPropertyRowMapper,零配置
扩展机制拦截器,高门槛AOP,零门槛
学习成本数周到数月(含踩坑)2 小时(8 个案例)
代码量基准1/3 ~ 1/4
性能95%99%
异常体系31 类自造异常仅数据库真实异常
执行链路七层三层
生态本质受害者联盟,补坑生态无坑,无需生态

六、结语:把时间留给生活,而不是框架

SimpleDAO 不是为了成为又一个流行的框架,而是为了证明一件事:

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

如果你:

  • 厌倦了复杂框架的折磨
  • 想要高效完成工作
  • 想要早点回家陪家人
  • 相信简单比复杂更有力量

那么,SimpleDAO 为你而存在。

SimpleDAO: SQL-First,白盒透明,能力无上限。


本文基于 SimpleDAO 1.2.1 版本及 8 个企业实战案例撰写。框架已在生产环境稳定运行 3 年+,支撑日均百万级请求,服务十余家企业客户。


相关开源地址

  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