前言
上篇文章講到了SqlSessionFactory的創建,這篇文章繼續對Mybatis的會話進行解析,那什麼是會話?
會話就是客戶端與數據庫建立的對話,一個Client和一個Database必須有一個會話,才能相互之間進行數據交互,它在Mybatis裏面對應的是SqlSession。
那麼哪些事情是和會話相關的?
- 數據庫的增刪查改
- 數據庫鏈接
- 數據庫事務
- 數據緩存
Mybatis是怎麼去實現上這些功能的?那麼接下來我們,就從代碼的角度去分析SqlSession
創建SqlSession
sqlSession操作數據庫流程
DefaultSqlSessionFactory通過openSession()方法創建DefaultSqlSession實例
public class DefaultSqlSessionFactory implements SqlSessionFactory {
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//1.獲取Environment配置
final Environment environment = configuration.getEnvironment();
//2.獲取TransactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//3.從configuration裏面拿到Executor
final Executor executor = configuration.newExecutor(tx, execType);
//4.實例化SqlSession
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 TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
}
上面的代碼做了4個動作:
- 獲取Environment配置
- 獲取TransactionFactory
- 從configuration裏面拿到Executor
- 實例化SqlSession
1.獲取Environment配置
通過configuration.getEnvironment()是從配置文件裏拿到Environment對象
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
}
Environment對象裏面有三個字段成員,分別爲id,transactionFactory,dataSource。
id是environment節點的名字,transactionFactory是用來創建事務的工廠,dataSource是數據庫的鏈接,這3個成員在下圖中都能找到對應的配置我們再回想一下,SqlSessionFactoryBuilder中的有個build()方法的重載,其中有個需要傳入environment的參數的,它的作用就是指定這個environment的id(Mybatis是可以配置多個environment,如果有配置多個則需要指定使用的是哪個)
2.獲取TransactionFactory
我們再看getTransactionFactoryFromEnvironment(environment)方法
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
“environment==null || environment.getTransactionFactory() == null” 用來判斷用戶配置TransactionFactory是否爲空,如果空的話返回默認的
public class ManagedTransactionFactory implements TransactionFactory {
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new ManagedTransaction(ds, level, closeConnection);
}
}
ManagedTransactionFactory.ManagedTransactionFactory對象創建的是ManagedTransaction事務,這個事務其實就是沒有事務,裏面並沒有commit和rolback實現(所以如果)
3.從configuration裏面拿到Executor
從configuration中拿到Executor對象(configuration其實也算是個工廠)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
Executor是執行器,是Mybatis真正用來執行操作的類,executorType參數是Executor的類型,從外面傳進來,如果用戶沒有指定是哪類執行器,SqlSessionFactory會從configuration.getDefaultExecutorType()中拿到默認的(Simple類型)
Executor有4種類型:
- BatchExecutor可批量操作的
- ReuseExecutor可重複使用的
- SimpleExecutor默認的
- CachingExecutor帶有二級緩存的
newExecutor()方法通過傳進來的executorType去實例化對應的Executor,如果配置了cacheEnabled=true的,還會將 實例化後的executor再包裝一下CachingExecutor。
4. 實例化SqlSession
SqlSession的創建是通過"new DefaultSqlSession(configuration, executor, autoCommit)"去實例化出來的,這裏傳了三個參數,分別爲configuration配置,executor執行器和autoCommit是否自動提交
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
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();
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();
}
從上面定義的接口上看,我們可以把它歸爲4類型:
- 數據庫的增刪查改操作
- 事務的提交和回滾
- 數據連接的關閉
- 緩存清理
接下來我們再看DefaultSqlSession的字段成員和構造函數
public class DefaultSqlSession implements SqlSession {
//配置信息
private final Configuration configuration;
//執行器
private final Executor executor;
//是否自動提交
private final boolean autoCommit;
//是否變髒
private boolean dirty;
//遊標(作用是可以用來一條一條的查詢結果集)
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
}
DefaultSqlSession 有5個成員變量,其中configuration,executor,autoCommit都是從外面傳進來的,dirty的初始值爲false,是用來記錄sqlSession是否有數據未做提交(insert和update之後會改成true,然後 comit,rollback或close之後都會重新把它改爲false),cursorList是遊標的集合,DefaultSqlSession只有在執行selectCursor時纔會進行對它初始化(遊標是用於遍歷結果集的,每次只查一條數據).
接下來我們來看selectOne方法
@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 (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
可以看到selectOne其實是特殊的selectList()方法,它會對結果集的條數作判斷,如果超過了一條會拋出異常,只有條數等於1的情況纔拿出第一條數據返回。(所以使用selectOne一定要保證查詢語句只會查出一條數據,如果有多條數據,Mybatis不會幫你做處理的)
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
上面的只列出了select()方法的兩個重載,一個有ResultHandler和一個沒有ResultHandler的,區別帶ResultHandler的是沒有返回參數,它需要通過ResultHandler去處理結果集, select()方法首先時獲取MappedStatement,MappedStatement是執行操作的內容的模板,Mybatis在通過SqlSessionFactoryBuider創建SqlSessionFactory時,就已經把所有的Mapper配置轉成MappedStatement並緩存到了Configuration了(我在上篇博客有介紹MappedStatement初始化的過程)
public MappedStatement getMappedStatement(String id) {
return this.getMappedStatement(id, true);
}
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
configuration.getMappedStatement()的方法,是通過MappedStatement的id直接去configuration裏的Map緩存裏面拿的,拿到了MappedStatement之後,再把parameter通過包裝成數組的形式,最後交給executor的query()方法去執行(下一篇再介紹executor的原理)。executor的query()也有對應的重載,帶ResultHandler和不帶的,帶ResultHandler的方法是沒有返回值的,它直接是填充到ResultHandler裏面去。
插入,刪除,修改功能:
@Override
public int insert(String statement) {
return insert(statement, null);
}
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
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();
}
}
@Override
public int delete(String statement) {
return update(statement, null);
}
@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
不管是insert、update、delete,在sqlSession裏面其實都是執行update操作,區分開來的作用只是sqlSession的模板的一種格式,和select類似,update()方法也是把執行的細節放在executor裏面,只不過executor.update()方法返回時的是int(影響條數)
除了增刪查改之外,sqlSession的其他方法
@Override
public void commit() {
commit(false);
}
@Override
public void commit(boolean force) {
try {
//執行提交,force爲true時強制提交,false需要判斷數據是後變髒和是否設置爲自動提交
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public void rollback() {
rollback(false);
}
@Override
public void rollback(boolean force) {
try {
//執行回滾,force爲true時強制提交,false需要判斷數據是後變髒和是否設置爲自動提交
executor.rollback(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error rolling back transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//判斷是否提交,force爲true時強制執行
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
@Override
public void clearCache() {
//清除本地緩存
executor.clearLocalCache();
}
@Override
public void close() {
try {
//關閉連接
executor.close(isCommitOrRollbackRequired(false));
closeCursors();
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
@Override
public List<BatchResult> flushStatements() {
try {
//該方法針對批量操作的,批量操作時將statement緩存到一個list上,調用這個方法時才把list中的Statement提交到數據庫,提交完後再清空list
return executor.flushStatements();
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error flushing statements. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
executor執行commit()、rollback()和close()時,會通過上面提到的TransactionFactory類,拿到具體的Transaction,再調用Transaction的commit、rollback和close方法
結語
SqlSession其實是一個模板方法,裏面封裝了用戶一些常用的操作,它的內部並沒有太多的執行細節,而是把複雜的處理邏輯放到了Executor裏面去
這篇對SqlSession介紹的博客到此爲止,後面會繼續分析Excutor的執行過程。
謝謝各位感興趣的小夥伴