你在 MyBatis 里学的东西,真的都是“收益”吗?

MyBatis 官网、文档、社区都在告诉你:它帮你解决了 SQL 和 Java 的分离问题、动态 SQL 问题、结果映射问题。它有一整套体系:Mapper 接口、XML 配置、OGNL 表达式、几十个标签、上百个属性、十几个注解……

你学会这些东西,就能在 XML 里拼字符串了。

但这些问题,Spring JDBC 解决不了吗?

答案是:Spring JDBC 本来就能做,而且不需要你额外学任何东西。

下面这张表,列出了你为了在 XML 里拼一个字符串,需要学习的所有“收益”。


一、为了“挂接”SQL,你学到的收益

MyBatis 概念它的作用这个“收益”是真实的吗?
Mapper 接口定义方法,XML 里的 id 要跟方法名一致Spring JDBC 里你直接写方法,不需要这个接口。
XML 文件存放 SQL 的地方Spring JDBC 里 SQL 就在 Java 里,不需要另一个文件。
Namespace区分不同 XML 文件里的同名 idSpring JDBC 没有这个问题,方法名天然唯一。
XML 文件头<!DOCTYPE...> 那一大坨没有 XML,就不需要这个头。
接口 ID ↔ XML id 映射手工维护两个文件的一致性Spring JDBC 里方法名就是方法名。
#{} vs ${}一个参数化查询,一个直接拼接Spring JDBC 里只有 ?,全参数化。
@Param 注解给方法参数起别名,供 XML 引用Spring JDBC 里参数名就是参数名。
@Mapper / @MapperScan让 Spring 识别这个接口Spring JDBC 里直接注入 JdbcTemplate。

这一组的真实收益:把本来在一个文件里能写完的东西,拆成两个文件,然后你必须同时维护它们的一致性。


二、为了“动态拼接 SQL”,你学到的收益

MyBatis 概念它的作用这个“收益”是真实的吗?
<if> 标签条件判断Java 里 if 就能做,不需要学 XML 标签。
<where> 标签自动处理第一个 AND这个逻辑你自己写,三行代码。
<set> 标签自动处理 UPDATE 末尾的逗号这个逻辑你自己写,三行代码。
<foreach> 标签遍历集合拼 IN 条件Java 里 for 循环就能做。
<choose> / <when> / <otherwise>多路分支Java 里 if-else 就能做。
<trim> 标签去掉前缀/后缀Java 里 replaceAll 或 substring 就能做。
<bind> 标签在 XML 里定义变量Java 里直接定义变量就行了。
OGNL 表达式test、item、index、collection你学 Java 已经学了表达式,现在还要再学一个 OGNL 的变种。

这一组的真实收益:把 Java 里的 if、for、else 替换成 XML 里的标签,换了个地方写同样的逻辑。


三、为了“控制 SQL 执行细节”,你学到的收益

MyBatis 概念它的作用这个“收益”是真实的吗?
fetchSize控制 JDBC 每次取多少行Spring JDBC 里也有,直接设置。
timeoutSQL 执行超时时间Spring JDBC 里也有,直接设置。
flushCache清空二级缓存Spring JDBC 没有二级缓存,不需要。
useCache是否使用二级缓存Spring JDBC 没有二级缓存,不需要。
statementTypeSTATEMENT / PREPARED / CALLABLESpring JDBC 的方法名已经区分了。
resultSetTypeFORWARD_ONLY / SCROLL_INSENSITIVE / SCROLL_SENSITIVESpring JDBC 里也有对应的设置。

这一组的真实收益:把本可以直接调用的 JDBC 参数配置,塞进 XML 的属性里。


四、为了“映射结果”,你学到的收益

