MyBatis 核心组件:SqlSession 全解析(创建、实现、线程安全与四大执行对象)
SqlSession 是 MyBatis 中连接开发者与底层数据库操作的核心接口,封装了数据库 CRUD、事务管理、缓存控制等核心能力。本文基于 SqlSession 接口定义、DefaultSqlSession 实现及 SqlSessionManager 源码,从 “接口定位→创建流程→实现逻辑→线程安全→底层协作” 五个维度,彻底拆解 SqlSession 的工作机制,同时关联 Executor 等四大核心对象,帮你掌握 MyBatis 会话管理的本质。
SqlSession 接口:MyBatis 会话的 “入口协议”
SqlSession 相当于 JDBC 中的 Connection
,但功能更丰富 —— 不仅是数据库连接的封装,还提供了 Mapper 接口绑定、缓存管理、事务控制等能力。其接口定义按功能可分为 5 大类方法,覆盖所有数据库交互场景。
1. 核心方法分类与解析
(1)查询方法:select* 系列
查询方法是 SqlSession 最常用的功能,支持单结果、多结果、Map 结果、游标(Cursor)等多种返回形式,核心差异在于结果处理方式:
方法签名 | 功能说明 | 适用场景 |
---|---|---|
<T> T selectOne(String statement) |
执行 SQL,返回单个结果(无参数) | 无参单结果查询(如 select count(*) ) |
<T> T selectOne(String statement, Object param) |
执行 SQL,传入参数,返回单个结果 | 有参单结果查询(如 selectById(1) ) |
<E> List<E> selectList(String statement) |
执行 SQL,返回结果列表(无参数) | 无参多结果查询(如 selectAll() ) |
<E> List<E> selectList(..., RowBounds rb) |
传入 RowBounds (起始索引 + 条数),实现逻辑分页 |
逻辑分页查询(不推荐生产,见前文) |
<K,V> Map<K,V> selectMap(..., String mapKey) |
将结果列表转为 Map,mapKey 为 Map 的键(需是结果中的某个属性) |
需快速通过某个属性查找结果(如按 id 查 User) |
<T> Cursor<T> selectCursor(...) |
返回游标对象,支持流式读取(避免一次性加载大量数据到内存) | 大数据量查询(如导出 10 万条数据) |
void select(..., ResultHandler handler) |
无返回值,结果由自定义 ResultHandler 处理(灵活控制结果封装) |
特殊结果处理(如直接写入文件) |
关键细节:
selectOne()
本质是调用selectList()
后取第一个元素,若结果集 size > 1 会抛出TooManyResultsException
;RowBounds
实现逻辑分页(先查全表再截取),生产环境需用物理分页(如 PageHelper);Cursor
是 MyBatis 3.4+ 新增功能,基于 JDBC 的ResultSet
流式读取,适合大数据量场景,避免 OOM。
(2)修改方法:insert/update/delete
MyBatis 将 insert、update、delete 统一归为 “修改操作”,底层均委托给 Executor.update()
方法,返回值为受影响的行数:
方法签名 | 功能说明 |
---|---|
int insert(String statement, Object param) |
执行插入操作,传入参数(如实体对象) |
int update(String statement, Object param) |
执行更新操作,传入参数(如含主键的实体对象) |
int delete(String statement, Object param) |
执行删除操作,传入参数(如主键) |
关键细节:
- 所有修改操作默认不会自动提交事务(需手动调用
commit()
),除非创建 SqlSession 时指定autoCommit=true
; - 插入操作若需返回自增主键,需在 Mapper 中配置
useGeneratedKeys=true
(如@Options(useGeneratedKeys=true, keyProperty="id")
)。
(3)事务方法:commit/rollback
SqlSession 直接封装事务控制逻辑,底层通过 Transaction
对象管理数据库事务:
方法签名 | 功能说明 |
---|---|
void commit() |
提交事务(仅当 autoCommit=false 时有效) |
void commit(boolean force) |
强制提交事务(即使 dirty=false ,也会触发提交,用于批量操作) |
void rollback() |
回滚事务(仅当 autoCommit=false 时有效) |
void rollback(boolean force) |
强制回滚事务(即使未发生修改,也会回滚,用于异常恢复) |
关键概念:dirty
标记
DefaultSqlSession 中有 private boolean dirty
属性,用于标记 “当前会话是否有未提交的修改”:
- 执行
insert/update/delete
后,dirty
会设为true
; - 调用
commit()
时,若dirty=false
且force=false
,会跳过提交(避免无意义的数据库交互); - 调用
rollback()
同理,dirty=false
且force=false
时跳过回滚。
(4)缓存与配置方法
SqlSession 管理 MyBatis 的一级缓存(会话级缓存),并提供配置查询能力:
方法签名 | 功能说明 |
---|---|
void clearCache() |
清空当前 SqlSession 的一级缓存(会话级缓存,仅当前会话有效) |
Configuration getConfiguration() |
获取 MyBatis 全局配置对象(包含数据源、Mapper 映射等所有配置) |
Connection getConnection() |
获取当前 SqlSession 对应的数据库连接(慎用,避免手动操作连接导致泄漏) |
(5)Mapper 接口绑定:getMapper ()
getMapper(Class<T> type)
是 SqlSession 与 Mapper 接口关联的核心方法,通过动态代理生成 Mapper 实现类,避免手动编写实现:
1 | // 示例:获取 UserMapper 代理对象 |
底层逻辑:委托 Configuration.getMapper()
→ MapperRegistry.getMapper()
,最终通过 MapperProxyFactory
生成 MapperProxy
动态代理对象(见前文 Mapper 代理章节)。
SqlSession 的创建者:SqlSessionFactory
SqlSession 不能直接 new 创建,需通过 SqlSessionFactory
工厂接口生成,MyBatis 提供默认实现 DefaultSqlSessionFactory
,核心逻辑是 “基于 Configuration 创建 SqlSession”。
1. DefaultSqlSessionFactory:默认工厂实现
DefaultSqlSessionFactory
持有 Configuration
对象,通过两种方式创建 SqlSession:从数据源获取连接(推荐)和用户提供连接(特殊场景)。
(1)方式一:从数据源创建(openSessionFromDataSource)
最常用的方式,通过 MyBatis 配置的数据源(DataSource)获取连接,自动管理事务:
1 | private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { |
关键参数解析:
- ExecutorType:执行器类型,决定 SQL 执行策略(见下表);
- TransactionIsolationLevel:事务隔离级别(如
READ_COMMITTED
、REPEATABLE_READ
),默认使用数据库全局隔离级别; - autoCommit:是否自动提交事务,默认
false
(MyBatis 手动管理事务)。
ExecutorType | 核心特点 | 适用场景 |
---|---|---|
SIMPLE |
默认类型,每次执行 SQL 都创建新的 PreparedStatement | 大多数单条 SQL 执行场景 |
REUSE |
复用 PreparedStatement(按 SQL 缓存) | 重复执行相同 SQL(如循环查询同一 SQL) |
BATCH |
批量执行 INSERT/UPDATE/DELETE,仅提交一次事务 | 批量操作(如批量插入 1000 条数据) |
(2)方式二:用户提供连接(openSessionFromConnection)
特殊场景下(如集成第三方事务管理器),用户手动提供数据库连接,SqlSession 基于该连接创建:
1 | private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) { |
注意:用户需手动管理连接的关闭(SqlSession 关闭时不会关闭该连接),避免连接泄漏。
2. SqlSessionFactory 的常用方法
SqlSessionFactory
提供多个 openSession()
重载方法,满足不同场景需求:
1 | // 1. 默认:ExecutorType.SIMPLE,默认隔离级别,autoCommit=false |
SqlSession 的默认实现:DefaultSqlSession
DefaultSqlSession
是 SqlSession 接口的核心实现,所有方法最终都委托给 Executor
执行,其核心属性和方法逻辑如下:
1. 核心属性
1 | public class DefaultSqlSession implements SqlSession { |
- cursorList:收集当前 SqlSession 创建的所有
Cursor
对象,在close()
时统一关闭,避免游标泄漏; - dirty:执行
insert/update/delete
后设为true
,commit()
/rollback()
后设为false
,用于优化事务操作(无修改时跳过提交)。
2. 核心方法逻辑拆解
(1)查询方法:selectOne ()
1 |
|
关键:wrapCollection(parameter)
用于处理集合类型参数(如 List、Array),将其封装为 Map
(键为 collection
/array
),便于后续 ParameterHandler 处理。
(2)修改方法:insert ()/update ()/delete ()
所有修改方法最终都委托给 update()
方法:
1 |
|
(3)事务方法:commit ()
1 |
|
(4)关闭方法:close ()
1 |
|
关键:executor.close()
会根据 autoCommit
和 dirty
决定是否提交事务(若 autoCommit=false
且 dirty=true
,会自动回滚未提交的事务,避免数据不一致)。
SqlSession 的线程安全增强:SqlSessionManager
SqlSessionManager
是 MyBatis 提供的线程安全的 SqlSession 管理类,同时实现 SqlSessionFactory
和 SqlSession
接口,解决了 DefaultSqlSession
线程不安全的问题(DefaultSqlSession
不能多线程共享)。
1. 核心设计:ThreadLocal 绑定 + 动态代理
SqlSessionManager
通过两个核心组件实现线程安全:
ThreadLocal<SqlSession> localSqlSession
:将 SqlSession 与当前线程绑定,确保同一线程复用同一个 SqlSession;SqlSession sqlSessionProxy
:通过 JDK 动态代理生成 SqlSession 代理对象,自动判断当前线程是否已绑定 SqlSession,决定复用或创建新对象。
核心属性
1 | public class SqlSessionManager implements SqlSessionFactory, SqlSession { |
2. 两种工作模式
SqlSessionManager
通过 SqlSessionInterceptor
(实现 InvocationHandler
)实现两种工作模式,逻辑在 invoke()
方法中:
(1)模式一:线程绑定模式(复用 SqlSession)
若当前线程已通过 startManagedSession()
绑定 SqlSession,则直接复用该对象,避免重复创建:
1 | // 绑定 SqlSession 到当前线程 |
使用示例:
1 | // 1. 创建 SqlSessionManager |
(2)模式二:自动创建模式(每次新对象)
若当前线程未绑定 SqlSession,则自动创建新的 SqlSession,使用 try-with-resources
确保自动关闭,避免泄漏:
1 | // 示例:直接调用 SqlSessionManager 的方法,无需手动绑定 |
3. 与 DefaultSqlSessionFactory 的区别
特性 | DefaultSqlSessionFactory | SqlSessionManager |
---|---|---|
线程安全 | 不安全(每次 openSession () 都创建新对象) | 安全(ThreadLocal 绑定线程,复用 SqlSession) |
事务管理 | 需手动管理 SqlSession 的 commit/close | 支持自动提交(模式二)和手动管理(模式一) |
性能 | 多线程频繁创建 / 关闭 SqlSession,性能损耗 | 同一线程复用 SqlSession,减少开销 |
适用场景 | 大多数 Spring 环境(Spring 管理 SqlSession) | 非 Spring 环境、多线程场景(如自定义线程池) |
SqlSession 的底层支撑:四大核心执行对象
SqlSession 本身不直接执行 SQL,而是通过 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四大对象协作完成,这四个对象被称为 MyBatis 的 “执行引擎”。
1. 四大对象的职责与协作流程
(1)Executor:执行器(“总指挥”)
- 职责:调度其他三个对象,负责 SQL 执行的整体流程(缓存查询、事务管理、SQL 调用);
- 核心方法:
query()
(查询)、update()
(修改)、commit()
(提交)、rollback()
(回滚); - 与 SqlSession 的关系:SqlSession 持有 Executor 对象,所有方法最终委托给 Executor 执行。
(2)StatementHandler:SQL 执行处理器(“执行者”)
- 职责:与数据库直接交互,创建 JDBC 的
Statement
/PreparedStatement
,执行 SQL 并返回ResultSet
; - 核心方法:
prepare()
(预编译 SQL)、parameterize()
(设置参数)、query()
/update()
(执行 SQL); - 创建逻辑:由 Executor 的
newStatementHandler()
方法创建,默认RoutingStatementHandler
(根据 SQL 类型选择具体实现)。
(3)ParameterHandler:参数处理器(“参数转换器”)
- 职责:将 Mapper 方法的参数(如
user.getId()
)转换为 JDBC 所需的参数(PreparedStatement.setXxx()
); - 核心方法:
setParameters()
(设置 SQL 占位符参数); - 创建逻辑:由 StatementHandler 初始化时创建,默认
DefaultParameterHandler
。
(4)ResultSetHandler:结果处理器(“结果转换器”)
- 职责:将 JDBC 的
ResultSet
解析为 Java 实体对象(或 List/Map),处理结果映射; - 核心方法:
handleResultSets()
(解析结果集)、handleOutputParameters()
(处理存储过程输出参数); - 创建逻辑:由 StatementHandler 初始化时创建,默认
DefaultResultSetHandler
。
2. 四大对象协作流程(以 selectOne 为例)
SqlSession 最佳实践
1. 生命周期管理:单次请求,及时关闭
生命周期:SqlSession 应与 “单次业务请求” 绑定,使用后立即关闭,避免长期占用连接;
关闭方式:优先使用try-with-resources(自动关闭),避免忘记关闭导致连接泄漏:
1
2
3
4
5
6
7
8// 推荐:try-with-resources 自动关闭 SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 业务逻辑
session.commit(); // 手动提交事务
} catch (Exception e) {
// 异常处理
}
2. 线程安全:禁止多线程共享 SqlSession
- DefaultSqlSession 线程不安全(无同步机制,
dirty
标记会被多线程篡改),每个线程应单独创建 SqlSession; - 若需多线程复用,使用
SqlSessionManager
的线程绑定模式,或依赖 Spring(Spring 会为每个线程注入独立的 SqlSession)。
3. 一级缓存:理解缓存范围与失效场景
- SqlSession 的一级缓存是 “会话级”,仅当前 SqlSession 有效,关闭后缓存清空;
- 缓存失效场景:执行
insert/update/delete
、调用clearCache()
、关闭 SqlSession; - 注意:一级缓存可能导致 “脏读”(如同一 SqlSession 中,其他线程修改了数据,当前会话仍读取缓存),若需禁用,可在 Mapper 中配置
flushCache="true"
。
4. 事务管理:手动提交 vs 自动提交
生产环境建议
autoCommit=false
(手动管理事务),避免部分修改成功后自动提交,导致数据不一致;批量操作(如批量插入)使用ExecutorType.BATCH,并在最后统一提交,减少事务开销:
1
2
3
4
5
6
7try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : userList) {
mapper.insertUser(user);
}
session.commit(); // 批量提交,仅一次事务
}
总结
SqlSession 是 MyBatis 会话管理的核心,其本质是 “数据库连接 + 执行器 + 配置” 的封装:
- 入口:通过
SqlSessionFactory
创建,支持从数据源或用户提供的连接生成; - 实现:
DefaultSqlSession
是默认实现,所有方法委托给Executor
执行,需注意线程安全; - 增强:
SqlSessionManager
通过 ThreadLocal 实现线程安全,支持同一线程复用 SqlSession; - 底层:依赖四大执行对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)完成 SQL 执行与结果处理
v1.3.10