1. MyBatis设计模式全景概览
作为Java生态中最受欢迎的ORM框架之一,MyBatis在架构设计中大量运用了经典设计模式。这些模式并非随意堆砌,而是针对持久层操作的特有问题提出的系统性解决方案。我们先来看一个典型场景:当执行SqlSession.selectList()时,背后经历了参数处理、SQL解析、执行计划生成、结果映射等十余个步骤,而设计模式正是将这些复杂逻辑优雅组织的关键。
在MyBatis核心源码中,以下9种模式构成了框架的骨架:
- 工厂方法模式:SqlSessionFactory的创建过程
- 建造者模式:XMLConfigBuilder构建配置对象
- 代理模式:Mapper接口的动态代理实现
- 模板方法模式:BaseExecutor定义SQL执行流程
- 装饰器模式:缓存模块的多层包装
- 策略模式:参数处理器与类型处理器
- 组合模式:动态SQL节点的树形结构
- 迭代器模式:结果集的游标访问
- 责任链模式:插件拦截器的链式调用
这些模式相互配合,使得MyBatis既能保持核心流程的稳定性,又能灵活应对各种扩展需求。比如当我们需要添加二级缓存时,装饰器模式允许在不修改原有代码的情况下增加新功能;当需要支持新的数据库类型时,策略模式使得只需新增一个实现类即可。
提示:阅读MyBatis源码时,建议先掌握这些设计模式的基本概念,再结合具体场景理解其应用方式。模式不是孤立存在的,往往在同一个类中会看到多种模式的组合使用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 创建型模式在MyBatis中的应用
2.1 工厂方法模式:SqlSession的诞生记
在org.apache.ibatis.session包中,SqlSessionFactory接口定义了创建SqlSession的工厂方法。DefaultSqlSessionFactory作为默认实现,其openSession()方法展示了典型的工厂方法模式:
java复制public class DefaultSqlSessionFactory implements SqlSessionFactory {
@Override
public SqlSession openSession() {
return openSessionFromDataSource(
configuration.getDefaultExecutorType(),
null,
false);
}
private SqlSession openSessionFromDataSource(...) {
// 创建Transaction
// 创建Executor
return new DefaultSqlSession(configuration, executor, autoCommit);
}
}
这种设计将对象创建的逻辑封装在工厂中,客户端无需关心具体的SqlSession实现类。在实际开发中,我们通过SqlSessionFactoryBuilder构建工厂实例,这又涉及到建造者模式。
2.2 建造者模式:复杂配置的逐步构建
MyBatis的配置文件往往包含数十个配置项,XMLConfigBuilder通过建造者模式将这些配置解析为Configuration对象。其核心在于将对象的构建过程分解为多个步骤:
java复制public Configuration parse() {
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
propertiesElement(root.evalNode("properties"));
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
// 其他配置项解析...
}
这种模式的优势在于:
- 构建过程可以灵活扩展,新增配置项只需添加对应方法
- 可以严格控制构建顺序,比如必须先解析typeHandler再解析mapper
- 最终生成的Configuration对象是不可变的,保证了线程安全
在自定义插件开发时,我们经常需要操作Configuration对象。理解其构建过程有助于正确获取各种配置信息。
3. 结构型模式解析
3.1 代理模式:Mapper接口的魔法
MyBatis最令人称道的特性之一就是只需定义接口而不需要实现类即可完成数据库操作。这背后的秘密在于org.apache.ibatis.binding包中的动态代理实现:
java复制public class MapperProxy<T> implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
return mapperMethod.execute(sqlSession, args);
}
}
代理模式在这里解决了两个关键问题:
- 解耦:将接口定义与具体实现分离
- 灵活性:可以在代理过程中插入各种增强逻辑(如缓存、日志)
实际开发中常见的坑是:
- 在事务方法内调用同类中另一个Mapper方法可能导致事务失效
- 代理对象没有实现类,不能直接实例化
3.2 装饰器模式:缓存的多层包装
MyBatis的缓存模块是装饰器模式的经典案例。以org.apache.ibatis.cache包为例,基础缓存实现是PerpetualCache,而其他缓存实现如LruCache、ScheduledCache都是装饰器:
java复制public class LruCache implements Cache {
private final Cache delegate;
public LruCache(Cache delegate) {
this.delegate = delegate;
// 初始化LRU相关结构
}
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
// 维护LRU队列
}
}
这种设计带来的好处是:
- 可以动态添加缓存特性而不影响原有功能
- 各种缓存策略可以自由组合(如LRU+定时刷新)
- 符合开闭原则,新增缓存策略无需修改现有代码
在配置二级缓存时,可以通过<cache>标签的eviction、flushInterval等属性控制装饰器的组合方式。
4. 行为型模式实战
4.1 模板方法模式:SQL执行的标准流程
BaseExecutor定义了SQL执行的模板方法,将整个过程分解为prepare、query、update等步骤,而具体实现留给子类:
java复制public abstract class BaseExecutor implements Executor {
public <E> List<E> query(...) {
Statement stmt = null;
try {
Configuration configuration = context.getConfiguration();
stmt = prepareStatement(handler, ...);
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
protected abstract Statement prepareStatement(...);
}
这种模式确保了:
- 所有Executor实现遵循相同的执行流程
- 子类可以重写特定步骤(如BatchExecutor的批量处理)
- 避免了重复代码
在自定义Executor时,需要特别注意模板方法中定义的流程控制点,比如错误处理和资源清理。
4.2 责任链模式:插件拦截器机制
MyBatis的插件系统基于责任链模式实现,InterceptorChain维护了所有拦截器的有序列表:
java复制public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
}
开发插件时需要实现Interceptor接口,并通过@Intercepts注解指定要拦截的方法。一个典型的分页插件实现会这样组织代码:
java复制@Intercepts(@Signature(
type= Executor.class,
method="query",
args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
// 解析分页参数
// 修改SQL
return invocation.proceed();
}
}
这种设计的精妙之处在于:
- 插件可以任意组合,互不影响
- 拦截点精确到具体方法
- 无需修改框架代码即可扩展功能
5. 设计模式组合应用案例
5.1 动态SQL的生成机制
MyBatis的动态SQL功能是多种模式协同工作的典范。以<if>标签的解析为例:
java复制public class IfSqlNode implements SqlNode {
private ExpressionEvaluator evaluator;
private String test;
private SqlNode contents;
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
这里运用了:
- 组合模式:所有SqlNode实现统一的apply接口,形成树形结构
- 策略模式:不同的SqlNode实现不同的SQL生成策略
- 解释器模式:对OGNL表达式的解析
在编写复杂动态SQL时,理解这些模式有助于:
- 避免过度嵌套导致的性能问题
- 合理使用
<choose>、<foreach>等标签 - 自定义SQL节点(需实现SqlNode接口)
5.2 类型处理系统的设计
TypeHandler体系完美展示了策略模式的应用。以枚举类型处理为例:
java复制public class EnumTypeHandler<E extends Enum<E>> implements TypeHandler<E> {
private Class<E> type;
@Override
public void setParameter(...) {
if (parameter == null) {
ps.setNull(i, JdbcType.VARCHAR.TYPE_CODE);
} else {
ps.setString(i, parameter.name());
}
}
}
系统内置了数十种类型处理器,运行时根据Java类型和JDBC类型自动选择最合适的处理器。这种设计使得:
- 新增类型支持只需添加处理器实现
- 不同类型可以有不同的处理策略
- 可以通过
@MappedTypes和@MappedJdbcTypes注解扩展系统
6. 设计模式在实战中的注意事项
6.1 性能与模式的权衡
虽然设计模式能提高代码质量,但在MyBatis这种基础框架中,性能考量同样重要。例如:
- 代理模式会引入方法调用的开销
- 装饰器模式可能导致多层嵌套调用
- 责任链模式的拦截器数量影响执行效率
在实际使用中,我们应当:
- 避免过度设计,只在必要时引入模式
- 对性能敏感路径(如Executor)进行针对性优化
- 合理使用缓存减少重复计算
6.2 扩展框架的正确姿势
基于MyBatis的设计模式特点,安全扩展框架的建议是:
- 优先使用插件机制:通过拦截器修改行为而非继承
- 利用配置注入:替换默认实现时保持接口兼容
- 遵循最小侵入原则:尽量不修改框架原生代码
一个典型的扩展案例是开发多租户插件:
java复制public class TenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
BoundSql boundSql = ((MappedStatement)invocation.getArgs()[0]).getBoundSql();
String newSql = addTenantCondition(boundSql.getSql());
resetSql(invocation, newSql);
return invocation.proceed();
}
}
这种实现方式既实现了功能需求,又不会破坏框架原有结构。
