为什么复杂系统更偏向 MyBatis?背后的设计思路其实很简单

4 阅读6分钟

为什么复杂系统更偏向 MyBatis?背后的设计思路其实很简单

引言

在前两篇文章中,我分析了 MyBatis PlusJPA。这两个框架都试图通过抽象来提升开发效率,但都引入了新的复杂度。

那么 MyBatis 呢?它没有 MyBatis Plus 的 Wrapper,也没有 JPA 的 Criteria API,但为什么没有被淘汰呢?

本文将从设计哲学的角度,探讨 MyBatis 的优势。


一、设计哲学的差异

1.1 三种框架的设计理念

MyBatis Plus 的理念

减少样板代码,提升开发效率

实现方式:在 Service 层提供 Wrapper API

JPA 的理念

透明持久化,面向对象的数据访问

实现方式:通过 ORM 映射隐藏 SQL

MyBatis 的理念

SQL 是第一公民,开发者完全控制

实现方式:显式编写 SQL,清晰的结果映射

1.2 对比分析

维度MyBatis PlusJPAMyBatis
核心理念减少代码透明持久化控制 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 的核心优势

  1. 简单的概念模型:5 个核心概念
  2. 清晰的分层架构:职责分离,边界明确
  3. SQL 第一公民:不做不必要的抽象
  4. 显式优于隐式:可预测,易调试

6.2 设计哲学的启示

简单不是简陋,而是经过深思熟虑的克制。

MyBatis 克制地不做:

  • 不做对象关系映射(ORM)
  • 不做透明持久化
  • 不做自动 SQL 生成(基础 CRUD 除外)

这种克制带来了

  • 更低的认知负担
  • 更清晰的代码
  • 更好的长期可维护性

6.3 对技术选型的思考

选择框架时,要问:

  • 这个抽象解决了什么问题?
  • 这个抽象带来了什么代价?
  • 我的项目真的需要这个抽象吗?

不要为了抽象而抽象。

6.4 下一篇预告

前三篇我们分析了具体的框架,下一篇我们从更高的视角来看:

《ORM 本质问题:技术选型的哲学思考》

我会分析:

  • 为什么每个 ORM 框架都有缺陷?
  • ORM 的本质矛盾是什么?
  • 技术选型的方法论

如果这篇文章对你有帮助,欢迎点赞、收藏、关注

欢迎在评论区分享你对 MyBatis 的看法。