手写MyBatis第30弹:重构MapperProxy,让SqlSession成为真正的执行核心

#AIcoding·八月创作之星挑战赛#

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥  有兴趣可以联系我。

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。 


热门标题推荐

  1. MyBatis架构精讲:重构MapperProxy,让SqlSession成为真正的执行核心

  2. 从Mapper代理到SqlSession:揭秘MyBatis接口绑定的底层适配机制

  3. 手写MyBatis进阶:将MapperProxy改造为SqlSession的优雅适配器

  4. 深入理解MyBatis设计哲学:为什么说SqlSession才是执行的唯一入口?

  5. 架构解耦的艺术:重构MapperProxy实现职责分离的最佳实践


正文

在上一篇文章中,我们亲手实现了SqlSession的CRUD方法,建立了MyBatis最底层的执行通道。然而,在日常开发中,我们更习惯使用Mapper接口这种优雅的方式。今天,我们将完成架构演进的关键一步:重构MapperProxy,让其从直接执行者转变为SqlSession的适配器,从而确立SqlSession作为统一执行入口的核心地位。

一、为何要重构:架构清晰化的必然选择

在初始实现中,MapperProxyinvoke方法很可能直接调用了Executor执行数据库操作。这种设计虽然功能上可行,但在架构上存在明显缺陷:

  1. 职责混乱MapperProxy作为动态代理,本应只负责"转发"或"适配",而不应该承担具体的执行职责。直接调用Executor让它承担了过多责任,违反了单一职责原则。

  2. API不统一:如果某些操作通过MapperProxy直接执行,而另一些操作通过SqlSession执行,会导致代码中存在两套执行路径,增加维护复杂度。

  3. 功能缺失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的不同集成环境中,有着不同的管理策略:

  1. 原生MyBatis(SqlSessionManager)

    • SqlSessionManager提供了两种模式:单例管理和线程本地管理。

    • 在单例模式下,所有操作共享同一个SqlSession,需要开发者手动管理事务和关闭。

    • 在线程本地模式下,每个线程有自己的SqlSession实例,通常在请求开始时创建,请求结束后关闭。

  2. MyBatis-Spring(SqlSessionTemplate)

    • SqlSessionTemplate是线程安全的模板类,内部通过TransactionSynchronizationManager管理SqlSession

    • 它与Spring的事务管理紧密集成,会根据当前事务状态决定是使用新的SqlSession还是重用现有的。

    • 它还将MyBatis的异常转换为Spring的DataAccessException体系,实现了更好的集成。

无论哪种方式,核心原则都是:SqlSession的生命周期应该与数据库事务保持一致。这也是为什么在Web应用中,我们通常采用"每个请求一个SqlSession"的模式。

四、重构的价值:更优雅的架构设计

这次重构带来了多重价值:

  1. 职责清晰化MapperProxy成为纯粹的适配器,SqlSession成为统一的执行入口,各组件职责单一且明确。

  2. 更好的扩展性:所有操作都通过SqlSession执行,便于添加统一的拦截、监控、日志等功能。

  3. 事务一致性:通过SqlSession统一执行,确保了通过Mapper接口执行的操作也能正确参与事务管理。

  4. 与官方设计对齐:我们的框架架构更加接近官方MyBatis,为后续添加更多高级特性奠定了基础。

五、总结

通过将MapperProxy从直接执行者重构为SqlSession的适配器,我们完成了一次重要的架构演进。这不仅让我们的手写MyBatis更加专业和健壮,也让我们深入理解了官方MyBatis的设计智慧。

这种"适配器模式"的应用随处可见:无论是Java中的IO流包装,还是Spring中的各种Template,亦或是RPC框架中的代理生成,其核心思想都是通过一个中间层来解耦接口与实现,提供统一的访问入口。

下次当你使用@Autowired注入一个Mapper接口时,不妨想一想背后的MapperProxy是如何优雅地将你的方法调用适配到SqlSession,再经由Executor最终到达数据库的。这份理解,将让你在面对复杂系统设计时,能够做出更加优雅的架构决策。


💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!

💖常来我家多看看,
📕我是程序员扣棣,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!

💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值