MyBatis 概念它的作用这个“收益”是真实的吗?
resultType自动映射到普通 Java 类Spring JDBC 里 RowMapper 一行代码。
resultMap手动配置字段映射Spring JDBC 里 RowMapper 直接按字段名匹配。
<id> 标签(在 resultMap 中)标记主键字段Spring JDBC 不需要区分主键。
<association>一对一关联映射Spring JDBC 里自己处理结果集组装。
<collection>一对多关联映射Spring JDBC 里自己处理结果集组装。
@Results / @Result在接口方法上配置映射Spring JDBC 不需要。
@One一对一关联本质上是再发一次查询,N+1 的源头之一。
@Many一对多关联本质上是再发一次查询,N+1 的源头之一。
@MapKey指定 Map 结果的 key 字段Spring JDBC 里自己组装 Map。
@Options配置 useGeneratedKeys、fetchSize 等跟 XML 属性完全重叠,换了个地方写。
@ResultType指定返回类型方法签名已经决定了返回类型。

这一组的真实收益:把本来可以在 RowMapper 里直接处理的逻辑,拆成了 XML 配置或注解配置。


五、为了“配置 MyBatis 本身”,你学到的收益

MyBatis 概念它的作用这个“收益”是真实的吗?
mybatis-config.xml主配置文件Spring JDBC 没有单独的配置文件。
settings几十个配置项Spring JDBC 没有这些配置项。
typeAliases包扫描,省略 XML 里的全限定类名Spring JDBC 直接用类名,不需要别名。
plugins拦截器配置Spring JDBC 用 AOP,不需要学拦截器。
environments环境配置Spring Boot 的 application.yml 已经做了。
DatabaseIdProvider多数据库方言切换Spring JDBC 直接写 SQL,不需要单独配置。
<databaseId>为不同数据库写不同版本的 SQLSpring JDBC 里你用一个变量判断就行了。

这一组的真实收益:多了一份配置文件,多了一堆配置项,多了一整套扩展机制的学习成本。


六、为了“定位错误”,你学到的收益

MyBatis 概念它的作用这个“收益”是真实的吗?
BindingException参数绑定出问题了Spring JDBC 里没有这种异常,参数直接绑定。
ExecutorException执行器出问题了Spring JDBC 里没有这种异常。
TooManyResultsException预期一条但查出了多条Spring JDBC 里 queryForObject 会抛 IncorrectResultSizeDataAccessException,但异常信息更清晰。
ReflectionException反射出问题了Spring JDBC 里不会因为反射失败而抛出这种异常。
CacheException缓存出问题了Spring JDBC 没有缓存。
PersistenceException持久层通用异常Spring JDBC 直接抛 DataAccessException。
总计 31 类异常(MyBatis 官方源码统计)你需要学习每一种异常的出处和含义Spring JDBC 只有 3 类 核心异常。

这一组的真实收益:你花时间调试的不是“数据库出了什么问题”,而是“框架的哪一层又炸了”。


你学完这些“收益”,最终实现的是什么?

就是在 XML 里拼一个字符串。

  • Mapper 接口 + XML 文件 + namespace + XML 头 + 接口 ID 映射 + @Param → 为了把 SQL 放进 XML
  • 几十个标签 + OGNL 表达式 → 为了在 XML 里做动态条件判断
  • 上百个属性 → 为了控制 XML 里 SQL 的执行细节
  • 十几个注解 → 为了在接口上标注 XML 对应的配置
  • 几十个异常类 → 为了定位 XML 拼字符串时出的错
  • mybatis-config.xml + settings + plugins + typeAliases → 为了配好环境让 XML 能正常运行

所有这些概念,解决的是同一个问题:在 XML 里拼一个字符串。但你在 Java 里本来就能拼。


你用 Spring JDBC,需要的只是一个工具类

如果你把条件拼接、参数管理、分页逻辑封装成几个方法:

  • addWhere():拼接条件 + 自动收集参数
  • addIf():条件判断 + 自动决定是否拼接
  • page():分页 + 自动处理 COUNT 和 LIMIT

全工程复用,覆盖 90% 以上的业务场景。

你不再需要:

  • Mapper 接口 → 方法就在类里,不需要额外接口
  • XML 文件 → SQL 就是字符串
  • namespace → 类名和方法名天然唯一
  • XML 头 → 没有 XML 就没有头
  • #{} vs ${} → 只有 ?
  • resultType / resultMap → RowMapper 直接处理
  • 几十个标签 → Java 的 if 已经够用
  • 上百个属性 → 方法参数直接设置
  • 十几个注解 → 什么都不需要
  • 几十个异常 → 数据库错误就是 SQLException
  • 拦截器 → Spring AOP 就能扩展

三个方法,解决 90% 的问题。剩下的 10%,本来就要手写 SQL,不关工具的事。


这个工具类已经有了现成的

它叫 SimpleDAO。

它在 Spring JDBC 的基础上,做了这些事情:

你原本需要手写的事SimpleDAO 做了什么
条件拼接 + 参数管理add()、and()、in()、notIn() 一行搞定,参数自动收集,空值自动跳过
多条件组合 + 跨位置参数合并mergeParams(c1, c2, c3) 自动合并多个条件类的参数,顺序与 SQL 中 ? 一致,无需手动维护 Object[]
模糊查询转义add("AND u.name LIKE ?", name, 3),site 控制 % 位置(左/右/前后),自动转义 % 和 _,防 SQL 注入
IN 条件in("id", ids),数组自动展开成 (?,?,?),参数自动收集
无参数 SQL 片段 + 动态开关add("AND dr = 0")、add("AND status = 1", flag),带布尔参数控制是否拼接
单表 CRUD继承 BaseDao<T>,空类即得 save、update、delete、findById、list、page、count、exists 等 20+ 方法,零代码、零 SQL
差异更新update(T) 不更新 null 字段,updateNull(T) 更新 null 字段,按需选择
批量操作saveBatch(list)、replaceBatch(list),底层复用数据库驱动批处理能力
自动分页page(cond) 一行搞定,自动生成 COUNT SQL,自动计算 LIMIT/OFFSET,返回 Page<T> 含数据列表 + 总条数 + 总页数;极端复杂 SQL 用 page0() 兜底(子查询法)
主键自动生成@Id("snow") 雪花算法、@Id("uuid") UUID、@Id("auto") 数据库自增、@Id("custom") 自定义,插入时自动赋值
审计字段自动填充save() 自动填充 createTime / createBy / dr,update() 自动填充 updateTime / updateBy
逻辑删除delete() 自动判断表是否有 dr 字段,有则 UPDATE SET dr=1,无则 DELETE FROM
结果集映射查询结果自动映射到实体/VO,驼峰 ↔ 下划线自动转换,无需 XML 配置
联表查询SQL 完整手写,支持 JOIN、子查询、UNION、CTE、窗口函数、聚合统计——数据库能写啥,它就能跑啥,SQL 直接复制到客户端可执行
复杂报表原生 SQL + BaseCondition 条件拼接,mergeParams 多条件合并,报表开发效率提升 50% 以上
数据权限Spring AOP 切面 + cond.setExtendCondition(and) 一行注入权限条件,无侵入、零框架依赖
字段脱敏Spring AOP 切面在 Service 层处理,不污染持久层
SQL 日志调试自动打印带参数的完整 SQL,字符串/日期自动加引号,复制即可在数据库客户端执行,无需手动替换占位符
异常处理数据库原生异常直接透传,不封装、不转换、不自造 31 类框架异常
原生能力保留危机时刻可直接调用 JdbcTemplate 或 NamedParameterJdbcTemplate,框架不挡路
框架存在感< 5%,业务代码浓度 > 95%,没有 XML、没有 OGNL、没有 Mapper 接口、没有 SqlSession

SimpleDAO 没有替换 Spring JDBC,它只是把你本来要手写的样板代码自动化了。


开源地址

  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

写在最后

MyBatis 的“收益”,加引号是认真的。因为你学了一大堆东西,得到的只是在 XML 里拼一个字符串。

SimpleDAO 的收益,不加引号也是认真的。因为它什么都没让你多学,只是帮你省掉了重复代码。

去 Gitee 拉一个 Demo,跑起来,看看代码量差多少,然后自己算账。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