为什么又写了一个数据库框架
这不是 MyBatis 教程,也不是 MP 替代品宣传。 这是一个关于"我为什么实在忍不了了"的故事。
一、那个凌晨三点
几年前某个凌晨,我在排查一个生产问题。
日志里疯狂滚动 SQL:
DEBUG - ==> Preparing: SELECT * FROM order WHERE user_id = ? AND status IN ( ? , ? , ? )
DEBUG - ==> Parameters: 12345(Long), 1(Integer), 2(Integer), 3(Integer)
看起来一切正常。但数据库 CPU 打到 100%,响应慢到接近雪崩。
我只有一个问题想知道:这条 SQL 是从哪一行代码执行的?
答案是:不知道。
我在 10 个微服务、400 多个 Mapper 接口里全局搜 user_id,搜出几十处结果,每一处都可能是凶手。我一个一个看调用链,猜测在哪个业务场景里触发,最后花了两个多小时才定位到——是一个新上线的"用户历史订单"页面,没加分页。
那一刻我就在想:这已经是 2020 年代了,框架能不能给我一条打印调用位置的日志?
二、是框架本身的问题吗?
冷静下来想,其实不是 MyBatis 做错了什么。
MyBatis 是一个优秀的持久层框架。它的问题是:它诞生于一个"SQL 应该写在 XML 里"的年代。
那个年代的假设是:
- SQL 和 Java 代码应该分离
- Mapper 接口是一层契约,Java 不关心 SQL 长什么样
- 运行时通过
MappedStatementID 找到 SQL,再执行
这个设计在 2010 年很合理。但 2020 年以后,我们的写法早就变了:
- 大家越来越多把 SQL 直接写在
@Select注解里 MyBatis-Plus的LambdaQueryWrapper让 SQL 在 Java 里动态拼装- IDE 可以直接跳转、重构、查找引用
"SQL 和代码分离"已经不再是事实,但框架的解耦层还在那里——它成了排查问题时的"迷雾带"。
一条 SQL 穿过:
业务代码
↓
Mapper 接口(代理)
↓
MapperMethod
↓
SqlSession
↓
Executor
↓
StatementHandler
↓
JDBC
这七层在顺利的时候你感知不到;出问题的时候它们是七层雾。
三、想要什么样的框架
那几年我一直在想:如果我自己写一个持久层框架,它应该长什么样?
我列了几条原则:
1. 一条 SQL 日志要告诉我三件事
- 完整的可执行 SQL(参数填好,直接复制能跑)
- 执行耗时
- 是哪一行 Java 代码触发的(能点击跳转)
第三点是关键。我不想再凌晨全局搜了。
2. 调用栈要浅
出了异常,栈深 15 层我是真的看不懂。理想状态是:3-5 层以内直达 JDBC。
3. 别强迫我建 6 个文件
一个简单的用户查询,为什么要 Entity + Mapper + XML + Service + ServiceImpl + Controller?让我 Controller 直接 DB.select(...) 行不行?
行,但要有一个前提:API 要足够克制,不能让这种写法变成屎山。这就要求:
- 没有隐式魔法(注解驱动的副作用)
- 一切显式(代码里能看见的,才是真正发生的)
- 规则少、特例少(出错概率低)
4. 不要给我造玩具
该有的企业能力不能少:
- 多数据源(而且要能运行时动态)
- 事务
- 逻辑删除
- 批量操作
- 预设 SQL 管理
但这些不能以加重框架复杂度为代价——Spring 早就做好了事务,我就用它的;Redis/Caffeine 做好了缓存,我就不造一个轮子。
四、于是有了 DLZ-DB
DLZ-DB 最开始只有三个文件,叫 DB.select()、DB.update()、DB.insert()。它是我在自己项目里用的"工具类",不是"框架"。
但用着用着我发现:
- 同事上手只需要 10 分钟
- 出 bug 从来不需要跳源码(异常栈干净)
- 凌晨再也没有"这条 SQL 从哪来"的恐惧
- 新业务接入从 1 天缩到 1 小时
于是它慢慢长出了:条件构造器、预设 SQL、动态数据源、逻辑删除、链式更新、JSONMap 深度取值……
但那个"凌晨三点找 SQL"的痛点一直是它的北极星。所有特性都要先回答一个问题:"如果你在凌晨三点用它,你会感谢它还是诅咒它?"
五、它不是为了取代谁
DLZ-DB 不想取代 MyBatis,也不想取代 MP。它想做的是:
在"我只是想查个数据"和"我要搭一个企业级持久层"之间,给你一个更短的路径。
如果你的项目已经深度绑定了 MP 或 JPA,继续用。 如果你是新项目、内部工具、微服务、SaaS 场景——给 DLZ-DB 15 分钟,你可能不会想回去。
至少凌晨三点排查问题的时候不会。
尾声
写这篇文章的时候我看了一下当年那条慢 SQL 的日志截图。如果当时是 DLZ-DB,那一行会长这样:
caller:(OrderHistoryController.java:78) list 2300ms sql:SELECT * FROM order WHERE user_id=12345 AND status IN (1,2,3)
78 行。OrderHistoryController。
这 5 秒就能定位的问题,我当年花了 2 小时。
这就是 DLZ-DB 存在的全部理由。