手写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接口中直接定义增删改查方法。

java 复制代码
 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去执行。

java 复制代码
 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;
     }
 ​
     @Override
     public <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;
         }
     }
 ​
     @Override
     public <E> List<E> selectList(String statementId, Object parameter) {
         // 1. 核心:根据statementId从配置中获取MappedStatement
         MappedStatement ms = configuration.getMappedStatement(statementId);
         // 2. 委托给Executor执行
         return executor.query(ms, parameter);
     }
 ​
     @Override
     public int insert(String statementId, Object parameter) {
         // 增删改本质上都是update操作
         return update(statementId, parameter);
     }
 ​
     @Override
     public int update(String statementId, Object parameter) {
         MappedStatement ms = configuration.getMappedStatement(statementId);
         return executor.update(ms, parameter);
     }
 ​
     @Override
     public 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:

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

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

java 复制代码
 EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
 Employee emp = mapper.selectById(1);

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

java 复制代码
 // 注意: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时更加得心应手,充满自信。


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

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

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