MyBatis是目前非常流行的ORM框架,它的功能很強大,然而其實現卻比較簡單、優雅。本文主要講述MyBatis的架構設計思路,然後探究MyBatis的是如何實現查詢的。
MyBatis執行流程
利用MyBatis實現一次查詢
InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//創建SqlSessionFactory
SqlSessionFactory factory = builder.build(inputStream);
//從SqlSession工廠 SqlSessionFactory中創建一個SqlSession,進行數據庫操作
SqlSession sqlSession = factory.openSession();
//使用SqlSession查詢
Map<String,Object> params = new HashMap<String,Object>();
params.put("xxx",xxx);
//執行查詢
List<Object> result = sqlSession.selectList("mapper.sqlId",params);
- SqlSession sqlSession = factory.openSession():開啓一個sqlSession,它表示和數據庫交互的會話,完成必要數據庫增刪改查功能。
- 執行sqlSession.selectList:返回查詢結果List,Mybatis和數據庫進行交互的流程都在此方法中。
那麼探究MyBtis就由此進入吧!
selectList(String statement, Object parameter)方法源碼:
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根據Statement Id,在mybatis 配置對象Configuration中查找和配置文件相對應的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//查找好以後交給executor.query方法執行下面的流程
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
<select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >
select
*
from table
</select>
說明:根據Statement獲得StatementId(如上面配置文件的Id爲selectByMinSalary),通過StatementId找出配置文件mapper.xml中對應的MappedStatement,並且將其交給executor的query方法來執行。
Executor的實現類BaseExecutor.query方法源碼
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//動態地生成需要執行的SQL語句,用BoundSql對象表示
BoundSql boundSql = ms.getBoundSql(parameter);
//設置一級緩存的key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
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();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
//queryFromDatabase方法
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;
}
//SimpleExecutor(BaseExecutor子類)類的doQuery()
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();
//創建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//StatementHandler根據configuration創建Statement
stmt = prepareStatement(handler, ms.getStatementLog());
//交給StatementHandler.query執行
//成對數據庫的查詢,最終返回List結果集。
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
說明:Executor的功能和作用是
1.創建BoundSql;
2.創建緩存
3.將BoundSql傳遞給StatementHandler,由StatementHandler來完成對數據庫的查詢,以及返回List。
StatementHandler
接着我們進入prepareStatement方法設置參數:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
//對statement設置參數,就是對?佔位符賦值
handler.parameterize(stmt);
return stmt;
}
進入parameterize方法:
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
此時我們知道,是通過使用ParameterHandler對象來完成對Statement的設值 ,將一個一個參數填充到”?”中。
此時Statement對象構建完成了,是一個可用的SQL語句了。
StatementHandler.query()方法:
//PreParedStatementHandler類來實現query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//將statement轉爲PreparedStatement
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//通過使用ResultHandler來處理ResultSet來返回List
return resultSetHandler.<E> handleResultSets(ps);
}
說明:StatementHandler通過ResultHandler的handleResultSets方法,實現將ResultSet結果集轉換成List 結果集。
ResultHandler
ResultHandler.handleResultSets用來將ResultSet結果集轉換成List
源碼如下
//
// HANDLE RESULT SETS
//DefaultResultSetHandler的handleResultSets方法
public List<Object> handleResultSets(Statement stmt) throws SQLException {
//這裏是List
final List<Object> multipleResults = new ArrayList<Object>();
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);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResulSets();
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);
}
總結
1.開啓一個sqlSession
2.sqlSession根據StatementId得到一個MapperStatement,並交給Executor來執行查詢
3.Executor得到MapperStatement後,會生成一個boundsql,並且創建一個代表緩存的cacheKey,如果對於緩存中有數據,則直接返回;如果緩存中沒有,就需要boundsql傳遞且藉助StatementHandler去數據庫中獲得數據,並返回結果集List,之後在加入緩存。
4.StatementHandle**r獲得boundsql以後,藉助**ParameterHandler 來講參數設置進Statement並返回一個PreparedStatement對象,此時執行查找返回結果集ResultSet。StatementHandler此時再借助ResultHandler.handleResultSets用來將ResultSet結果集轉換成List 。
借用大神的一張圖來概括上述流程: