一、MyBatis整体架构与设计模式续篇
在之前的文章中,我们探讨了MyBatis中的构建者模式、工厂模式和代理模式。本文将继续深入源码,剖析MyBatis里另外三种至关重要的设计模式:装饰器模式、模板方法模式和策略模式。正是这些模式的应用,赋予了MyBatis强大的功能扩展能力、性能优化潜力以及架构上的灵活性。
1.1 MyBatis整体架构回顾
MyBatis采用了经典的分层架构设计,从用户接口到数据源,主要包括以下几个层次:
- 应用层:用户应用程序
- 接口层:SqlSession、Mapper接口
- 核心处理层:SQL解析、执行、结果映射
- 基础支撑层:缓存、事务、类型处理、日志等
- 数据层:数据源、连接池、JDBC驱动
本文将重点聚焦于装饰器、模板方法和策略模式在上述层次中的具体实现与协同。

1.2 本文涉及的设计模式
- 装饰器模式(Decorator Pattern):以
CachingExecutor和Cache装饰器链为代表,动态增强对象功能。
- 模板方法模式(Template Method Pattern):体现在
BaseExecutor和BaseTypeHandler中,定义算法的稳定骨架。
- 策略模式(Strategy Pattern):如
RoutingStatementHandler和不同的Executor类型,封装可互换的算法族。
二、装饰器模式(Decorator Pattern)
装饰器模式是一种结构型设计模式,其核心在于不改变原有对象结构的前提下,动态地为其添加额外的职责或行为。它通过将对象包裹在装饰器类中来实现功能的“即插即用”。
2.1 CachingExecutor
CachingExecutor是MyBatis中运用装饰器模式的典范,它为一个普通的Executor披上了“二级缓存”的外衣。
2.1.1 类定义核心
public class CachingExecutor implements Executor {
// 关键:持有一个被装饰的Executor实例
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
// 防止重复装饰
if (delegate instanceof CachingExecutor) {
throw new IllegalArgumentException(“Cannot wrap a CachingExecutor“);
}
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
// 如果该MappedStatement配置了缓存
if (cache != null) {
flushCacheIfRequired(ms); // 按需清空缓存
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 先尝试从缓存中获取
@SuppressWarnings(“unchecked“)
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 缓存未命中,委托给真正的Executor执行查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 将结果放入缓存
tcm.putObject(cache, key, list);
}
return list;
}
}
// 未配置缓存,直接委托执行
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 更新操作需要清空相关缓存
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
}

2.1.2 装饰器模式的优势
- 非侵入式扩展:无需修改
SimpleExecutor等原有类,通过包装即可新增缓存功能,符合开闭原则。
- 动态组合:可以在运行时根据配置决定是否使用
CachingExecutor,功能组合非常灵活。
- 职责单一:
CachingExecutor只关心缓存逻辑,delegate只关心基础SQL执行,各司其职。
- 嵌套增强:理论上可以多层装饰,实现功能的叠加(尽管MyBatis中
CachingExecutor通常只装饰一层)。
2.2 Cache装饰器链
MyBatis的整个缓存系统都是装饰器模式的杰作。Cache接口拥有众多装饰器实现,它们可以像“俄罗斯套娃”一样层层嵌套,构建出功能强大的缓存链。
2.2.1 Cache接口
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
ReadWriteLock getReadWriteLock();
}
2.2.2 丰富的Cache装饰器实现
MyBatis提供了一系列Cache装饰器,每个都负责一项特定的功能:
PerpetualCache:基础实现,内部就是一个HashMap。
LruCache:装饰后具备LRU(最近最少使用)淘汰策略。
FifoCache:装饰后具备FIFO(先进先出)淘汰策略。
BlockingCache:防止缓存击穿的阻塞装饰器。
LoggingCache:用于统计缓存命中率的日志装饰器。
ScheduledCache:定时清理过期货的装饰器。
SerializedCache:将值序列化后存储的装饰器,保证线程安全或跨会话。
SynchronizedCache:提供同步控制的装饰器。
TransactionalCache:支持事务的缓存装饰器,事务提交后才真正写入缓存。
2.2.3 Cache装饰器链的构建者
CacheBuilder类负责将这些装饰器组装成链:
public class CacheBuilder {
private String id;
private Class<? extends Cache> implementation;
private List<Class<? extends Cache>> decorators;
public Cache build() {
setDefaultImplementations();
// 1. 创建基础Cache(如PerpetualCache)
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// 2. 按顺序应用装饰器
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
return cache; // 返回装饰完成的最终Cache对象
}
}

