MyBatis原始碼分析---呼叫SqlSession增刪改查到底執行了什麼步驟
SqlSession建立流程:
sqlSession可謂是MyBatis中最為核心的物件,相當於與資料庫的一次會話,是程式和資料庫的橋樑。
SqlSession物件由SqlSessionFactory工廠物件建立
SqlSessionFactory工廠物件提供了一大堆過載方法用來建立SqlSession物件
public interface SqlSessionFactory { SqlSession openSession(); //是否自動提交 SqlSession openSession(boolean autoCommit); //設定事物隔離級別 SqlSession openSession(TransactionIsolationLevel level); //ExecutorType 列舉型別 用於定義執行sql的方式 決定了使用的Executor執行器 SqlSession openSession(ExecutorType execType); SqlSession openSession(ExecutorType execType, boolean autoCommit); Configuration getConfiguration(); }
無參構造:
無參構造是平日裡最常用的一個方法,不自動提交事物,使用預設的執行sql命令方式(ExecutorType.SIMPLE)
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
sqlSessionFactory的預設實現類DefaultSqlSessionFactory轉而呼叫openSessionFromDataSource()方法
再次獲取配置檔案中設定的執行sql命令方式,不設定事物隔離級別,設定不自動提交。
再來看看openSessionFromDataSource方法做了什麼
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //從配置中獲取環境物件 final Environment environment = configuration.getEnvironment(); //獲取環境配置中的事物工廠物件 主要用於建立事物物件 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); //事物物件包含了本次訪問的connection物件以及對事物的管理 //sqlsession操作事物時實際就是在操作這個物件 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //建立核心執行器物件(預設情況下使用的是SimpleExecutor類) //SqlSession物件進行增刪改查實際上全是在依賴這個執行器物件 final Executor executor = configuration.newExecutor(tx, execType); //sqlsession依賴的物件全部建立完成 返回sqlSession物件 return new DefaultSqlSession(configuration, executor, autoCommit); }catch... }
Executor執行器物件的建立流程:
ConfigUration物件下的newExecutor方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
//由於我們沒有傳入ExecutorType並且沒有在配置檔案中配置
//預設使用了simpleExecutor作為實現
executor = new SimpleExecutor(this, transaction);
}
//如果在全域性配置開啟了cacheEnabled(二級快取)
//則再對執行器物件進行包裝:使用了裝飾器模式,當執行器執行查詢操作時會先從二級快取中查詢
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
SqlSession執行流程:
大家都知道,在呼叫SqlSession.getMapper()時,返回的是該介面的代理類
在執行代理類所有方法時,實際上是執行了MapperProxy類下的invoke()方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如果呼叫了Object類的方法 直接呼叫返回結果
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
//是否是預設方法 (jdk1.8之後可以在介面中定義default方法)
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//MapperMethod:包含了要執行方法的所有資訊 方法的唯一標識
//執行的方法名 方法的型別:select/insert/delete/update 方法返回值型別
//方法依賴的引數個數 方法引數是否使用了@Param註解 ......
final MapperMethod mapperMethod = cachedMapperMethod(method);
//執行方法
return mapperMethod.execute(sqlSession, args);
}
現在已經拿到了執行方法的所有資訊,和傳遞過來的引數
呼叫了MapperMethod物件的execute()方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
//無論是insert/update/delte執行的都是一樣的流程 最終都會執行sqlsession中的update方法
case INSERT/UPDATE/DELETE: {
//將傳入方法的引數進行轉換
//引數只有一個並且沒有註解的話直接返回
//多個引數就封裝為map物件
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
//返回值為List/set/Array
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
//返回值為Map
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
//呼叫儲存過程返回遊標
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
//返回值為單個物件
} else {
//封裝引數 同上
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
}
return result;
}
其實在呼叫介面代理類的方法後,到頭來還是呼叫了sqlSession去執行增刪改查方法.
sqlSession的增刪改查方法都需要sql對映檔案下增刪改查標籤的唯一標識,以及依賴的引數
只不過在這中間,MyBatis將方法解析了出來,程式設計師只需要照常呼叫介面內的方法
由mybatis告訴sqlsession該執行update還是select,並且將引數封裝一下。
查詢單個物件:
sqlSession的selectOne()方法:
@Override
/**
* statement : 所執行方法在sql對映檔案中的namespace + id
* parameter : 由MyBatix進行封裝後的引數(匹配更方便)
*/
public <T> T selectOne(String statement, Object parameter) {
// 其實無論查詢一個還是查詢多個 最後都進入了selectList()方法
List<T> list = this.<T>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;
}
}
sqlSession的selectList()方法:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根據方法的唯一標識(全類名加方法名) 從配置中獲取節點資訊並封裝為MappedStatement物件
//MappedStatement相當於對mapper.xml中一個select/insert/update/delete節點的封裝
//包含了該節點的所有資訊(sql文字,屬性)
//還包含了一個sqlSource物件 用於根據引數動態生成sql語句並繫結到BoundSql物件中
MappedStatement ms = configuration.getMappedStatement(statement);
//wrapCollection(parameter)判斷引數是否是collection型別或者陣列 如果是 轉換為map
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();
}
}
在sqlSession執行的selectList()方法中主要匹配到了mapper.xml檔案中對應的select節點並封裝為mappedStatement物件
已經有了當前節點的全部資訊以及引數
executor物件以及可以開始工作了
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//通過mapperStatement物件獲取最終執行的sql語句(執行了一系列條件判斷 動態生成了最終sql)
BoundSql boundSql = ms.getBoundSql(parameter);
//建立用作於一級快取Map物件的key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//呼叫過載方法
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//先從一級快取中獲取資料
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//用於處理呼叫儲存過程的情況
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//快取中未找到,從資料庫中進行查詢
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
從資料庫進行查詢
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//現在已經確定快取中沒有要查詢的資料 必須從資料庫進行查詢
//並且動態生成後的sql語句和引數都已經準備好了
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
//查詢完畢後將查詢出的資料放入一級快取
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
在doQuery()方法中,建立了一個StatementHandler物件
這個物件用來建立statement物件\設定引數\以及操作statement進行增刪改查
StatementHandler是一個介面 使用時的實現類由標籤中的StatementType屬性來決定
StatementType屬性用來決定執行sql的方式,它有三個取值
1):STATEMENT:直接操作sql,不進行預編譯,設定引數:${} 實現類:SimpleStatementHandler
2):PREPARED:預處理,引數,進行預編譯, 設定引數:#{} 實現類:PrepareStatementHandler
3):CALLABLE:執行儲存過程 實現類:CallableStatementHandler
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 物件建立時還會建立MyBatis的最後兩大核心元件:
//ParameterHandler(用於給statement繫結引數)和ResultSetHandler(處理結果集)
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//在executor物件中呼叫prepareStatement(handler, ms.getStatementLog())
//分別執行了三個操作:
//1):從繫結的事物物件獲取connection
//2):呼叫statementHandler物件的prepare(Coneection conn)並獲取Statement物件
//3):呼叫statementHandler物件的parameterize(Statement statement)方法
//為statement設定引數(如果是SimplePrapareHandler則不需要 方法內是空實現)
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
初始化好的statement物件已經準備就緒了 轉而呼叫statementHandler的query方法
以下是prepareStatementHandler類對query的實現
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//執行sql
ps.execute();
//處理resultSet,封裝結果集 並返回封裝好的List<E>集合
//執行完畢
return resultSetHandler.<E> handleResultSets(ps);
}
在進行增刪改的方法時其實與查詢方法的步驟大同小異,流程基本相似