当前位置: 首页 > news >正文

手写MyBatis第28弹:告别代理,直击本质:手写MyBatis SqlSession的增删改查奥秘

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

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

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

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

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

目录

热门标题推荐

正文

一、为何需要非Mapper代理的API?

二、核心架构与执行流程剖析

三、手写实现:从接口定义到具体执行

四、实战演示:如何使用底层API五、总结与思考


  1. 深入MyBatis内核:徒手打造SqlSession的CRUD圣殿

  2. 告别代理,直击本质:手写MyBatis SqlSession的增删改查奥秘

  3. MyBatis源码进阶(一):从SqlSession的CRUD接口看框架执行脉络

  4. “硬核”操作:在手写MyBatis中实现最底层的SQL执行API

  5. 架构师视角:为什么SqlSession的直接CRUD API是MyBatis灵活性的基石?


正文

在我们日常使用MyBatis进行开发时,最熟悉的莫过于定义Mapper接口并配合@Select@Insert等注解或XML文件来轻松实现数据库操作。这背后是MyBatis强大的动态代理机制在默默工作。但你是否曾想过,抛开这层“魔法”,MyBatis最核心、最底层的执行单元究竟是什么?今天,我们就将深入MyBatis的内核,亲手实现SqlSession接口中的直接CRUD方法,探究其原理、价值与设计哲学。

一、为何需要非Mapper代理的API?

在回答如何实现之前,我们必须先理解为什么要提供这样一种看似“繁琐”的API。Mapper代理的方式固然优雅,但它并非万能。

  1. 极致灵活性与动态性:在某些高度动态的场景中,SQL语句的statementId(即namespace.id)甚至SQL片段本身都可能是在运行时根据业务逻辑生成的。此时,我们无法在编码阶段定义出固定的Mapper接口方法。通过SqlSession直接传入statementId的方式,可以灵活地执行任何在配置中定义的SQL映射。

  2. 理解框架执行脉络:Mapper代理只是一个“外观模式”(Facade Pattern)的优雅实现,它最终调用的仍然是SqlSessionselectOneinsert等方法。学习直接使用这些方法,有助于我们更深层次地理解MyBatis的执行流程和数据流转,这对于排查复杂问题、进行性能调优至关重要。

  3. 遗留系统或特殊集成:在一些古老的系统或者特殊的集成需求中,可能无法按照MyBatis的约定来定义接口。这种底层API提供了更大的集成空间。

  4. 框架扩展的基石:许多基于MyBatis的增强框架或中间件(如分库分表组件),其内部往往需要绕过Mapper代理,直接与最核心的ExecutorSqlSession打交道。理解这一层,是进行二次开发的基础。

正因为其底层和灵活的特性,SqlSession实例的生命周期管理就显得尤为重要。它通常不是线程安全的,因此其生命周期应被限定在一次请求或一个方法作用域内(即“请求级别”)。通过SqlSessionFactory来创建,在使用后务必通过close()方法将其关闭,以防止数据库连接泄漏。这也是为什么我们常用try-with-resources语句来确保其被正确关闭。

二、核心架构与执行流程剖析

在开始手写代码之前,我们先通过一张图来俯瞰整个执行流程,这有助于理解各个组件是如何协同工作的:

如图所示,SqlSession是API的入口,但它只是一个“指挥中心”,真正的执行工作委托给了Executor。而Executor又会进一步将参数处理、SQL执行、结果集映射等任务分发给各自的专职组件(StatementHandler, ParameterHandler, ResultSetHandler)。整个流程清晰且职责分明,是经典职责链模式的体现。

我们的核心任务,就是在DefaultSqlSession中实现这个“指挥”的逻辑。

三、手写实现:从接口定义到具体执行

现在,让我们开始动手,一步步实现图中的流程。

第一步:定义SqlSession接口的CRUD方法