2.3 装饰器模式的应用场景与对比
在MyBatis中,装饰器模式主要应用于功能增强场景,例如为Executor加缓存、为Cache加各种策略和特性。
与继承相比,装饰器模式的优势明显:
| 对比项 |
装饰器模式 |
继承 |
| 扩展方式 |
动态组合,灵活 |
静态编译,固定 |
| 灵活性 |
高,可运行时增减功能 |
低,编译时确定 |
| 类数量 |
功能类线性增加,组合更多样 |
容易导致类爆炸(多种功能组合需多继承) |
| 设计原则 |
更好遵循“组合优于继承”原则 |
容易破坏封装性 |
三、模板方法模式(Template Method Pattern)
模板方法模式是一种行为型设计模式。它在父类中定义了一个操作算法的骨架,而将一些步骤延迟到子类中实现。这使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。
3.1 BaseExecutor
BaseExecutor是模板方法模式在MyBatis中的核心体现。它定义了SQL执行的标准流程,并将具体的数据库交互细节留给子类完成。
3.1.1 BaseExecutor核心代码(模板方法)
public abstract class BaseExecutor implements Executor {
// 模板方法:定义了查询的固定算法骨架
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// ... 错误检查、清空本地缓存等前置操作 ...
List<E> list;
try {
queryStack++;
// 步骤1: 检查一级缓存(本地缓存)
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 命中缓存,处理输出参数
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 步骤2: 缓存未命中,调用抽象方法 `doQuery` 查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
// ... 延迟加载等后置操作 ...
return list;
}
// 关键抽象方法:由子类实现具体的数据库查询逻辑
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException;
// 抽象方法:由子类实现具体的更新逻辑
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
}
3.1.2 BaseExecutor的子类实现(具体步骤)
不同的子类通过实现 doQuery 和 doUpdate 来提供不同的执行策略,这正是模板方法模式的威力所在。
SimpleExecutor:最简单的实现,每次执行都会创建新的Statement对象,用完后立即关闭。
ReuseExecutor:重用执行器。它会缓存预处理过的Statement对象,当执行相同的SQL时直接复用,减少了创建和解析的开销。
BatchExecutor:批量执行器。它将多个更新操作缓存起来,最后通过executeBatch()一次性提交,极大提升了批量操作的性能。

3.2 BaseTypeHandler
BaseTypeHandler 是另一个应用模板方法模式的典型例子,它定义了Java类型与JDBC类型之间转换的标准流程。
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
// 模板方法:设置非空参数的标准流程(可能包含空值判断等)
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
setParameter(ps, i, parameter, jdbcType); // 调用具体的设置方法
}
// 模板方法:获取可能为空结果的标准流程(处理wasNull)
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
T result = getNullableResult(rs, columnName); // 调用具体的获取方法
return rs.wasNull() ? null : result; // 统一的空值处理
}
// 抽象方法:由子类(如StringTypeHandler, IntegerTypeHandler)实现具体类型转换
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
}
3.3 模板方法模式的优势
- 代码复用:将公共的算法逻辑提升到父类,避免了子类中的代码重复。
- 框架稳定:保证了算法的主流程不可变,提高了系统的稳定性和可预测性。
- 扩展便捷:子类只需要关注自己需要变化的步骤,易于扩展新的行为。
- 便于维护:算法结构的修改只需要在父类中进行,符合“开放-封闭”原则。
四、策略模式(Strategy Pattern)
策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
4.1 RoutingStatementHandler
RoutingStatementHandler 是一个策略上下文(Context),它本身不处理具体逻辑,而是根据MappedStatement的配置,将请求路由到不同的具体策略(StatementHandler)中去。
4.1.1 RoutingStatementHandler源码
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate; // 持有一个具体的策略
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根据StatementType选择具体的策略
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED: // 最常见的情况
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException(“Unknown statement type: “ + ms.getStatementType());
}
}
// 所有方法都委托给具体的策略对象执行
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
// ... 其他委托方法
}
4.2 ExecutorType策略
在创建SqlSession时,我们可以指定ExecutorType,这同样是策略模式的应用。Configuration 的 newExecutor 方法充当了策略工厂和上下文。
public class Configuration {
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
// 根据类型选择策略
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction); // 默认策略
}
// 装饰器模式再次介入:如果启用缓存,用CachingExecutor装饰
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 代理模式介入:应用所有插件
return (Executor) interceptorChain.pluginAll(executor);
}
}
4.2.2 三种Executor策略对比
| Executor类型 |
核心特点 |
适用场景 |
性能特点 |
| SimpleExecutor |
每次执行都创建新Statement,执行完毕立即关闭。 |
通用场景,单条操作。 |
中等,有重复创建开销。 |
| ReuseExecutor |
在会话内复用相同SQL的Statement对象。 |
短时间内需要反复执行相同SQL的场景。 |
较好,减少了创建和预编译开销。 |
| BatchExecutor |
缓存多个更新语句,最后统一执行executeBatch()。 |
大批量插入、更新、删除操作。 |
最佳(批量场景),极大减少网络IO。 |

