SqlSession是MyBatis核心接口之一,也是MyBatis接口層的主要組成部分,對外提供MyBatis常用的API。mybatis提供了兩個SqlSession接口的實作,分别為DefaultSqlSession、SqlSessionManager,其中最常用的是DefaultSqlSession。另外,跟前面分析過的源碼mybatis的源碼一樣,mybatis也為SqlSession提供了相應的工廠接口SqlSessionFactory,及實作該接口的實作DefaultSqlSessionFactory(SqlSessionManager同時實作了SqlSession和SqlSessionFactory接口)。
1 SqlSession
在SqlSession中定義了常用的資料庫操作以及事務的相關操作,為了友善使用者使用,每種類型的操作都提供了多種重載。
public interface SqlSession extends Closeable {
// 泛型方法,參數是要執行查詢的sql語句,傳回值為查詢的結果對象
<T> T selectOne(String statement);
// 第二個參數表示 需要使用者傳入的實參,即 sql語句綁定的實參
<T> T selectOne(String statement, Object parameter);
// 查詢結果有多條記錄,會封裝成 結果對象清單 并傳回
<E> List<E> selectList(String statement);
// 參數 + 多記錄結果集
<E> List<E> selectList(String statement, Object parameter);
// 參數RowBounds主要用于邏輯分頁,邏輯分頁會将所有的結果都查詢到,
// 然後根據RowBounds中提供的offset和limit值來擷取最後的結果
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
// mapKey表示将結果集中的哪一列(如 主鍵列或編碼列)作為Map的key,
// value則為列值 對應的那條記錄
<K, V> Map<K, V> selectMap(String statement, String mapKey);
// 多了個parameter參數,其它與上面相同
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
// 多了個RowBounds參數,其它與上面相同
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
// 除了傳回值是Cursor對象,其它與selectList相同
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
// 查詢出的結果集 将由傳入的ResultHandler對象處理,其它與selectList相同
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
// 執行insert語句
int insert(String statement);
int insert(String statement, Object parameter);
// 執行update語句
int update(String statement);
int update(String statement, Object parameter);
// 執行delete語句
int delete(String statement);
int delete(String statement, Object parameter);
// 送出事務
void commit();
void commit(boolean force);
// 復原事務
void rollback();
void rollback(boolean force);
// 将對資料庫的操作請求 刷到資料庫
List<BatchResult> flushStatements();
// 關閉目前session
void close();
// 清空緩存
void clearCache();
// 擷取Configuration對象
Configuration getConfiguration();
// 擷取type對應的Mapper對象
<T> T getMapper(Class<T> type);
// 擷取該SqlSession對應的資料庫連接配接
Connection getConnection();
}
1.1 DefaultSqlSession
DefaultSqlSession是單獨使用MyBatis進行開發時,最常用的SqISession接口實作。其實作了SqISession接口中定義的方法,及各方法的重載。select()系列方法、selectOne()系列方法、selectList()系列方法、selectMap()系列方法之間的調用關系如下圖,殊途同歸,它們最終都會調用Executor的query()方法。
上述重載方法最終都是通過調用Executor的query(MappedStatement, Object, RowBounds,ResultHandler)方法實作資料庫查詢操作的,但各自對結果對象進行了相應的調整,例如:selectOne()方法是從結果對象集合中擷取了第一個元素傳回;selectMap()方法會将List類型的結果集 轉換成Map類型集合傳回;select()方法是将結果集交由使用者指定的ResultHandler對象處理,且沒有傳回值;selectList()方法則是直接傳回結果對象集合。
DefaultSqlSession的insert()方法、update()方法、delete()方法也有多個重載,它們最後都是通過調用DefaultSqlSession的update(String, Object)方法實作的,該重載首先會将dirty字段置為true,然後再通過Executor的update()方法完成資料庫修改操作。
DefaultSqlSession的commit()方法、rollback()方法以及close()方法都會調用Executor中相應的方法,其中就會涉及清空緩存的操作,之後就會将dirty字段設定為false。
上述的dirty字段主要在isCommitOrRollbackRequired()方法中,與autoCommit字段以及使用者傳入的force參數共同決定是否送出/復原事務。該方法的傳回值将作為Executor的commit()方法和rollback()方法的參數。
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
2 SqlSessionFactory
SqlSessionFactory負責建立SqlSession對象,其中包含了多個openSession()方法的重載,可以通過其參數指定事務的隔離級别、底層使用Executor的類型、以及是否自動送出事務等方面的配置。
public interface SqlSessionFactory {
// 提供了openSession()方法的多種重載,根據相應的參數 可以指定事務的隔離級别、
// 底層使用的Executor類型、以及是否自動送出事務等配置
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
2.1 DefaultSqlSessionFactory
DefaultSqlSessionFactory是SqlSessionFactory接口的預設實作,主要提供了兩種建立DefaultSqlSession對象的方式,一種方式是通過資料源擷取資料庫連接配接,并建立Executor對象以及DefaultSqlSession對象;另一種方式是使用者提供資料庫連接配接對象,DefaultSqlSessionFactory根據該資料庫連接配接對象擷取autoCommit屬性,建立Executor對象以及DefaultSqlSession對象。
DefaultSqISessionFactory提供的所有openSession()方法重載都是基于上述兩種方式建立DefaultSqlSession對象的。
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 擷取配置的Environment對象
final Environment environment = configuration.getEnvironment();
// 從environment中擷取TransactionFactory對象,如果沒有,就建立一個ManagedTransactionFactory執行個體并傳回
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 從事務工廠中擷取一個事務對象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根據事務對象tx和配置的Executor類型execType建立Executor執行個體
// ExecutorType是個枚舉類型,有三個值 SIMPLE, REUSE, BATCH,分别對應了
// SimpleExecutor、ReuseExecutor、BatchExecutor
final Executor executor = configuration.newExecutor(tx, execType);
// 建立DefaultSqlSession對象
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();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
// 根據目前連接配接對象擷取autoCommit屬性(是否自動送出事務)
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
autoCommit = true;
}
// 除了擷取autoCommit屬性的方式和上面不一樣外,下面的處理都與上面完全相同
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
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();
}
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
private void closeTransaction(Transaction tx) {
if (tx != null) {
try {
tx.close();
} catch (SQLException ignore) {
// Intentionally ignore. Prefer previous error.
}
}
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return openSessionFromDataSource(execType, null, autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return openSessionFromConnection(execType, connection);
}
@Override
public Configuration getConfiguration() {
return configuration;
}
}
2.2 SqlSessionManager
SqlSessionManager同時實作了SqlSession接口和SqlSessionFactory接口,是以同時提供了SqlSessionFactory建立SqlSession對象,以及SqlSession操縱資料庫的功能。
SqlSessionManager與DefaultSqlSessionFactory的主要不同點SqlSessionManager 提供了兩種模式,第一種模式與DefaultSqlSessionFactory的行為相同,同一線程每次通過SqlSessionManager對象通路資料庫時,都會建立新的SqlSession對象完成資料庫操作。第二種模式是SqlSessionManager通過localSqlSession這ThreadLocal 變量,記錄與目前線程綁定的SqlSession對象,供目前線程循環使用,進而避免在同一線程多次建立SqlSession對象帶來的性能損失。
SqlSessionManager的構造方法是唯一且私有的,如果要建立SqlSessionManager對象,需要調用其newInstance()方法(但需要注意的是,這不是單例模式,因為每次調用newInstance()方法都傳回了一個新的對象)。
SqlSessionManager的openSession()系列方法,都是通過直接調用其持有的
DefaultSqlSessionFactory執行個體來實作的。
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
// 通過持有DefaultSqlSessionFactory對象 來産生SqlSession對象
private final SqlSessionFactory sqlSessionFactory;
// 用于記錄一個與目前線程綁定的SqlSession對象
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
// localSqlSession中記錄的SqlSession對象的代理對象(JDK動态代理)
// SqlSessionManager初始化時 生成本代理對象,可以看下 下面的構造函數
private final SqlSession sqlSessionProxy;
// 私有的構造函數,也是SqlSessionManager唯一的構造函數
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
// 傳入的這個SqlSessionFactory對象 往往是DefaultSqlSessionFactory的執行個體
this.sqlSessionFactory = sqlSessionFactory;
// JDK動态代理生成代理對象,可以看得出,SqlSessionInterceptor一定實作了
// InvocationHandler接口
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
// 通過newInstance()方法建立SqlSessionManager對象,有多種重載,
// 但最後都是new了一個DefaultSqlSessionFactory的執行個體
public static SqlSessionManager newInstance(Reader reader) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null));
}
public static SqlSessionManager newInstance(Reader reader, String environment) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, environment, null));
}
public static SqlSessionManager newInstance(Reader reader, Properties properties) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, properties));
}
public static SqlSessionManager newInstance(InputStream inputStream) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, null));
}
public static SqlSessionManager newInstance(InputStream inputStream, String environment) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, environment, null));
}
public static SqlSessionManager newInstance(InputStream inputStream, Properties properties) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, properties));
}
public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionManager(sqlSessionFactory);
}
// openSession()系列方法都是通過目前SqlSessionManager對象持有的
// DefaultSqlSessionFactory執行個體的openSession()實作的
@Override
public SqlSession openSession() {
return sqlSessionFactory.openSession();
}
@Override
public SqlSession openSession(boolean autoCommit) {
return sqlSessionFactory.openSession(autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return sqlSessionFactory.openSession(connection);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return sqlSessionFactory.openSession(level);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return sqlSessionFactory.openSession(execType);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return sqlSessionFactory.openSession(execType, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return sqlSessionFactory.openSession(execType, level);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return sqlSessionFactory.openSession(execType, connection);
}
}
SqlSessionManager中實作的SqlSession接口方法,例如select ()系列方法、update()系列方法等,都是直接調用sqlSessionProxy代理對象對應的方法實作的。在建立該代理對象時使用的InvocationHandler對象是SqlSessionlnterceptor,它是SqISessionManager的内部類。
private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() { }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 擷取 與目前線程綁定的SqlSession
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
// 如果有綁定的SqlSession對象
if (sqlSession != null) { // 模式二
try {
// 調用真正的sqlSession對象,完成資料庫操作
return method.invoke(sqlSession, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 如果沒有綁定的SqlSession對象
} else { // 模式一
// 建立一個新的SqlSession對象
final SqlSession autoSqlSession = openSession();
try {
// 通過反射調用該SqlSession對象的方法,完成資料庫操作
final Object result = method.invoke(autoSqlSession, args);
// 送出事務
autoSqlSession.commit();
return result;
} catch (Throwable t) {
// 出異常就復原
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(t);
} finally {
// 關閉該SqlSession對象
autoSqlSession.close();
}
}
}
}
通過對SqlSessionlnterceptor的分析可知,第一種模式中建立的SqlSession在使用完成後會立即關閉。在第二種模式中,與目前線程綁定的SqISession對象需要先通過SqlSessionManager的startManagedSession()方法進行設定,此方法也存在多種重載,但都彼此相似 且簡單。
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
public void startManagedSession(boolean autoCommit) {
this.localSqlSession.set(openSession(autoCommit));
}
public void startManagedSession(Connection connection) {
this.localSqlSession.set(openSession(connection));
}
public void startManagedSession(TransactionIsolationLevel level) {
this.localSqlSession.set(openSession(level));
}
public void startManagedSession(ExecutorType execType) {
this.localSqlSession.set(openSession(execType));
}
public void startManagedSession(ExecutorType execType, boolean autoCommit) {
this.localSqlSession.set(openSession(execType, autoCommit));
}
public void startManagedSession(ExecutorType execType, TransactionIsolationLevel level) {
this.localSqlSession.set(openSession(execType, level));
}
public void startManagedSession(ExecutorType execType, Connection connection) {
this.localSqlSession.set(openSession(execType, connection));
}
public boolean isManagedSessionStarted() {
return this.localSqlSession.get() != null;
}
當需要送出/復原事務,或關閉IocalSqlSession中記錄的SqlSession對象時,需要通過SqlSessionManager的commit()、rollback()以及close()方法完成,其中會先檢測目前線程是否綁定了SqlSession對象,如果未綁定則抛出異常,如果綁定了則調用該SqlSession對象的相應方法。
@Override
public void clearCache() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot clear the cache. No managed session is started.");
}
sqlSession.clearCache();
}
@Override
public void commit() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot commit. No managed session is started.");
}
sqlSession.commit();
}
@Override
public void commit(boolean force) {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot commit. No managed session is started.");
}
sqlSession.commit(force);
}
@Override
public void rollback() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot rollback. No managed session is started.");
}
sqlSession.rollback();
}
@Override
public void rollback(boolean force) {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot rollback. No managed session is started.");
}
sqlSession.rollback(force);
}
@Override
public List<BatchResult> flushStatements() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot rollback. No managed session is started.");
}
return sqlSession.flushStatements();
}
@Override
public void close() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot close. No managed session is started.");
}
try {
sqlSession.close();
} finally {
localSqlSession.set(null);
}
}