Mybatis原始碼分析(6)—— 從JDBC看Mybatis的設計
Java資料庫連線,(Java Database Connectivity,簡稱JDBC)是Java語言中用來規範客戶端程式如何來訪問資料庫的應用程式介面,提供了諸如查詢和更新資料庫中資料的方法。
六步流程:
- 載入驅動(5.x驅動包不需要這步了)
- 建立連線
- 建立Statement
- 執行SQL語句
- 獲取結果集
- 關閉資源
這裡只取後面幾步分析下,基本上都是從Executor開始。DefaultSqlSession被每個Mapper持有,將各種基於SQL的操作轉移到呼叫Executor的query和update方法。
Executor的類圖如下:
以ReuseExecutor的doQuery方法為例:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this , ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
}
建立連線
上面ReuseExecutor的doQuery方法呼叫其prepareStatement方法:
private Statement prepareStatement(StatementHandler handler , Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
然後呼叫getConnection方法獲取連線:
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled() || connectionLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog);
} else {
return connection;
}
}
這個連線是從Transaction物件中獲取的。
當我們和Spring一起使用時,當然由Spring統一管理,回顧DefaultSqlSessionFactory類中的openSessionFromDataSource方法,這個連線就是找Spring要的(DataSourceUtils#getConnection),要過來放到了SpringManagedTransaction中。
建立Statement
- Statement createStatement()
建立Statement 物件,Statement介面提供基本執行SQL語句的能力。
- PreparedStatement prepareStatement(String sql)
建立PreparedStatement物件,PreparedStatement介面繼承了Statement介面,提供SQL語句接受輸入引數的能力。
- CallableStatement prepareCall(String sql)
建立CallableStatement物件,CallableStatement介面繼承了PreparedStatement介面,提供執行儲存過程的能力。
針對這三種情況,Mybatis提供了三個StatementHandler實現:
並建立了一個RoutingStatementHandler作為路由(代理類),根據MappedStatement中的statementType來確定建立哪個作為實際的StatementHandler實現。對外提供一致介面,實際呼叫交給委託類。
還是接著看上面的prepareStatement方法,它獲取連線後就接著呼叫StatementHandler的prepare方法:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
這個來自BaseStatementHandler的prepare方法又是呼叫子類的instantiateStatement方法來例項化Statement:
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
這裡最終呼叫connection的prepareStatement方法,回到了我們傳統的操作。
執行SQL語句
在Executor的實現類中,doUpdate和doQuery方法都會呼叫下面兩個方法:
handler.update(stmt);
handler.query(stmt, resultHandler);
SimpleStatementHandler的query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
PreparedStatementHandler的query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
CallableStatementHandler的query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
CallableStatement cs = (CallableStatement) statement;
cs.execute();
List<E> resultList = resultSetHandler.<E>handleResultSets(cs);
resultSetHandler.handleOutputParameters(cs);
return resultList;
}
這裡都是直接調的Statement或PreparedStatement的execute方法。
返回結果集
上面的三種StatementHandler,最終都會處理結果集,CallableStatementHandler有點特殊,其它兩種都是呼叫ResultSetHandler的handleResultSets方法來處理結果集。ResultSetHandler在基類BaseStatementHandler的建構函式中通過configuration物件來建立:
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
具體的實現有兩種,FastResultSetHandler和NestedResultSetHandler,且NestedResultSetHandler繼承了FastResultSetHandler,重寫了handleRowValues方法。
在FastResultSetHandler的handleResultSets方法中:
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<Object>();
final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
int resultSetCount = 0;
ResultSet rs = stmt.getResultSet();
while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB 2.1)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else {
if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break;
}
}
}
// 驗證是否定義了resultType或者resultMap
validateResultMapsCount(rs, resultMapCount);
while (rs != null && resultMapCount > resultSetCount) {
final ResultMap resultMap = resultMaps.get(resultSetCount);
ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
rs = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
return collapseSingleResultList(multipleResults);
}
處理結果行:
protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
final DefaultResultContext resultContext = new DefaultResultContext();
skipRows(rs, rowBounds);
while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
callResultHandler(resultHandler, resultContext, rowValue);
}
}
skipRows就是跳過行,基於查詢結果的分頁使用,其中會直接呼叫ResultSet的absolute方法:
rs.absolute(rowBounds.getOffset());
而shouldProcessMoreRows方法就是判斷是否應該獲取更多的行:
protected boolean shouldProcessMoreRows(ResultSet rs, ResultContext context, RowBounds rowBounds) throws SQLException {
return !context.isStopped() && rs.next() && context.getResultCount() < rowBounds.getLimit();
}
也對應了分頁的limit欄位,這種分頁可以說是偽分頁,查出來再分頁。所以我們一般使用外掛的形式來實現分頁,基於sql的動態替換。
具體針對行的處理在getRowValue方法中:
protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(resultObject);
boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
foundValues = applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache) || foundValues;
}
final List<String> mappedColumnNames = resultColumnCache.getMappedColumnNames(resultMap, null);
foundValues = applyPropertyMappings(rs, resultMap, mappedColumnNames, metaObject, lazyLoader, null) || foundValues;
foundValues = (lazyLoader != null && lazyLoader.size() > 0) || foundValues;
resultObject = foundValues ? resultObject : null;
return resultObject;
}
return resultObject;
}
根據ResultMap提供的相關引數(返回的型別等),構造一個空的物件,然後根據屬性名到ResultSet中獲取結果,再通過MetaObject給set進物件裡。
具體的取值是通過TypeHandler:
final Object value = typeHandler.getResult(rs, columnName);
每種型別的都對應一個TypeHandler的實現,以Integer型別的為例(IntegerTypeHandler):
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getInt(columnName);
}
框架就是這樣,底層就是最基本的原理和最簡單的介面,而這些介面通常就是所謂的標準。它所做的事就是解放我們的雙手,把能省的都給省了,讓我們專注於業務程式碼的編寫。
但我們也應該知其所以然。一是出於好奇心,這是程式設計師的本能;二是為了學習其優秀的思想和設計理念,以後可以為我所用,這可稱之為野心吧。