我们在SqlSession接口中直接定义增删改查方法。

 public interface SqlSession extends Closeable {​/*** 检索一条记录* @param statementId namespace.id* @param parameter 参数对象* @return 单条结果对象*/<T> T selectOne(String statementId, Object parameter);​/*** 插入一条记录* @param statementId namespace.id* @param parameter 参数对象* @return 影响的行数*/int insert(String statementId, Object parameter);​// 类似定义 update, delete 方法int update(String statementId, Object parameter);int delete(String statementId, Object parameter);// ... 其他方法如 getMapper, getConfiguration, commit, rollback 等}

第二步:在DefaultSqlSession中实现这些方法

DefaultSqlSession是接口的默认实现,它持有ConfigurationExecutor的引用,所有方法的核心逻辑都是:

  1. 通过Configuration获取对应的MappedStatement

  2. 将参数和MappedStatement交给Executor去执行。

 public class DefaultSqlSession implements SqlSession {​private final Configuration configuration;private final Executor executor;​public DefaultSqlSession(Configuration configuration, Executor executor) {this.configuration = configuration;this.executor = executor;}​@Overridepublic <T> T selectOne(String statementId, Object parameter) {// 本质上就是调用selectList,然后取第一条结果List<T> list = this.selectList(statementId, parameter);if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new RuntimeException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}}​@Overridepublic <E> List<E> selectList(String statementId, Object parameter) {// 1. 核心:根据statementId从配置中获取MappedStatementMappedStatement ms = configuration.getMappedStatement(statementId);// 2. 委托给Executor执行return executor.query(ms, parameter);}​@Overridepublic int insert(String statementId, Object parameter) {// 增删改本质上都是update操作return update(statementId, parameter);}​@Overridepublic int update(String statementId, Object parameter) {MappedStatement ms = configuration.getMappedStatement(statementId);return executor.update(ms, parameter);}​@Overridepublic int delete(String statementId, Object parameter) {return update(statementId, parameter);}​// ... 关闭、提交、回滚等方法}

关键点剖析:

  • MappedStatement:它是MyBatis的核心数据结构之一,是一个不可变的对象。它完整地描述了一条SQL语句的所有信息:SQL源、执行类型(SELECT/UPDATE等)、输入参数映射、输出结果映射、缓存策略、ID等。通过statementId(格式为namespace.id)从Configuration这个全局配置容器中获取它,是执行的第一步。

  • Executor:它是真正的执行器,是MyBatis的调度核心SqlSession的所有数据库操作命令都委托给它来完成。它定义了queryupdatecommitrollback等方法。我们的默认实现(SimpleExecutor)会处理整个执行流程:

    1. 创建StatementHandler(它负责创建PreparedStatement)。

    2. 创建ParameterHandler(它负责将用户传入的参数按规则设置到SQL中)。

    3. 执行语句。

    4. 创建ResultSetHandler(它负责将返回的ResultSet转换成Java对象集合)。

  • 一致性:注意到insertupdatedelete最终都调用了executor.update()。这说明在JDBC层面和MyBatis底层视角中,只有SELECTUPDATE两种操作INSERTDELETE在JDBC中也属于更新操作)。这种设计简化了内部实现。

四、实战演示:如何使用底层API

假设我们在XML中配置了一个SQL:

 <mapper namespace="com.example.EmployeeMapper"><select id="selectById" resultType="com.example.Employee">SELECT * FROM employee WHERE id = #{id}</select></mapper>

使用Mapper代理方式,我们会这样调用:

 EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);Employee emp = mapper.selectById(1);

而使用我们今天实现的底层API,则可以这样调用:

 // 注意:statementId 必须是 "namespace.id" 的全限定名String statementId = "com.example.EmployeeMapper.selectById";Employee emp = sqlSession.selectOne(statementId, 1);

可以看到,这种方式确实更为繁琐,需要手动拼接字符串且容易出错,但它赋予了我们在运行时动态选择SQL的巨大灵活性。

五、总结与思考

通过手写SqlSession的CRUD方法,我们剥离了MyBatis的动态代理“外衣”,直击其核心执行引擎。我们看到了SqlSession如何作为API门面,Configuration如何作为配置中枢,Executor如何作为调度核心,以及MappedStatement如何作为SQL指令的载体。

这种底层API的存在,不仅是MyBatis框架灵活性和扩展性的体现,更是我们开发者深入理解框架、解决复杂问题的钥匙。它告诉我们,任何一个优秀的框架,其高层抽象的便捷性必然构建在底层坚实的灵活性和可扩展性之上。

下次当你轻松地调用一个Mapper接口方法时,不妨在脑海中回顾一下这条清晰的执行链:Interface -> Proxy -> SqlSession -> Executor -> JDBC Statement。这份理解,将让你在使用MyBatis时更加得心应手,充满自信。


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

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

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

http://www.dtcms.com/a/342054.html

相关文章:

  • 深入解析HashMap的存储机制:扰动函数、哈希计算与索引定位
  • Halcon那些事:Halcon非常核心的1个概念reduce_domain算子的理解和1个详细的使用示例
  • Nginx缓存配置指南:使用proxy_cache为动态网站提速10倍
  • WPF中UI线程频繁操作造成卡顿的处理
  • Ingress控制器深度解析:Nginx与Traefik实战指南
  • 【DICOM HL7】DICOM hl7协议的哪个字段对应操作者,操作者ID?
  • C++析构函数
  • Linux下Docker版本升级保姆攻略
  • 结合 Flutter 和 Rust 的跨平台开发方案
  • 微软Auzre云的技术支持运营模式是什么
  • Flutter - UI布局
  • Android APP防止应用被动态调试
  • 大数据毕业设计选题推荐-基于大数据的北京气象站数据可视化分析系统-Hadoop-Spark-数据可视化-BigData
  • 浏览器【详解】页面加载过程(含页面加载时序图,页面加载性能优化方案)
  • 搭建我的世界mc服务器全流程——阿里云游戏攻略
  • 09_测试与性能优化
  • 新型犯罪浪潮下的法律迷局:网络、AI与跨境犯罪解析
  • 惯性导航中的IMU传感器是什么?
  • 第5.2节:awk变量的使用
  • 适配器模式 java demo
  • 电能质量监测装置 分布式光伏安全并网“准入证”
  • AI工作负载“加速跑”,高性能网络如何“护航”?
  • EfficientVMamba代码略讲
  • 档案宝系统功能:权限分级,保障档案安全
  • KingbaseES数据库增删改查操作分享
  • 项目集成 Chrono 时间轴
  • Pytest 插件怎么写:从0开发一个你自己的插件
  • SamOutVXP: 轻量级高效语言模型
  • 用nohup setsid绕过超时断连,稳定反弹Shell
  • Spring 循环依赖:从 “死锁” 到 “破局” 的完整解析