4.3 策略模式的优势与对比
策略模式的优势:
- 算法独立:策略类之间彼此独立,新增或修改算法不影响客户端。
- 消除条件判断:避免了大量的
if-else或switch语句,代码更清晰。
- 便于测试:每个策略都可以单独进行单元测试。
| 策略模式 vs 模板方法模式: |
对比项 |
策略模式 |
模板方法模式 |
| 关系 |
组合关系(Has-a) |
继承关系(Is-a) |
| 重点 |
封装可互换的完整算法 |
定义算法的骨架,部分步骤可变 |
| 灵活性 |
更高,运行时动态替换策略 |
稍低,编译时确定子类关系 |
五、设计模式的综合应用
在MyBatis的实际运行中,这些设计模式并非孤立存在,而是精妙地协同工作,共同构建了其稳定而灵活的内核。
5.1 一个SQL的执行旅程
以一个简单的查询为例,其执行过程融合了多种模式:
- 策略模式:根据
ExecutorType选择具体的Executor(如SimpleExecutor)。
- 装饰器模式:如果配置了二级缓存,
CachingExecutor会装饰选中的Executor。
- 代理模式:通过
Plugin机制(动态代理),为Executor挂载拦截器链。
- 模板方法模式:执行查询时,进入
BaseExecutor定义的模板流程(先查一级缓存)。
- 策略模式:在
BaseExecutor内部,调用子类实现的doQuery方法(具体策略)。
- 策略模式:
doQuery中创建StatementHandler时,由RoutingStatementHandler根据SQL类型路由到PreparedStatementHandler等具体策略。
5.2 Cache的构建旅程
// 这个构建过程是装饰器模式与工厂模式的结合
Cache cache = new CacheBuilder(“myCache”)
.implementation(PerpetualCache.class) // 基础组件
.addDecorator(LruCache.class) // 装饰:LRU淘汰
.addDecorator(LoggingCache.class) // 装饰:命中率日志
.addDecorator(SynchronizedCache.class)// 装饰:线程安全
.build(); // 工厂方法返回装饰链的最终产物

六、总结与最佳实践
6.1 关键要点回顾
- 装饰器模式:用于功能增强。
CachingExecutor和Cache装饰器链是典型代表,实现了功能的动态、透明叠加。
- 模板方法模式:用于定义算法骨架。
BaseExecutor和BaseTypeHandler通过固定主流程、开放具体步骤,实现了代码的高度复用和架构稳定。
- 策略模式:用于封装算法族。
RoutingStatementHandler和ExecutorType让不同的SQL执行策略和语句处理策略可以灵活替换。
6.2 如何选择设计模式?
- 需要动态、透明地添加功能 → 优先考虑装饰器模式。
- 算法整体流程固定,但部分步骤需变化 → 使用模板方法模式。
- 有多种算法实现,且需要在运行时切换 → 采用策略模式。
- 面对复杂对象的构建 → 使用建造者模式(MyBatis配置解析)。
- 需要控制对象访问,增强功能 → 使用代理模式(MyBatis插件)。
6.3 避免过度设计
设计模式是解决特定问题的优秀方案,但切忌“为模式而模式”。应始终以代码的简洁性、可读性和实际需求为首要目标。在初期,可以优先实现功能;在重构或需求明确扩展时,再引入合适的设计模式进行优化。
通过对MyBatis源码中这些经典设计模式的剖析,我们不仅能更好地理解和使用MyBatis,更能深刻体会如何将这些模式应用于自己的程序开发中,设计出高内聚、低耦合、易扩展的优雅代码。希望本文能为你深入理解MyBatis及设计模式带来帮助。如果你想了解更多关于框架源码或系统设计的深度内容,欢迎在云栈社区交流探讨。