前言
由於Mybatis的源碼系列文章,期間有長時間的脫節,導致筆者在寫代碼解析的時候中間出現了斷裂,這個要和大家說聲對不起,因此本篇文章是補深入淺出Mybatis源碼解析——獲取Mapper代理對象流程照片文章的,在這篇文章中簡單的說了一下SqlSession執行主流程,也只是簡單的說了說,後面的核心代碼還沒有涉及。
說到這裏,我們還是儘快進入主題,首先回顧下前面提到的那篇文章最後的代碼,如下:
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 根據傳入的statementId,獲取MappedStatement對象
MappedStatement ms = configuration.getMappedStatement(statement);
// 調用執行器的查詢方法
// RowBounds是用來邏輯分頁(按照條件將數據從數據庫查詢到內存中,在內存中進行分頁)
// wrapCollection(parameter)是用來裝飾集合或者數組參數
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();
}
}
一、querying database
在前面說到的那篇文章中,值簡單的解析了getMappedStatement和wrapCollection方法,那麼這裏將會正式進入query方法,代碼如下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
在上面的代碼中,我們也看到了前一篇文章解析的getBoundSql,關於中間的createCacheKey部分,感興趣的同學可以自己去看看具體的實現,這裏就先不說了,我們還是繼續跟query這個方法,看看它裏面的具體實現,代碼如下:
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
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 {
// 執行查詢
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方法,如下:
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 獲取Configuration對象
Configuration configuration = ms.getConfiguration();
// 創建RoutingStatementHandler,用來處理Statement
// RoutingStatementHandler類中初始化delegate類(SimpleStatementHandler、PreparedStatementHandler)
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds,
resultHandler, boundSql);
// 子流程1:設置參數
stmt = prepareStatement(handler, ms.getStatementLog());
// 子流程2:執行SQL語句(已經設置過參數),並且映射結果集
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
在前面的代碼中,我們看到這短短的代碼中步驟還是比較多的,首先是獲取Configuration對象,然後創建RoutingStatementHandler,用來處理Statement,之後再在RoutingStatementHandler類中初始化delegate類(SimpleStatementHandler、PreparedStatementHandler),最後設置參數、執行SQL語句並且映射結果集。那我們先說說創建RoutingStatementHandler。因爲獲取Configuration對象只是把一開始初始化的configuration從MappedStatement中獲取一下。
二、創建RoutingStatementHandle
這裏我們來看看是怎麼獲創建RoutingStatementHandle的,代碼如下:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 創建路由功能的StatementHandler,根據MappedStatement中的StatementType
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
從上面的代碼可以知道步驟大概如下:
- 創建路由功能的StatementHandler
- 通過statementHandler到interceptorChain去pluginAll
這裏我們可以看看RoutingStatementHandler的構造器代碼,如下:
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
在上面的代碼中,可以看到幾個很熟悉的類:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler,當然這裏還是要根據StatementType去判斷的。
看完了這個構造器我們要繼續看子流程1:設置參數這個步驟了。
三、參數設置
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 獲取連接
Connection connection = getConnection(statementLog);
// 創建Statement(PreparedStatement、Statement、CallableStatement)
stmt = handler.prepare(connection, transaction.getTimeout());
// SQL參數設置
handler.parameterize(stmt);
return stmt;
}
上面的代碼大概分爲三步:
- 獲取連接
- 創建Statement(PreparedStatement、Statement、CallableStatement)
- SQL參數設置
那就先看看獲取連接:
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
這裏首先通過transaction對象來獲取connection,然後statementLog參數是否爲true,如果是則通過映射創建實例。最後返回connection對象。
那繼續看看創建Statement:
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 實例化Statement,比如PreparedStatement
statement = instantiateStatement(connection);
// 設置查詢超時時間
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
這段代碼的邏輯很簡單,就不多說了,我們繼續SQL參數設置代碼的解析:
PreparedStatementHandler.java
@Override
public void parameterize(Statement statement) throws SQLException {
// 通過ParameterHandler處理參數
parameterHandler.setParameters((PreparedStatement) statement);
}
@SuppressWarnings("unchecked")
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 獲取要設置的參數映射信息
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// 只處理入參
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 獲取屬性名稱
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 獲取每個參數的類型處理器,去設置入參和獲取返回值
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 獲取每個參數的JdbcType
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 給PreparedStatement設置參數
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException(
"Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException(
"Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
上面的兩段代碼中最核心的部分是在第二部分中,其主要步驟爲:
- 獲取要設置的參數映射信息
- 處理入參
- 獲取屬性名稱(然後再通過這個屬性名稱進行其他處理)
- 獲取每個參數的類型處理器,去設置入參和獲取返回值
- 獲取每個參數的JdbcType
- 給PreparedStatement設置參數
四、執行SQL語句
在說完了參數設置後,最後我們來看看執行SQL語句的邏輯:
PreparedStatementHandler.java
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 執行PreparedStatement,也就是執行SQL語句
ps.execute();
// 處理結果集
return resultSetHandler.handleResultSets(ps);
}
可以看到上面的代碼中有兩個主要步驟:執行SQL語句、處理結果集。這裏我們只說執行SQL語句的,關於處理結果集流給我們的下一篇文章吧,也就是最後一篇文章。
其實這裏的執行SQL就是設計到數據庫的底層執行代碼了,這裏我就只以MySQL的來展示一段代碼,由於筆者還沒設計這一塊,所以同學們感興趣的,可以自己去看看。
@Override
public boolean execute() throws SQLException {
synchronized (checkClosed().getConnectionMutex()) {
boolean returnVal = false;
checkStreamability();
setInOutParamsOnServer();
setOutParams();
returnVal = super.execute();
if (this.callingStoredFunction) {
this.functionReturnValueResults = this.results;
this.functionReturnValueResults.next();
this.results = null;
}
retrieveOutParams();
if (!this.callingStoredFunction) {
return returnVal;
}
// Functions can't return results
return false;
}
}
由於篇幅原因,本篇文章就先到這裏了,因爲筆者的水平,也沒有做太深的說明,只供一些入門的同學來相互學習,下一篇底阿媽將是本系列的最後一篇文章了,後面筆者將寫點其他系列的文章,譬如MySQL,然後將會繼續回到Java的領域內,進行深入的學習,這也是筆者2020年的flag。