為什么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押赊,當(dāng)線程A進行了插入操作未完成瓶埋,但是此時線程B進行查詢并緩存了數(shù)據(jù)纲仍,這是就出現(xiàn)了一級緩存與數(shù)據(jù)庫數(shù)據(jù)不一致的問題耻矮。
對于Sessioin與Connection的關(guān)系果正,有如下代碼過程追蹤:
1炎码、通過SessionFactory創(chuàng)建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);
// 這里創(chuàng)建事務(wù)
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 這里將事務(wù)傳遞給執(zhí)行器Executor秋泳,這個是session執(zhí)行數(shù)據(jù)庫操作的核心
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潦闲、當(dāng)session執(zhí)行操作時是通過executor來進行的,繼續(xù)追蹤Executor迫皱,在SimpleExecutor
中最終創(chuàng)建了Statement
這個JDBC對象歉闰,而這個對象是要通過connection創(chuàng)建的。
@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);
// 這里創(chuàng)建statement對象
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
4卓起、繼續(xù)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 {
// 這里最終同通過創(chuàng)建Executor時傳入的transcation進行了連接獲取
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
5和敬、繼續(xù)看transcation的getConnection
@Override
public Connection getConnection() throws SQLException {
// 這里只要有連接了就不重新打開連接了(從數(shù)據(jù)源中再次獲取)戏阅,說明只能有一個連接在一個org.apache.ibatis.transaction.Transaction中
if (connection == null) {
openConnection();
}
return connection;
}
至此可以看到一次SqlSession的執(zhí)行最終是通過是通過事務(wù)獲取了連接昼弟,而一個session中只能有一個事務(wù),一個事務(wù)又只能有一個連接奕筐,所以一個session中只有一個connection舱痘。
以上代碼追蹤證實由于Connection的線程不安全,所以SqlSession也是線程不安全的离赫。