jdbc執行流程圖
執行sql的過程就是發起請求的過程, 前文中已經學習瞭如何建立並獲取數據庫連接, 本文主要就是學習一下在service調用mapper獲取結果的過程, 也就是上圖的3~6的過程
方法調用鏈
首先通過上文, 我們知道了Mybatis執行sql必須要先獲取數據庫的連接, 所以我們在SimpleExecutor這個類中的prepareStatement方法中獲取了connection, 我們找到調用prepareStatement的方法, 這個方法應該就是查詢方法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 = ms.getConfiguration();
// 這裏是獲取一個statement的handler, handler主要是處理生成statement的
// 在newStatementHandler中還過了一次攔截器鏈
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 使用handler獲取一個statement
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
至於statement是如何生成的, 我們以後再看
點擊進入query方法
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// ps中的內容截圖在下方
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
我們可以看見, 這裏存放的是我們的入參, 以及前面處理好的sql語句, 即實際在mysql中使用的sql語句,
PreparedStatement 的execute方法就是將sql語句送往mysql執行
接下來這個statement通過getResultSet()方法獲取結果集, 這裏用到了代理
這是最後執行的方法
public ResultSet getResultSet() throws SQLException {
// 傳入的statement中有delegate屬性, delegate可以getResultSet
final ResultSet resultSet = delegate.getResultSet();
if (resultSet != null) {
if (proxyResultSet == null || ((ProxyResultSet) proxyResultSet).delegate != resultSet) {
proxyResultSet = ProxyFactory.getProxyResultSet(connection, this, resultSet);
}
}
else {
proxyResultSet = null;
}
return proxyResultSet;
}
這一步的return中會處理上面獲得的結果集
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
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;
}
}
}
return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
封裝結果集
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
super();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = rs;
// 獲取元數據
final ResultSetMetaData metaData = rs.getMetaData();
// 獲取查詢出來的字段個數
final int columnCount = metaData.getColumnCount();
// 循環設置字段名以及mysql的變量類型和java的類型映射
for (int i = 1; i <= columnCount; i++) {
columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
classNames.add(metaData.getColumnClassName(i));
}
}
結果集返回給resultSetHandler
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 這裏接收上面的結果集
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 處理指定個數的結果集
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 進入DefaultResultSetHandler中的結果集處理
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
DefaultResultSetHandler中的結果集處理, 具體操作不在本文描述, 以後再講
通過這個方法處理結果集, 會返回我們需要的java類型, 並且將字段的值注入
handleRowValues這個方法中會處理字段和值的映射
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
// 對應於jdbc中的關閉結果集操作
closeResultSet(rsw.getResultSet());
}
}
結果集關閉後就層層返回到doQuery關閉statement, 由於我們的結果設置的不是list也不是map, 所以使用的是DefaultSqlSession中的selectOne方法
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
// 可以看見無論結果設置是否多個, 都是調用selectList去數據庫中查詢
List<T> list = this.selectList(statement, parameter);
// 由於是selectOne, 所以這裏返回的list必須是只有一個元素
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;
}
}
最後的結果返回SqlSessionTemplate類, 這個攔截器是template的內部類,
是在MapperMethod類中的execute方法的selectOne進入的方法,
大家去看看Template類可以發現, selectOne返回的是this.sqlSessionProxy.selectOne,
而這個sqlSessionProxy屬性是構造器中new出來的內部類SqlSessionInterceptor
也就是最後進入的其實是下面這個invoke方法
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 這裏這個invoke進入的是DefaultSqlSession中的selectOne方法
// 真正被執行的selectOne
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
// 最後關閉會話
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
我們調用自己寫的mapper中的方法之後, 會通過代理進入MapperProxy中, 可以看見這裏調用了上面的execute, 這個invoke返回之後, 就會在我們的service得到最終的java對象
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
後記
本篇主要是講了講Mybatis在執行sql的時候, 得到數據庫連接後, 何時創建statement, 何時創建resultSet
不難發現, mybatis的很多實現都是依賴代理, 我們調用mapper方法後就會進入MapperProxy, 這裏就是一個代理
如果除去所有代理的話, 其實流程就是先判斷返回的結果集個數從而決定調用selectOne還是selectList方法, 然後判斷一級緩存, 如果不存在就去數據庫查找, 查找過程就是先通過Hikari中獲取的connection創建statement, 然後提交sql到數據庫, 獲得結果後通過resultSetHandler處理結果返回到我們調用mapper方法的service中