mybatis之SqlSession
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 实现类,避免手动编写实现:
// 示例:获取 UserMapper 代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(1);
底层逻辑:委托 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)获取连接,自动管理事务:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 1. 从 Configuration 中获取数据库环境(Environment:数据源 + 事务工厂)
final Environment environment = configuration.getEnvironment();
// 2. 创建事务工厂(TransactionFactory),默认 JdbcTransactionFactory
final TransactionFactory txFactory = getTransactionFactoryFromEnvironment(environment);
// 3. 创建 Transaction 对象(封装数据库连接和事务隔离级别)
tx = txFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 4. 创建 Executor 执行器(核心执行组件,按 execType 选择类型)
final Executor executor = configuration.newExecutor(tx, execType);
// 5. 创建 DefaultSqlSession(持有 Configuration、Executor、autoCommit)
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // 异常时关闭事务,避免连接泄漏
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
关键参数解析:
- 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 基于该连接创建:
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
// 1. 获取连接的自动提交状态(默认 true,若连接已设置则沿用)
boolean autoCommit = connection.getAutoCommit();
// 2. 创建 Transaction(基于用户提供的连接,不自动管理连接生命周期)
final Environment environment = configuration.getEnvironment();
final TransactionFactory txFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = txFactory.newTransaction(connection);
// 3. 创建 Executor 和 DefaultSqlSession
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
注意:用户需手动管理连接的关闭(SqlSession 关闭时不会关闭该连接),避免连接泄漏。
2. SqlSessionFactory 的常用方法
SqlSessionFactory 提供多个 openSession() 重载方法,满足不同场景需求:
// 1. 默认:ExecutorType.SIMPLE,默认隔离级别,autoCommit=false
SqlSession openSession();
// 2. 指定是否自动提交
SqlSession openSession(boolean autoCommit);
// 3. 指定 ExecutorType(如批量执行)
SqlSession openSession(ExecutorType execType);
// 4. 指定事务隔离级别
SqlSession openSession(TransactionIsolationLevel level);
// 5. 指定 ExecutorType 和自动提交
SqlSession openSession(ExecutorType execType, boolean autoCommit);
// 6. 指定 ExecutorType 和隔离级别
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
// 7. 基于用户提供的连接创建
SqlSession openSession(Connection connection);
SqlSession 的默认实现:DefaultSqlSession
DefaultSqlSession 是 SqlSession 接口的核心实现,所有方法最终都委托给 Executor 执行,其核心属性和方法逻辑如下:
1. 核心属性
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration; // 全局配置对象(唯一)
private final Executor executor; // 执行器(SQL 执行核心)
private final boolean autoCommit; // 是否自动提交事务
private boolean dirty; // 是否有未提交的修改(脏数据标记)
private List<Cursor<?>> cursorList; // 管理游标对象,关闭时统一释放
}
- cursorList:收集当前 SqlSession 创建的所有
Cursor对象,在close()时统一关闭,避免游标泄漏; - dirty:执行
insert/update/delete后设为true,commit()/rollback()后设为false,用于优化事务操作(无修改时跳过提交)。
2. 核心方法逻辑拆解
(1)查询方法:selectOne ()
public <T> T selectOne(String statement, Object parameter) {
// 1. 调用 selectList() 获取结果列表
List<T> list = this.selectList(statement, parameter);
// 2. 结果列表 size 为 1 则返回第一个元素,否则抛出异常
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null; // 无结果返回 null
}
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 1. 从 Configuration 中获取 MappedStatement(封装 SQL 元信息)
MappedStatement ms = configuration.getMappedStatement(statement);
// 2. 委托 Executor.query() 执行查询,传入 ResultHandler(默认 null)
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
关键:wrapCollection(parameter) 用于处理集合类型参数(如 List、Array),将其封装为 Map(键为 collection/array),便于后续 ParameterHandler 处理。
(2)修改方法:insert ()/update ()/delete ()
所有修改方法最终都委托给 update() 方法:
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
public int update(String statement, Object parameter) {
try {
dirty = true; // 标记为有未提交修改
MappedStatement ms = configuration.getMappedStatement(statement);
// 委托 Executor.update() 执行修改操作
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
(3)事务方法:commit ()
public void commit() {
commit(false); // 默认不强制提交
}
public void commit(boolean force) {
try {
// 1. 委托 Executor 提交事务
executor.commit(isCommitOrRollbackRequired(force));
dirty = false; // 提交后重置脏数据标记
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 判断是否需要提交:force=true 或 dirty=true(有未提交修改)
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
(4)关闭方法:close ()
public void close() {
try {
// 1. 关闭 Executor(释放连接、关闭游标、提交/回滚事务)
executor.close(isCommitOrRollbackRequired(false));
// 2. 统一关闭所有游标
closeCursors();
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
private void closeCursors() {
if (cursorList != null && !cursorList.isEmpty()) {
for (Cursor<?> cursor : cursorList) {
try {
cursor.close();
} catch (IOException e) {
throw ExceptionFactory.wrapException("Error closing cursor. Cause: " + e, e);
}
}
cursorList.clear();
}
}
关键: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,决定复用或创建新对象。
核心属性
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory; // 底层依赖的工厂
private final SqlSession sqlSessionProxy; // SqlSession 动态代理
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>(); // 线程绑定的 SqlSession
}
2. 两种工作模式
SqlSessionManager 通过 SqlSessionInterceptor(实现 InvocationHandler)实现两种工作模式,逻辑在 invoke() 方法中:
(1)模式一:线程绑定模式(复用 SqlSession)
若当前线程已通过 startManagedSession() 绑定 SqlSession,则直接复用该对象,避免重复创建:
// 绑定 SqlSession 到当前线程
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
// 代理逻辑:优先使用线程绑定的 SqlSession
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 从 ThreadLocal 中获取当前线程绑定的 SqlSession
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
// 2. 复用 SqlSession,调用对应方法
return method.invoke(sqlSession, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} else {
// 模式二:创建新 SqlSession(见下文)
try (SqlSession autoSqlSession = openSession()) {
try {
final Object result = method.invoke(autoSqlSession, args);
autoSqlSession.commit();
return result;
} catch (Throwable t) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
}
}
使用示例:
// 1. 创建 SqlSessionManager
SqlSessionManager manager = SqlSessionManager.newInstance(sqlSessionFactory);
// 2. 绑定 SqlSession 到当前线程(当前线程后续复用该对象)
manager.startManagedSession();
try {
// 3. 多次获取 SqlSession,实际是同一个对象
UserMapper mapper1 = manager.getMapper(UserMapper.class);
UserMapper mapper2 = manager.getMapper(UserMapper.class);
mapper1.selectById(1);
mapper2.updateUser(user);
// 4. 手动提交事务
manager.commit();
} finally {
// 5. 关闭 SqlSession 并从线程解绑
manager.close();
manager.localSqlSession.remove();
}
(2)模式二:自动创建模式(每次新对象)
若当前线程未绑定 SqlSession,则自动创建新的 SqlSession,使用 try-with-resources 确保自动关闭,避免泄漏:
// 示例:直接调用 SqlSessionManager 的方法,无需手动绑定
try (SqlSessionManager manager = SqlSessionManager.newInstance(sqlSessionFactory)) {
UserMapper mapper = manager.getMapper(UserMapper.class);
User user = mapper.selectById(1);
// 自动提交事务(代理逻辑中会调用 autoSqlSession.commit())
}
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(自动关闭),避免忘记关闭导致连接泄漏:
// 推荐: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,并在最后统一提交,减少事务开销:
try (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 执行与结果处理