本文由 AI 阅读网络公开技术资讯生成,力求客观但可能存在信息偏差,具体技术细节及数据请以权威来源为准
摘要
本文为《MyBatis 源码深度解析》系列第四篇,聚焦 SqlSession 体系的核心设计与实现细节。在前几篇已厘清整体架构、配置加载及 Mapper 代理机制的基础上,本篇深入剖析 SqlSession 作为 MyBatis 数据操作门面的生命周期管理、执行器(Executor)协同机制、事务封装逻辑及其与 Configuration、MapperProxy 的交互脉络,揭示其轻量、线程不安全却高度灵活的设计哲学。
关键词
SqlSession, 源码解析, MyBatis, 核心设计, 实现细节
SqlSession 是 MyBatis 框架中真正承载数据操作使命的“呼吸口”——它不显山露水,却串联起配置、映射、执行与事务的全部脉搏。在 MyBatis 的世界里,它并非一个持久化对象,而是一次性、短生命周期的轻量级会话载体:从 SqlSessionFactory.openSession() 被唤起那一刻起,它便开始承载用户对数据库的读写意图;直至显式调用 close() 或随作用域结束而释放,其内部持有的 Executor、Transaction 及缓存上下文也随之归零。这种“用完即弃”的设计,并非权宜之计,而是深思熟虑后的克制——它规避了线程安全陷阱,将状态管理的主动权交还给开发者,也正因如此,SqlSession 被明确定义为线程不安全,却换来了极致的灵活性与可组合性。它是 Mapper 代理背后沉默的执行者,是用户代码与 JDBC 底层之间最可信的翻译官,更是 MyBatis “约定优于配置”哲学在运行时最真切的落点。
SqlSession 本身是一个高度抽象的接口,其具体实现由 DefaultSqlSession 承载,而该实例的诞生,则严格依赖于 SqlSessionFactory 这一工厂枢纽。SqlSessionFactory 并非凭空构建 SqlSession,而是依据 Configuration 中已解析完毕的环境配置(如数据源、事务工厂)、执行器类型(SIMPLE/REUSE/BATCH)及是否启用自动提交等参数,动态装配出具备完整行为能力的会话实例。在方法层面,selectOne、selectList、insert、update、delete 等命名直指语义本质,不加掩饰地暴露其 CRUD 本色;而 commit()、rollback()、clearCache() 等则构成对会话状态的显式干预入口。尤为值得注意的是,所有这些方法均以统一签名接收 String statementId(即 Mapper 接口全限定名 + 方法名)与任意参数,这正是 SqlSession 与 Mapper 代理机制深度咬合的技术支点——它不关心调用来自 XML 还是注解,也不追问参数是 POJO 还是 Map,只专注将逻辑指令精准投递给底层执行器。
SqlSession 的状态,本质上是其内部 Transaction 与 Executor 协同演化的结果。事务并非 SqlSession 自主管理的实体,而是由 TransactionFactory 创建并交由 Executor 持有;当 autoCommit = false(默认值),SqlSession 将事务控制权完全让渡给开发者——commit() 与 rollback() 成为不可回避的责任契约;而一旦启用自动提交,Executor 便会在每次执行后立即触发 JDBC Connection.commit(),此时 SqlSession 更像一个无状态的执行通道。更精微之处在于其缓存状态:一级缓存(PerpetualCache)依附于 SqlSession 生命周期存在,查询结果默认被缓存,但任何 update 类操作都会清空当前会话缓存——这一“读写分离”的隐式契约,既保障了数据一致性,又无需额外配置。这种状态管理不靠魔法,而靠清晰的边界划分:SqlSession 不越界持有连接,不僭越封装事务,只忠实地反映并协调其所聚合组件的状态变迁。
SqlSession 是门面模式(Facade Pattern)教科书级的实践:它将 Executor 的复杂调度、Transaction 的底层交互、Configuration 的元数据检索、MapperRegistry 的代理生成等多重子系统,浓缩为一组语义明确、调用简洁的公开方法。用户无需知晓 CachingExecutor 如何装饰 BaseExecutor,亦不必理解 RoutingStatementHandler 怎样分发 StatementType——这一切都被 SqlSession 这扇门温柔地掩去。而 SqlSessionFactory 则稳稳立于工厂模式(Factory Pattern)的中心:它不直接暴露 DefaultSqlSession 的构造细节,而是通过 openSession() 系列重载方法,依据传入的 ExecutorType、TransactionIsolationLevel、autoCommit 标志等参数,封装对象创建逻辑,确保每一次会话诞生都符合当前上下文语义。两种模式在此交汇——工厂负责“造人”,门面负责“示人”;一个赋予 SqlSession 以生命,一个赋予它以人格。这并非架构师的炫技,而是面向开发者心智模型的一次郑重致敬:让复杂可感,让接口可信,让每一次 sqlSession.selectList("UserMapper.selectAll") 都成为一次沉静而笃定的对话。
DefaultSqlSession 并非一个被精心修饰的“成品”,而是一具精密咬合的骨架——它不渲染表象,只承载力与序。作为 SqlSession 接口的唯一官方实现,它不定义行为,却严格编排行为的执行次序:持有一个不可变的 Configuration 引用,确保元数据的一致性;封装一个动态生成的 Executor 实例,将所有 CRUD 操作无差别转译为执行器指令;同时轻量持有 Transaction 对象,仅作事务生命周期的见证者,而非干预者。它的构造函数拒绝裸露细节,所有初始化逻辑均被收束于 SqlSessionFactory 的工厂契约之内;它的每个方法——从 selectList 到 update——都遵循同一范式:参数校验 → 语句解析(通过 Configuration.getMappedStatement(statementId))→ 执行委托(executor.query/update/...)→ 结果封装或异常透传。没有冗余状态,没有隐式缓存控制,没有跨方法的上下文延续——它只是冷静地、忠实地,把开发者的每一次调用,翻译成 Executor 能听懂的语言。这种克制,不是贫乏,而是对“单一职责”的虔诚践行:它不做 Executor 的事,也不越界扮演 Transaction,它只是那个站在光与暗交界处的信使,既不遮蔽底层,也不喧宾夺主。
当一行 sqlSession.selectList("UserMapper.selectAll") 被执行,一场静默而严密的协作即刻启程。起点是 DefaultSqlSession.selectList,它不加思索地将 statementId 与空参数交予 Configuration,由后者精准定位 MappedStatement——那是一份早已在启动时解析完毕的 SQL 元数据契约。紧接着,控制权移交至 Executor.query(),此处开始分叉:若启用二级缓存,则经 CachingExecutor 尝试命中;否则直抵 BaseExecutor,触发 queryFromDatabase——先注册占位缓存项,再委派 StatementHandler 准备 JDBC Statement,最终由 ParameterHandler 绑定参数、ResultSetHandler 映射结果。整个链路如钟表齿轮般严丝合缝:SqlSession 不参与任何 SQL 构建或结果转换,它只负责发起、等待、交付;它像一位守门人,确认来者身份(statementId),打开门(调用 executor),然后安静退至幕后,任由更专业的组件完成全部繁复工作。没有魔法,只有清晰的委托边界与可追溯的调用栈——这正是 MyBatis 可信感的来源:每一步,都可查;每一环,皆可见。
Executor 是 SqlSession 的手与脚,而 SqlSession 是它的喉舌与契约。二者之间不存在继承或强耦合,唯有组合与信赖:DefaultSqlSession 仅持有一个 Executor 引用,且该引用在会话创建后即冻结,永不替换——这是稳定性的基石。Executor 类型(SIMPLE/REUSE/BATCH)由 SqlSessionFactory 在构建 SqlSession 时注入,决定了后续所有操作的行为底色:SIMPLE 每次新建 Statement,REUSE 复用已准备好的 Statement,BATCH 则延迟执行、批量提交。SqlSession 从不干预其内部策略,却通过 commit()、rollback()、clearCache() 等方法,向 Executor 发出高层语义指令;而 Executor 则以事务感知、缓存刷新、执行重试等能力,回应这份信任。尤为关键的是,Executor 持有 Transaction 实例,并直接操作 JDBC Connection——SqlSession 从不触碰连接本身,它只说“请执行”,而把“如何连、如何提、如何回”全权托付。这种松耦合、高内聚的协作,让 SqlSession 始终保持轻盈,也让 Executor 得以自由演进——它们之间没有誓言,却有最坚固的约定:一个负责表达意图,一个负责兑现承诺。
一级缓存,是 SqlSession 生命里最私密的记忆——它不共享、不持久、不越界,只属于当前会话自身。其实现极为朴素:BaseExecutor 内部持有一个 PerpetualCache 实例,键为 CacheKey(由 statementId、参数、分页参数、环境 ID 等共同构成),值为查询结果。每次 query 调用前,先查缓存;命中则直接返回,跳过数据库访问;未命中则执行真实查询,并将结果写入缓存。然而,这份记忆极其脆弱:任何 update、insert 或 delete 操作,都会触发 clearLocalCache(),清空整个 PerpetualCache——这不是保守,而是清醒的取舍。MyBatis 明白,在单会话范围内,一次写操作足以污染所有读结果;与其费力追踪依赖关系,不如以最简策略保障强一致性。更值得玩味的是,缓存的生命周期与 SqlSession 完全绑定:close() 一调,缓存随 Executor 一同消散;commit() 或 rollback() 后,缓存亦被清空——因为事务结束,意味着本次会话语义的终结,旧记忆不再可信。一级缓存从不标榜性能,它只是 SqlSession 在短暂存在中,为自己点亮的一盏微灯:不照亮他人,只温暖当下。
SqlSession 作为 MyBatis 数据操作的统一门面,其设计以轻量性、线程不安全性与高度组合性为根本特征。它不承载持久状态,不直管连接与事务,而是通过严谨的委托机制,将执行逻辑交由 Executor,将事务生命周期交由 Transaction,将元数据检索锚定于 Configuration。从 openSession() 的工厂创建,到 selectList/update 等语义化方法的调用,再到一级缓存的自动管理与写操作触发的即时失效,整个体系始终恪守“单一职责”与“边界清晰”的工程信条。其背后融合的门面模式与工厂模式,并非抽象概念的堆砌,而是面向开发者认知负荷的一次系统性减法——让复杂可追溯,让接口可信赖,让每一次数据库交互,都成为一次意图明确、路径透明、责任分明的技术实践。