🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论
🔥🔥🔥(源码 + 调试运行 + 问题答疑)
🔥🔥🔥 有兴趣可以联系我。
我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。
热门标题推荐
-
MyBatis架构精讲:重构MapperProxy,让SqlSession成为真正的执行核心
-
从Mapper代理到SqlSession:揭秘MyBatis接口绑定的底层适配机制
-
手写MyBatis进阶:将MapperProxy改造为SqlSession的优雅适配器
-
深入理解MyBatis设计哲学:为什么说SqlSession才是执行的唯一入口?
-
架构解耦的艺术:重构MapperProxy实现职责分离的最佳实践
正文
在上一篇文章中,我们亲手实现了SqlSession
的CRUD方法,建立了MyBatis最底层的执行通道。然而,在日常开发中,我们更习惯使用Mapper接口这种优雅的方式。今天,我们将完成架构演进的关键一步:重构MapperProxy
,让其从直接执行者转变为SqlSession
的适配器,从而确立SqlSession
作为统一执行入口的核心地位。
一、为何要重构:架构清晰化的必然选择
在初始实现中,MapperProxy
的invoke
方法很可能直接调用了Executor
执行数据库操作。这种设计虽然功能上可行,但在架构上存在明显缺陷:
-
职责混乱:
MapperProxy
作为动态代理,本应只负责"转发"或"适配",而不应该承担具体的执行职责。直接调用Executor
让它承担了过多责任,违反了单一职责原则。 -
API不统一:如果某些操作通过
MapperProxy
直接执行,而另一些操作通过SqlSession
执行,会导致代码中存在两套执行路径,增加维护复杂度。 -
功能缺失:
SqlSession
还提供了事务控制(commit/rollback)、缓存清理(clearCache)等重要功能。如果Mapper代理绕过SqlSession
,这些功能就无法正确作用于通过接口执行的操作。
通过重构,我们将建立清晰的职责边界:MapperProxy
只做适配,SqlSession
统一执行。这种架构与官方MyBatis的设计完全一致,让我们的小框架更加专业和健壮。
为了更直观地理解重构前后的架构变化,请看下面的对比图:
从图中可以清晰看出,重构后SqlSession
成为了所有数据库操作的唯一入口,架构更加清晰统一。
二、重构实战:从直接执行到优雅适配
第一步:修改MapperProxy构造函数,注入SqlSession
重构的第一步是让MapperProxy
能够访问到SqlSession
实例。
public class MapperProxy<T> implements InvocationHandler {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
// 构造函数注入SqlSession实例
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}
// ... 其他代码
}
第二步:重构invoke方法,适配到SqlSession
这是最核心的改动。invoke
方法不再直接执行SQL,而是将方法调用"翻译"成对SqlSession
相应方法的调用。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 处理Object类的方法(如toString、hashCode等)
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 获取方法对应的MappedStatement的id(namespace.id)
String statementId = mapperInterface.getName() + "." + method.getName();
// 根据方法返回类型,适配到SqlSession的不同方法
Class<?> returnType = method.getReturnType();
if (Void.TYPE.equals(returnType)) {
// 无返回值的方法,适合执行update/insert/delete
sqlSession.update(statementId, args != null && args.length > 0 ? args[0] : null);
return null;
} else if (returnType.isAssignableFrom(List.class)) {
// 返回列表
return sqlSession.selectList(statementId, args != null && args.length > 0 ? args[0] : null);
} else if (returnType.isAssignableFrom(Map.class)) {
// 返回Map(可能需要特殊处理,这里简化)
return sqlSession.selectMap(statementId, args != null && args.length > 0 ? args[0] : null, "id");
} else if (int.class.equals(returnType) || Integer.class.equals(returnType)) {
// 返回int(通常用于影响行数)
return sqlSession.update(statementId, args != null && args.length > 0 ? args[0] : null);
} else {
// 默认按单对象查询处理
return sqlSession.selectOne(statementId, args != null && args.length > 0 ? args[0] : null);
}
}
第三步:修改SqlSession的getMapper方法
现在需要在创建MapperProxy
时传入SqlSession
实例。
public class DefaultSqlSession implements SqlSession {
// ... 其他代码
@Override
public <T> T getMapper(Class<T> type) {
// 使用当前SqlSession实例创建MapperProxy
MapperProxy<T> mapperProxy = new MapperProxy<>(this, type);
return (T) Proxy.newProxyInstance(
type.getClassLoader(),
new Class[]{type},
mapperProxy
);
}
// ... 其他代码
}
三、架构思考:SqlSession的生命周期管理
重构后,SqlSession
的重要性更加凸显。它的生命周期管理成为了关键问题。在MyBatis的不同集成环境中,有着不同的管理策略:
-
原生MyBatis(SqlSessionManager):
-
SqlSessionManager
提供了两种模式:单例管理和线程本地管理。 -
在单例模式下,所有操作共享同一个
SqlSession
,需要开发者手动管理事务和关闭。 -
在线程本地模式下,每个线程有自己的
SqlSession
实例,通常在请求开始时创建,请求结束后关闭。
-
-
MyBatis-Spring(SqlSessionTemplate):
-
SqlSessionTemplate
是线程安全的模板类,内部通过TransactionSynchronizationManager
管理SqlSession
。 -
它与Spring的事务管理紧密集成,会根据当前事务状态决定是使用新的
SqlSession
还是重用现有的。 -
它还将MyBatis的异常转换为Spring的
DataAccessException
体系,实现了更好的集成。
-
无论哪种方式,核心原则都是:SqlSession的生命周期应该与数据库事务保持一致。这也是为什么在Web应用中,我们通常采用"每个请求一个SqlSession"的模式。
四、重构的价值:更优雅的架构设计
这次重构带来了多重价值:
-
职责清晰化:
MapperProxy
成为纯粹的适配器,SqlSession
成为统一的执行入口,各组件职责单一且明确。 -
更好的扩展性:所有操作都通过
SqlSession
执行,便于添加统一的拦截、监控、日志等功能。 -
事务一致性:通过
SqlSession
统一执行,确保了通过Mapper接口执行的操作也能正确参与事务管理。 -
与官方设计对齐:我们的框架架构更加接近官方MyBatis,为后续添加更多高级特性奠定了基础。
五、总结
通过将MapperProxy
从直接执行者重构为SqlSession
的适配器,我们完成了一次重要的架构演进。这不仅让我们的手写MyBatis更加专业和健壮,也让我们深入理解了官方MyBatis的设计智慧。
这种"适配器模式"的应用随处可见:无论是Java中的IO流包装,还是Spring中的各种Template,亦或是RPC框架中的代理生成,其核心思想都是通过一个中间层来解耦接口与实现,提供统一的访问入口。
下次当你使用@Autowired
注入一个Mapper接口时,不妨想一想背后的MapperProxy
是如何优雅地将你的方法调用适配到SqlSession
,再经由Executor
最终到达数据库的。这份理解,将让你在面对复杂系统设计时,能够做出更加优雅的架构决策。
💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!💖常来我家多看看,
📕我是程序员扣棣,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!