SqlSession 生命周期
SqlSession 是 MyBatis 的核心操作接口,理解其生命周期对正确使用连接池、事务管理至关重要。
生命周期概览
Java
创建 执行 提交/回滚 关闭
| | | |
v v v v
openSession() -> select/insert -> commit()/rollback() -> close()
↑ ↑ ↑ ↑
获取Connection 获取Statement 事务边界 释放Connection
SqlSession 创建过程
openSession 方法族
Java
public class DefaultSqlSessionFactory implements SqlSessionFactory {
@Override
public SqlSession openSession() {
// 使用默认配置:自动提交=false,从数据源获取连接
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(Connection connection) {
// 使用外部传入的连接,不管理连接生命周期
return openSessionFromConnection(configuration.getDefaultExecutorType(), false);
}
}
从 DataSource 创建 Session
Java
private SqlSession openSessionFromDataSource(ExecutorType execType,
TransactionIsolationLevel level,
boolean autoCommit) {
Transaction tx = null;
try {
// 1. 获取 Environment 配置
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 2. 从数据源获取连接
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 3. 创建 Executor(应用插件拦截)
final Executor executor = configuration.newExecutor(tx, execType);
// 4. 包装为 DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // 发生异常时关闭事务
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
关键点:创建 SqlSession 时获取数据库连接,但连接的实际获取是延迟到第一次执行 SQL 时(由数据源连接池控制)。
SqlSession 内部结构
Java
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration; // 全局配置(线程安全)
private final Executor executor; // 执行器(非线程安全)
private final boolean autoCommit; // 是否自动提交
private boolean dirty; // 是否有未提交的修改
private List<Cursor<?>> cursorList; // 持有的游标列表
}
| 字段 | 线程安全 | 说明 |
|---|---|---|
| configuration | 是 | 全局单例,只读配置 |
| executor | 否 | 持有 Connection,包含状态 |
| autoCommit | 是 | 创建时确定,不变 |
| dirty | 否 | 标记是否有写操作未提交 |
| cursorList | 否 | 管理查询游标生命周期 |
执行方法
查询操作
Java
@Override
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result but found " + list.size());
}
return null;
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 通过 MappedStatement ID 获取 SQL 定义
MappedStatement ms = configuration.getMappedStatement(statement);
// 委托给 Executor 执行
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();
}
}
更新操作
Java
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true; // 标记为 dirty,表示有未提交的修改
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
dirty 标记作用:执行 insert/update/delete 后设置 dirty=true,commit 时根据此标记判断是否需要实际提交事务。
事务提交与回滚
commit 流程
Java
@Override
public void commit(boolean force) {
try {
// force=true 强制提交,dirty=true 有修改时提交
executor.commit(isCommitOrRollbackRequired(force));
dirty = false; // 重置 dirty 标记
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private boolean isCommitOrRollbackRequired(boolean force) {
// autoCommit=true 时需要手动提交
// dirty=true 且有 force 标记时需要提交
return (!autoCommit && dirty) || force;
}
Executor.commit 内部
Java
public void commit(boolean required) throws SQLException {
if (closed) throw new ExecutorException("Cannot commit, session is closed.");
// 1. 清理本地缓存(防止脏读)
clearLocalCache();
// 2. 刷新批量执行语句
flushStatements();
// 3. 提交事务
if (required) {
transaction.commit();
}
}
rollback 流程
Java
@Override
public void rollback(boolean force) {
try {
executor.rollback(isCommitOrRollbackRequired(force));
dirty = false; // 重置 dirty 标记
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error rolling back transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
close 流程
Java
@Override
public void close() {
try {
// 1. 关闭所有持有的 Cursor
for (Cursor<?> cursor : cursorList) {
cursor.close();
}
cursorList.clear();
// 2. 关闭 Executor
executor.close(isCommitOrRollbackRequired(false));
// 3. 重置 dirty
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
Executor.close 内部
Java
public void close(boolean forceRollback) {
try {
// 1. 回滚未提交的修改
if (forceRollback) {
rollback(true);
}
// 2. 清理缓存
clearLocalCache();
// 3. 关闭事务,释放连接回连接池
transaction.close();
} finally {
this.closed = true;
}
}
重要:close() 不是简单的资源释放,如果存在未提交的修改且 autoCommit=false,close 会触发隐式回滚,防止数据不一致。
线程安全性分析
为什么 SqlSession 不是线程安全的
Java
线程A 线程B
| |
|--executor.query()---------->|
| |--executor.update()---|
| | |
|<--返回结果 | |
| <-------|--共享同一 Connection |
| 事务被意外提交/回滚 |
| 查询结果不可预期 |
根本原因:
- Connection 共享:SqlSession 持有的 Connection 在同一时刻只能被一个线程使用
- 事务状态共享:dirty、autoCommit 等状态会被多线程并发修改
- Executor 状态:LocalCache(一级缓存)会被多线程互相污染
正确使用方式
text
// ❌ 错误:多线程共享 SqlSession
SqlSession session = factory.openSession();
new Thread(() -> session.selectList("findAllUsers")).start();
new Thread(() -> session.insert("addUser", user)).start();
// ✅ 正确:每个线程独立 SqlSession
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
new Thread(() -> {
try { session1.selectList("findAllUsers"); }
finally { session1.close(); }
}).start();
new Thread(() -> {
try { session2.insert("addUser", user); session2.commit(); }
finally { session2.close(); }
}).start();
MyBatis-Spring 中的线程安全处理
text
// SqlSessionTemplate 使用动态代理 + ThreadLocal 保证线程安全
public class SqlSessionTemplate implements SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy; // 动态代理
public SqlSessionTemplate(SqlSessionFactory factory) {
this.sqlSessionFactory = factory;
this.executorType = factory.getConfiguration().getDefaultExecutorType();
// 创建代理,每次方法调用都获取新的 SqlSession
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 每次调用都获取当前线程的 SqlSession
SqlSession session = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType);
return method.invoke(session, args);
}
}
}
连接管理时序图
text
应用代码 SqlSession Executor Transaction DataSource
| | | | |
|--openSession()------>| | | |
| |--newTransaction-->| | |
| | |--newTransaction()->| |
| | | |--getConnection()->|
| | | |<--Connection------|
| |<------------------|<------------------| |
|<--session------------| | | |
| | | | |
|--selectList()------->| | | |
| |--query()--------->| | |
| | |--执行SQL---------->| |
| | |<--结果-------------| |
| |<--List------------| | |
| | | | |
|--commit()----------->| | | |
| |--commit()------->| | |
| | |--commit()-------->| |
| | | |--commit()------->|
| |<------------------|<------------------|<-----------------|
| | | | |
|--close()------------>| | | |
| |--close()-------->| | |
| | |--close()-------->| |
| | | |--close()-------->|
| | | | (返回连接池) |
要点总结
- 生命周期四阶段:创建(openSession)→ 执行(select/insert/update/delete)→ 提交(commit/rollback)→ 关闭(close)
- 非线程安全:SqlSession 内部持有状态的 Executor 和 Connection,多线程共享会导致数据不一致
- dirty 标记机制:执行写操作时设置,commit/rollback/close 时根据此标记判断是否需要实际提交或回滚
- 隐式回滚:close() 时如果有未提交的修改且 autoCommit=false,会自动回滚,防止数据不一致
- 一级缓存清理:commit 和 close 时都会清理 localCache,防止脏读
- Spring 集成方案:SqlSessionTemplate 使用动态代理 + ThreadLocal 实现线程安全的 SqlSession 代理
📝 发现内容有误?点击此处直接编辑