为什么复杂系统更偏向 MyBatis?背后的设计思路其实很简单
引言
在前两篇文章中,我分析了 MyBatis Plus和 JPA。这两个框架都试图通过抽象来提升开发效率,但都引入了新的复杂度。
那么 MyBatis 呢?它没有 MyBatis Plus 的 Wrapper,也没有 JPA 的 Criteria API,但为什么没有被淘汰呢?
本文将从设计哲学的角度,探讨 MyBatis 的优势。
一、设计哲学的差异
1.1 三种框架的设计理念
MyBatis Plus 的理念:
减少样板代码,提升开发效率
实现方式:在 Service 层提供 Wrapper API
JPA 的理念:
透明持久化,面向对象的数据访问
实现方式:通过 ORM 映射隐藏 SQL
MyBatis 的理念:
SQL 是第一公民,开发者完全控制
实现方式:显式编写 SQL,清晰的结果映射
1.2 对比分析
| 维度 | MyBatis Plus | JPA | MyBatis |
|---|---|---|---|
| 核心理念 | 减少代码 | 透明持久化 | 控制 SQL |
| SQL 生成 | 运行时动态生成 | 自动生成 | 手写 |
| 抽象程度 | 中等 | 高 | 低 |
| 学习曲线 | 低 | 高 | 中 |
| 可控性 | 中等 | 低 | 高 |
1.3 设计权衡
MyBatis Plus / JPA:
- 牺牲可控性 → 换取便利性
- 引入抽象 → 隐藏复杂度
- 自动生成 → 减少代码量
MyBatis:
- 保留可控性 → 牺牲便利性
- 不做抽象 → 直面复杂度
- 手写 SQL → 增加代码量
关键问题:哪种权衡更合理?
二、简单性的价值
2.1 概念复杂度对比
MyBatis Plus 需要理解的概念:
- LambdaQueryWrapper
- LambdaUpdateWrapper
- Wrapper 的各种方法(eq、ne、gt、ge、lt、le、like...)
- 条件构造器
- 链式调用
- 运行时 SQL 生成
JPA 需要理解的概念:
- Entity 生命周期
- 持久化上下文
- 懒加载 vs 急加载
- 脏检查机制
- flush 时机
- N+1 问题
- Criteria API
- Specification
- EntityGraph
- Native SQL 映射
MyBatis 需要理解的概念:
- SQL
- 参数映射
- 结果映射
- 动态 SQL 标签
明显的差异:
- MyBatis Plus:10+ 个概念
- JPA:15+ 个概念
- MyBatis:5 个核心概念
2.2 认知负担
从认知心理学的角度:
工作记忆容量有限(7±2 个单位)
当框架的概念模型超过这个限制时:
- 学习曲线陡峭
- 容易出错
- 调试困难
- 需要频繁查文档
MyBatis 的优势:
- 概念少,容易掌握
- 接近 SQL,认知负担低
- 不需要理解复杂的生命周期
2.3 显式 vs 隐式
显式(Explicit)的优势:
// MyBatis:显式
public void updateOrder(Long orderId, String status) {
orderDao.updateStatus(orderId, status); // 清晰:调用了更新方法
}
隐式(Implicit)的问题:
// JPA:隐式
@Transactional
public void updateOrder(Long orderId, String status) {
Order order = orderRepository.findById(orderId).get();
order.setStatus(status); // 隐式:什么时候更新?执行了什么 SQL?
}
Python 之禅:
Explicit is better than implicit.
(显式优于隐式)
MyBatis 遵循了这一原则。
三、架构的长期价值
3.1 分层架构的坚守
MyBatis 强制执行清晰的分层:
┌─────────────────────────────────────────┐
│ Service 层 │
│ ├─ 纯业务逻辑 │
│ └─ orderDao.findList(query) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ DAO 层 │
│ ├─ 数据访问接口 │
│ └─ List<Order> findList(OrderQuery) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Mapper.xml │
│ ├─ SQL 语句 │
│ └─ <select id="findList">...</select> │
└─────────────────────────────────────────┘
为什么这很重要?
因为:
- 职责清晰 → 易于理解
- 边界明确 → 易于测试
- 关注点分离 → 易于修改
3.2 对比 MyBatis Plus
MyBatis Plus 打破了这个边界:
┌─────────────────────────────────────────┐
│ Service 层 │
│ ├─ 业务逻辑 │
│ ├─ Wrapper 构建 ◄── 持久层逻辑! │
│ └─ orderMapper.selectList(wrapper) │
└─────────────────────────────────────────┘
后果:
- 第 1 个月:开发很快
- 第 6 个月:代码混乱
- 第 12 个月:想重构但成本高
3.3 技术债务曲线
从时间维度看:
技术债务
│
高 │ MyBatis Plus ↗
│ ↗
│ ↗
│ ↗
│ JPA ↗
│ ↗ ↗
中 │ ↗ ↗
│ ↗
│ ↗
低 │━━━━━━━━━━━━━━━ MyBatis
│
└──────────────────────────────────▶ 时间
0 3m 6m 12m 24m
MyBatis:
- 前期:稍慢(要写 XML)
- 后期:平稳(架构清晰)
MyBatis Plus / JPA:
- 前期:快(少写代码)
- 后期:债务累积(架构混乱)
四、SQL 的不可替代性
4.1 SQL 是声明式语言
SQL 的本质是声明式的:
SELECT u.name, COUNT(o.id) as order_count
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 1
GROUP BY u.id
HAVING COUNT(o.id) > 10
ORDER BY order_count DESC
这段 SQL 表达了:
- 查询什么数据(SELECT)
- 从哪里查(FROM、JOIN)
- 什么条件(WHERE)
- 如何分组(GROUP BY)
- 分组后的过滤(HAVING)
- 如何排序(ORDER BY)
清晰、简洁、表达力强。
4.2 用代码表达相同的逻辑
MyBatis Plus:
wrapper.eq(User::getStatus, 1)
.groupBy(User::getId)
.having("COUNT(orders.id) > 10")
.orderByDesc("order_count");
// 还需要处理 JOIN 和聚合,代码会更复杂
JPA:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
Root<User> user = cq.from(User.class);
Join<User, Order> orders = user.join("orders", JoinType.LEFT);
cq.multiselect(
user.get("name"),
cb.count(orders.get("id")).alias("orderCount")
);
cq.where(cb.equal(user.get("status"), 1));
cq.groupBy(user.get("id"));
cq.having(cb.gt(cb.count(orders.get("id")), 10));
cq.orderBy(cb.desc(cb.count(orders.get("id"))));
对比:
- SQL:8 行,清晰直观
- Wrapper:10+ 行,抽象但复杂
- Criteria:15+ 行,啰嗦难懂
结论:SQL 本身就是最好的 DSL。
4.3 抽象的代价
尝试用代码抽象 SQL 的问题:
- 丢失了 SQL 的表达力
- 增加了认知负担
- 引入了新的复杂度
- 仍然需要理解 SQL 才能用好
既然如此,为什么不直接用 SQL?
这就是 MyBatis 的设计哲学。
五、适度抽象原则
5.1 YAGNI 原则
You Aren't Gonna Need It
(你不会需要它)
很多抽象是为了"可能的需求":
- 数据库无关性(但 90% 的项目不会切换数据库)
- 透明持久化(但开发者还是要理解 SQL)
- 动态 SQL 生成(但复杂查询还是要手写)
MyBatis 的做法:
- 不做不必要的抽象
- 需要什么功能,手写什么 SQL
- 简单、直接、有效
5.2 Worse is Better
简单的设计比完美的设计更好。
Unix 哲学:
- 做一件事,并做好
- 组合简单的工具
MyBatis 的设计:
- 只做 SQL 映射
- 不做对象关系映射
- 不做透明持久化
- 把 SQL 控制权交给开发者
结果:
- 概念简单
- 容易理解
- 足够灵活
六、总结与反思
6.1 MyBatis 的核心优势
- 简单的概念模型:5 个核心概念
- 清晰的分层架构:职责分离,边界明确
- SQL 第一公民:不做不必要的抽象
- 显式优于隐式:可预测,易调试
6.2 设计哲学的启示
简单不是简陋,而是经过深思熟虑的克制。
MyBatis 克制地不做:
- 不做对象关系映射(ORM)
- 不做透明持久化
- 不做自动 SQL 生成(基础 CRUD 除外)
这种克制带来了:
- 更低的认知负担
- 更清晰的代码
- 更好的长期可维护性
6.3 对技术选型的思考
选择框架时,要问:
- 这个抽象解决了什么问题?
- 这个抽象带来了什么代价?
- 我的项目真的需要这个抽象吗?
不要为了抽象而抽象。
6.4 下一篇预告
前三篇我们分析了具体的框架,下一篇我们从更高的视角来看:
《ORM 本质问题:技术选型的哲学思考》
我会分析:
- 为什么每个 ORM 框架都有缺陷?
- ORM 的本质矛盾是什么?
- 技术选型的方法论
如果这篇文章对你有帮助,欢迎点赞、收藏、关注!
欢迎在评论区分享你对 MyBatis 的看法。