📌 PDF :大白话说Java面试题 --- 05_Mybatis篇
第4题:MyBatis 源码中用了哪些设计模式?为什么要用这些设计模式?
📚 回答:
- 核心考点 : MyBatis 源码中设计模式的应用不是"背出几个模式名字"就能过关的。大厂面试中,面试官期望你深入理解 每个设计模式在 MyBatis 中的具体落地 (不是泛泛而谈,而是能说出类名、方法名、调用链路)、多种设计模式的协同工作 (如 Executor 创建过程中装饰器 + 策略 + 模板方法 + 代理的叠加),以及 设计模式选择的工程权衡(为什么这里用装饰器而非继承、为什么插件用责任链而非 AOP)。面试官真正想判断的是:你是否具备从优秀开源框架中学习架构设计的能力。
1. 构建者模式(Builder Pattern):复杂对象的渐进式构建
- 1.1 应用场景:SqlSessionFactory 的创建
SqlSessionFactory的创建涉及 XML 解析、配置初始化、Mapper 注册等复杂步骤,MyBatis 使用构建者模式将过程拆解:
java
// 典型使用:三步构建
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
- 1.2 源码解析:分阶段构建 构建者模式的核心是将复杂对象的构建过程与其表示分离:
java
public class SqlSessionFactoryBuilder {
// 阶段1:从 InputStream 解析 XML
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder 是具体构建器:解析配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 阶段2:生成 Configuration(产品的一部分)
Configuration config = parser.parse();
// 阶段3:用 Configuration 构建最终产品
return build(config);
} finally {
inputStream.close();
}
}
// 阶段3:从 Configuration 构建 SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
| 角色 | 类 | 职责 |
|---|---|---|
| Director(导演) | SqlSessionFactoryBuilder.build() |
控制构建步骤的顺序 |
| Builder(抽象构建器) | BaseBuilder |
定义构建接口 |
| ConcreteBuilder(具体构建器) | XMLConfigBuilder / XMLMapperBuilder |
解析 XML,填充 Configuration |
| Product(产品) | SqlSessionFactory / Configuration |
最终构建的复杂对象 |
- 1.3 为什么用构建者模式? | 不用构建者模式 | 使用构建者模式 | | ------------ | ------------ | | 构造函数参数爆炸(10+ 个参数) | 分阶段设置,参数清晰 | | 构建顺序混乱,易出错 | 导演控制顺序,保证一致性 | | 无法支持多种配置源 | 支持 XML、注解、Java Config 多种构建方式 | | 构建逻辑与产品耦合 | 构建逻辑独立,产品类更纯粹 |
2. 工厂模式(Factory Pattern):对象的统一创建
- 2.1 应用场景:SqlSession 和 Executor 的创建 MyBatis 使用工厂模式统一创建核心对象,隐藏实现细节:
java
// SqlSessionFactory 是工厂接口
public interface SqlSessionFactory {
SqlSession openSession(); // 创建 SqlSession
SqlSession openSession(boolean autoCommit); // 带参数的创建
SqlSession openSession(Connection connection);
}
// DefaultSqlSessionFactory 是具体工厂
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
@Override
public SqlSession openSession() {
return openSessionFromDataSource(
configuration.getDefaultExecutorType(), // 获取默认执行器类型
null,
false
);
}
private SqlSession openSessionFromDataSource(ExecutorType execType,
TransactionIsolationLevel level,
boolean autoCommit) {
// 1. 从 Environment 获取数据源
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = environment.getTransactionFactory();
// 2. 创建事务
final Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 3. 创建 Executor(工厂模式再次应用)
final Executor executor = configuration.newExecutor(tx, execType);
// 4. 创建 SqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
}
}
- 2.2 Executor 的工厂创建
Configuration作为工厂,根据ExecutorType创建不同的 Executor:
java
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);
}
// 代理模式:插件拦截
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
| Executor 类型 | 特点 | 适用场景 |
|---|---|---|
SimpleExecutor |
每次执行新建 Statement | 通用场景 |
ReuseExecutor |
复用 Statement(Map 缓存) | 同 SQL 多次执行 |
BatchExecutor |
批量缓存,统一提交 | 批量插入/更新 |
CachingExecutor |
装饰器,添加二级缓存 | 读多写少 |
3. 动态代理模式(Dynamic Proxy Pattern):Mapper 接口的实现生成
- 3.1 应用场景:Mapper 接口无需实现类 开发者只定义接口,MyBatis 通过 JDK 动态代理自动生成实现:
java
// 使用:像调用普通方法一样执行 SQL
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectById(1);
-
3.2 源码解析:MapperProxy 的拦截链路 详细链路见第2题,核心类关系:
MapperRegistry(注册中心)
│
▼
MapperProxyFactory(代理工厂)
│
▼
MapperProxy implements InvocationHandler(方法拦截)
│
▼
MapperMethod(SQL 执行分发)
│
▼
SqlSession → Executor → JDBC -
3.3 为什么用动态代理而非直接生成类? | 方案 | 优点 | 缺点 | | ---- | ---- | ---- | | 动态代理 | 无侵入,接口即契约 | 只能代理接口方法 | | 直接生成类(CGLIB) | 可代理类的方法 | 需引入第三方库,字节码操作复杂 | | 静态代码生成 | 运行时无代理开销 | 编译期生成,灵活性差 |
MyBatis 选择 JDK 动态代理的原因:Mapper 本身就是接口,JDK 代理天然适合;无额外依赖;代理范围精准(只代理接口方法,Object 方法过滤)。
4. 装饰器模式(Decorator Pattern):功能的动态叠加
-
4.1 应用场景:Executor 和 Cache 的功能增强 装饰器模式是 MyBatis 中最核心的设计模式之一,用于在不修改原有类的情况下动态添加功能。
-
4.2 Executor 的装饰器:CachingExecutor
CachingExecutor为Executor添加二级缓存功能:
java
public class CachingExecutor implements Executor {
private final Executor delegate; // 被装饰的 Executor
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 1. 先查二级缓存
Cache cache = ms.getCache();
if (cache != null) {
List<E> list = (List<E>) cache.getObject(cacheKey);
if (list != null) return list; // 缓存命中
}
// 2. 缓存未命中,委托给被装饰的 Executor
return delegate.query(ms, parameter, rowBounds, resultHandler);
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
// 更新操作先清空缓存
flushCacheIfRequired(ms);
// 委托给被装饰的 Executor
return delegate.update(ms, parameter);
}
}
- 4.3 Cache 的装饰器链 二级缓存通过多层装饰器实现功能叠加:
java
// CacheBuilder 构建装饰器链
public Cache build() {
// 1. 基础缓存
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
// 2. 设置属性
setCacheProperties(cache);
// 3. 装饰器层层包装
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
// 标准装饰器链
cache = setStandardDecorators(cache);
}
return cache;
}
private Cache setStandardDecorators(Cache cache) {
try {
// 装饰器1:序列化(对象隔离)
cache = new SerializedCache(cache);
// 装饰器2:日志(命中率统计)
cache = new LoggingCache(cache);
// 装饰器3:同步(线程安全)
cache = new SynchronizedCache(cache);
// 装饰器4:阻塞(防缓存击穿)
if (blocking) {
cache = new BlockingCache(cache);
}
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
return cache;
}
| 装饰器 | 功能 | 被装饰对象 |
|---|---|---|
SerializedCache |
序列化隔离 | Cache |
LoggingCache |
命中率统计 | Cache |
SynchronizedCache |
线程安全 | Cache |
BlockingCache |
防缓存击穿 | Cache |
CachingExecutor |
二级缓存 | Executor |
- 4.4 为什么用装饰器而非继承? | 继承 | 装饰器 | | ---- | ------ | | 静态绑定,编译期确定功能 | 动态组合,运行期叠加功能 | | 功能叠加导致类爆炸(N个功能需2^N个子类) | 功能独立,任意组合 | | 修改父类影响所有子类 | 被装饰类无感知 | | 不符合开闭原则 | 符合开闭原则(扩展开放,修改关闭) |
5. 模板方法模式(Template Method Pattern):流程标准化,细节定制化
- 5.1 应用场景:BaseExecutor 定义查询/更新流程
BaseExecutor定义了 SQL 执行的标准流程,子类只实现具体步骤:
java
public abstract class BaseExecutor implements Executor {
// 模板方法:定义查询的标准流程
@Override
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 1. 获取 BoundSql(标准步骤)
BoundSql boundSql = ms.getBoundSql(parameter);
// 2. 创建 CacheKey(标准步骤)
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 3. 执行查询(调用模板方法)
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 4. 检查一级缓存(标准步骤)
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 5. 查一级缓存(标准步骤)
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 6. 缓存未命中,调用抽象方法(子类实现)
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;
// 模板方法中的具体步骤
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER); // 占位符防循环
try {
// 调用子类实现的抽象方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
return list;
}
}
- 5.2 模板方法的标准流程
1. 获取 BoundSql(标准) 2. 创建 CacheKey(标准) 3. 检查一级缓存(标准) 4. 缓存命中 → 返回结果(标准) 5. 缓存未命中 → 调用 doQuery()(子类实现) 6. 结果放入缓存(标准)
| 子类 | 实现的 doQuery 特点 | | ---- | ------------------- | | SimpleExecutor | 每次新建 Statement | | ReuseExecutor | 复用 Statement | | BatchExecutor | 批量缓存 Statement |
- 5.3 BaseTypeHandler 也是模板方法 类型处理器使用模板方法定义处理流程:
java
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
// 模板方法:定义设置参数的标准流程
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
// 标准步骤:处理 null
setNull(ps, i, jdbcType);
} else {
// 钩子方法:子类实现非 null 的处理
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
// 抽象方法:子类必须实现
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}
6. 策略模式(Strategy Pattern):算法的可插拔替换
- 6.1 应用场景:Executor 类型选择和 StatementHandler 路由 策略模式定义一族算法,让它们可以互相替换:
java
// ExecutorType 枚举定义策略
public enum ExecutorType {
SIMPLE, // 简单执行策略
REUSE, // 复用 Statement 策略
BATCH // 批量执行策略
}
// 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);
}
// ...
}
- 6.2 RoutingStatementHandler:策略路由 根据 SQL 类型路由到不同的
StatementHandler:
java
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
// 根据 SQL 类型选择策略
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);
}
}
| 策略 | 适用场景 | | ---- | -------- | | SimpleStatementHandler | 静态 SQL,无参数 | | PreparedStatementHandler | 预编译 SQL,有参数(最常用) | | CallableStatementHandler | 存储过程调用 |
7. 责任链模式(Chain of Responsibility Pattern):插件的链式拦截
- 7.1 应用场景:InterceptorChain 插件机制 MyBatis 插件通过责任链模式实现多层拦截:
java
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
// 添加拦截器
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
// 责任链:对所有目标对象应用所有拦截器
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target); // 层层代理
}
return target;
}
}
- 7.2 插件的代理叠加 每个插件通过
Plugin.wrap()生成代理对象,多个插件形成代理链:
java
// 插件1:分页插件
@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) throws Throwable {
// 1. 处理分页逻辑
// 2. 调用下一个拦截器(或目标方法)
return invocation.proceed();
}
}
// 插件2:SQL 性能监控
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {...})
})
public class PerformanceInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed(); // 调用下一个
long cost = System.currentTimeMillis() - start;
System.out.println("SQL cost: " + cost + "ms");
return result;
}
}
-
7.3 代理链的执行顺序
调用者 → 插件1代理 → 插件2代理 → 原始 Executor → 插件2代理 → 插件1代理 → 调用者每个插件可以在proceed()前后添加前置/后置逻辑。 -
7.4 为什么用责任链而非 Spring AOP? | 责任链 | Spring AOP | | ------ | ---------- | | 框架内置,无额外依赖 | 需引入 Spring | | 精准拦截 MyBatis 四大对象 | 通用性强,但配置复杂 | | 插件可独立发布(如 PageHelper) | 与 Spring 强耦合 | | 代理粒度到方法签名级别 | 基于切点表达式 |
8. 设计模式的协同:Executor 创建全过程
MyBatis 中多种设计模式协同工作 ,以 Configuration.newExecutor() 为例:
java
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 1. 策略模式:根据类型选择 Executor 实现
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);
}
// 2. 装饰器模式:添加二级缓存功能
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 3. 代理模式(责任链):应用插件拦截
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
| 步骤 | 设计模式 | 作用 | | ---- | -------- | ---- | | 选择 Executor 实现 | 策略模式 | 根据配置切换算法 | | 包装 CachingExecutor | 装饰器模式 | 动态添加缓存功能 | | 应用插件 | 代理模式 + 责任链 | 多层拦截增强 | | 最终执行 | 模板方法模式 | 标准化执行流程 |
9. 其他设计模式
| 设计模式 | 应用场景 | 具体类 | | -------- | -------- | ------ | | 单例模式 | Configuration、ErrorContext | 全局唯一配置 | | 适配器模式 | Log 接口适配多种日志框架 | Slf4jImpl、Log4jImpl | | 组合模式 | SqlNode(动态 SQL 解析) | MixedSqlNode、IfSqlNode | | 迭代器模式 | PropertyTokenizer | 属性名分词迭代 | | 外观模式 | SqlSession | 简化数据库操作接口 | | 享元模式 | SqlSource 缓存 | 复用解析后的 SQL | |
10. 面试官追问与高分回答模板
-
追问 1:"MyBatis 源码中用了哪些设计模式?"
低分回答:"工厂模式、代理模式、装饰器模式等。"(没有具体落地)
高分回答:
"MyBatis 源码中至少使用了 8 种设计模式,每种都有明确的落地:
- 构建者模式 :
SqlSessionFactoryBuilder分阶段构建SqlSessionFactory,XMLConfigBuilder解析 XML 填充Configuration。 - 工厂模式 :
SqlSessionFactory.openSession()创建SqlSession,Configuration.newExecutor()根据ExecutorType创建不同的 Executor。 - 动态代理模式 :
MapperProxy实现InvocationHandler,为 Mapper 接口生成代理对象,拦截方法调用映射到 SQL。 - 装饰器模式 :
CachingExecutor装饰Executor添加二级缓存,SerializedCache/LoggingCache等装饰PerpetualCache。 - 模板方法模式 :
BaseExecutor定义查询/更新的标准流程(缓存检查 → 执行 → 结果缓存),doQuery()/doUpdate()由子类实现。 - 策略模式 :
ExecutorType枚举选择SimpleExecutor/ReuseExecutor/BatchExecutor,RoutingStatementHandler根据 SQL 类型路由。 - 责任链模式 :
InterceptorChain管理插件,pluginAll()层层代理形成拦截链。 - 单例模式 :
Configuration全局唯一,存储所有配置信息。
这些模式不是孤立使用的,而是协同工作。例如
Configuration.newExecutor()中,先用策略模式 选择 Executor,再用装饰器模式 包装CachingExecutor,最后用代理模式应用插件。" - 构建者模式 :
-
追问 2:"为什么 Executor 用装饰器模式而非继承?"
高分回答:
"MyBatis 选择装饰器模式而非继承,核心原因是避免类爆炸和满足开闭原则:
- 类爆炸问题:如果用继承,缓存功能、日志功能、同步功能需要排列组合。3 个功能就需要 2^3 = 8 个子类,N 个功能需要 2^N 个子类,不可维护。
- 动态组合 :装饰器模式允许运行期动态叠加功能。
CachingExecutor可以装饰SimpleExecutor,也可以装饰ReuseExecutor,组合灵活。 - 开闭原则 :新增功能只需新增装饰器类,不修改原有
Executor代码。例如新增BlockingCache只需实现Cache接口并包装现有实现。 - 职责单一 :每个装饰器只负责一个功能(如
SerializedCache只负责序列化),符合单一职责原则。
源码中
CacheBuilder.setStandardDecorators()就是典型应用:依次包装SerializedCache→LoggingCache→SynchronizedCache,层层叠加。" -
追问 3:"MyBatis 的插件机制是怎么实现的?为什么用责任链模式?"
高分回答:
"MyBatis 插件机制基于责任链模式 + JDK 动态代理实现:
- 注册阶段 :解析
mybatis-config.xml中的<plugins>,将拦截器加入InterceptorChain.interceptors列表。 - 代理创建阶段 :
Configuration.newExecutor()创建 Executor 后,调用interceptorChain.pluginAll(executor),遍历所有拦截器,每个拦截器通过Plugin.wrap()生成代理对象。 - 执行阶段 :调用代理对象方法时,
Plugin.invoke()检查方法签名是否匹配@Intercepts注解,匹配则调用Interceptor.intercept(),否则直接调用目标方法。
为什么用责任链而非 Spring AOP?
- MyBatis 插件是框架内置能力,无需引入 Spring,减少依赖。
- 插件可独立发布(如 PageHelper),用户按需引入。
- 代理粒度精准到方法签名(
type+method+args),AOP 的切点表达式更通用但配置复杂。 - 责任链的执行顺序由配置顺序决定,可控且直观。"
- 注册阶段 :解析
-
追问 4:"模板方法模式在 BaseExecutor 中是怎么体现的?"
高分回答:
"
BaseExecutor是模板方法模式的经典应用,它定义了 SQL 查询的标准流程 (模板方法query()),将具体执行延迟到子类(doQuery()):模板方法
query()的标准流程:- 获取
BoundSql - 创建
CacheKey - 检查一级缓存(
localCache.getObject(key)) - 缓存命中 → 返回结果
- 缓存未命中 → 调用
queryFromDatabase() queryFromDatabase()中调用抽象方法doQuery()(子类实现)- 结果放入一级缓存
子类实现的差异:
SimpleExecutor.doQuery():每次新建StatementHandler→ 新建Statement→ 执行ReuseExecutor.doQuery():从Map<String, Statement>复用 StatementBatchExecutor.doUpdate():将 SQL 加入批处理队列,统一提交
这种设计把公共流程 (缓存、事务)和可变逻辑(Statement 创建方式)分离,避免代码重复。"
- 获取
-
追问 5:"构建者模式和工厂模式在 MyBatis 中有什么区别?"
高分回答:
"两者都用于对象创建,但关注点不同:
维度 构建者模式 工厂模式 关注点 复杂对象的分阶段构建 对象的统一创建入口 典型类 SqlSessionFactoryBuilderSqlSessionFactory、Configuration产品复杂度 高(需多步骤组装) 中等(根据参数创建不同实现) 调用方式 链式调用或分步调用 简单工厂方法 具体例子:
- 构建者模式 :
SqlSessionFactoryBuilder.build(inputStream)分三步:解析 XML → 生成 Configuration → 创建 DefaultSqlSessionFactory。每一步都依赖前一步的结果。 - 工厂模式 :
SqlSessionFactory.openSession()直接返回SqlSession,隐藏了Transaction、Executor的创建细节。Configuration.newExecutor()根据ExecutorType返回不同的 Executor 实现。
简单说:构建者模式解决怎么一步一步构建 ,工厂模式解决创建哪个实现类。"
- 构建者模式 :
-
追问 6:"如果从零设计一个 ORM 框架,你会怎么选择设计模式?"
高分回答:
"我会参考 MyBatis 的设计,根据需求做取舍:
- 构建者模式 :配置解析和核心对象创建必须用,如
SessionFactoryBuilder,保证复杂对象的正确构建。 - 动态代理模式:Mapper 接口代理必选,JDK 代理足够,除非需要代理类才考虑 CGLIB。
- 装饰器模式:缓存、日志、监控等功能用装饰器叠加,避免继承爆炸。
- 模板方法模式:定义 SQL 执行的标准流程(如查询:缓存检查 → 执行 → 结果映射),子类实现数据库方言差异。
- 策略模式:支持多种执行策略(简单、批处理、流式查询),支持多种数据库方言(MySQL、PostgreSQL、Oracle)。
- 责任链模式:插件机制用责任链,但会限制插件数量(代理层数过多影响性能)。
- 事件驱动模式:考虑用观察者模式替代部分插件功能,降低代理开销。
关键权衡:
- 性能敏感场景减少装饰器和代理层数
- 插件机制提供但限制使用场景,避免过度设计
- 类型转换用策略模式而非大量 if-else"
- 构建者模式 :配置解析和核心对象创建必须用,如
11. 方案选型速查表
| 设计模式 | MyBatis 落地类 | 解决的问题 | 替代方案及不适用原因 |
|---|---|---|---|
| 构建者模式 | SqlSessionFactoryBuilder |
复杂对象分阶段构建 | 构造函数:参数过多;工厂:不关注构建过程 |
| 工厂模式 | DefaultSqlSessionFactory |
统一创建入口 | 直接 new:创建逻辑暴露 |
| 动态代理 | MapperProxy |
接口实现自动生成 | 静态代理:每个接口需手写;CGLIB:过度设计 |
| 装饰器模式 | CachingExecutor、SerializedCache |
功能动态叠加 | 继承:类爆炸;AOP:框架过重 |
| 模板方法 | BaseExecutor、BaseTypeHandler |
流程标准化 | 策略模式:无法定义固定流程 |
| 策略模式 | ExecutorType、RoutingStatementHandler |
算法可插拔 | 枚举+if-else:扩展性差 |
| 责任链 | InterceptorChain |
多层拦截 | AOP:引入外部框架;观察者:无法顺序控制 |
| 单例模式 | Configuration |
全局唯一配置 | 静态类:无法 mock 测试 |
💡 面试官想要的满分总结:
MyBatis 源码是设计模式的教科书级实践 ,不是简单堆砌模式,而是根据问题特征选择最合适的模式,并让多种模式协同工作。
理解 MyBatis 的设计模式必须抓住三个核心洞察:
- 装饰器模式是架构核心 :
CachingExecutor装饰Executor、SerializedCache装饰PerpetualCache,用组合替代继承,避免类爆炸,符合开闭原则。这是 MyBatis 扩展性最强的设计。- 模板方法定义框架骨架 :
BaseExecutor定义查询/更新的标准流程(缓存检查 → 执行 → 结果缓存),doQuery()由子类实现,把公共流程和可变逻辑精准分离。- 多种模式协同而非孤立 :
Configuration.newExecutor()是最佳案例------策略模式选择实现 → 装饰器模式添加缓存 → 代理模式应用插件 → 模板方法模式定义执行流程。工程启示:设计模式不是炫技,而是解决特定问题的标准方案。MyBatis 的选择告诉我们------当需要动态叠加功能时用装饰器,当需要标准化流程时用模板方法,当需要算法替换时用策略模式。真正的高手不是记住多少模式,而是能根据问题特征快速匹配最合适的模式,并理解模式之间的协作关系。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