为什么Mybatis DefaultSqlSession不是线程安全的
首先在DefaultSqlSession
的源码中明确说了不是线程安全的:
/**
*
* The default implementation for {@link SqlSession}.
* Note that this class is not Thread-Safe.
*
* @author Clinton Begin
*/
我的理解主要是两方面:
- 首先由于JDBC的Connection对象本身不是线程安全的,而session中又只有一个connection,所以不是线程安全的
- 一级缓存
由于一级缓存是session级别的,所以如果多个线程同时使用session,当线程A进行了插入操作未完成,但是此时线程B进行查询并缓存了数据,这是就出现了一级缓存与数据库数据不一致的问题。
对于Sessioin与Connection的关系,有如下代码过程追踪:
1、通过SessionFactory创建session
SqlSession sqlSession = sqlSessionFactory.openSession();
2、可以追踪代码到org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 这里创建事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 这里将事务传递给执行器Executor,这个是session执行数据库操作的核心
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
3、当session执行操作时是通过executor来进行的,继续追踪Executor,在SimpleExecutor
中最终创建了Statement
这个JDBC对象,而这个对象是要通过connection创建的。
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 这里创建statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
4、继续prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 看下面的方法
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
protected Connection getConnection(Log statementLog) throws SQLException {
// 这里最终同通过创建Executor时传入的transcation进行了连接获取
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
5、继续看transcation的getConnection
@Override
public Connection getConnection() throws SQLException {
// 这里只要有连接了就不重新打开连接了(从数据源中再次获取),说明只能有一个连接在一个org.apache.ibatis.transaction.Transaction中
if (connection == null) {
openConnection();
}
return connection;
}
至此可以看到一次SqlSession的执行最终是通过是通过事务获取了连接,而一个session中只能有一个事务,一个事务又只能有一个连接,所以一个session中只有一个connection。
以上代码追踪证实由于Connection的线程不安全,所以SqlSession也是线程不安全的。