1 引言和主要類
創建完sqlSession實例後,我們就可以進行數據庫操作了。比如通過selectOne()方法查詢數據庫,如代碼
// 讀取XML配置文件
String resource = "main/resources/SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 創建sqlSessionFactory單例,初始化mybatis容器
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 創建sqlSession實例,用它來進行數據庫操作,mybatis運行時的門面
SqlSession session = sessionFactory.openSession();
// 進行數據庫查詢操作
User user = session.selectOne("test.findUserById", 1); // 訪問數據庫,statement爲mapper.xml中的id
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
創建sqlSessionFactory單例和sqlSession實例在前兩節中分析過了,下面我們着重來分析sqlSession操作數據庫的過程。
sqlSession操作數據庫有兩種方法,可以直接使用select update insert delete等方法;也可以通過getMapper()先獲取mapper動態代理實例,然後再進行數據庫操作。相比而言mapper方式更靈活且不易出錯,是mybatis推薦的方式。本節我們分析直接使用select等方法的流程,下一節再分析mapper方式。
本節以selectOne()方法的實現過程爲例來分析sqlSession操作數據庫的流程,涉及的主要類如下。
- DefaultSqlSession:SqlSession的默認實現,其方法基本都是利用Executor代理實現。
- Executor:mybatis運行的核心,調度器。調度mybatis其他三大組件的執行。
- StatementHandler:SQL語句執行器,cache的管理等
- ParameterHandler:入參處理器,statementType爲PREPARE是需要使用到它,來解析入參到preparedStatement中
- ResultSetHandler:結果集映射處理器,將數據庫操作原始結果(主要是查詢操作),映射爲Java POJO。這正是ORM要解決的關鍵問題。
2 流程
2.1 DefaultSqlSession的selectOne()
先從DefaultSqlSession的selectOne()方法看起。
public <T> T selectOne(String statement, Object parameter) {
// selectOne本質上是調用selectList實現,如果結果集大於一個,則報TooManyResultsException。
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;
}
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
// 由sql語句的標示statement和入參parameter,查詢滿足條件的數據列表
// @Param statement: mapper.xml中mapper節點下的select delete update insert等子節點的id屬性
// @Param parameter: 傳入sql語句的入參
// @Param rowBounds: 邏輯分頁,包含offset和limit兩個主要成員變量。mybatis分頁邏輯爲捨棄offset之前條目,取剩下的limit條。默認DEFAULT不分頁
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 從mappers節點初始化階段創建好的mappedStatements這個Map中,找到key爲當前要找到的sql的id的那條
MappedStatement ms = configuration.getMappedStatement(statement);
// 通過執行器Executor作爲總調度來執行查詢語句,後面以BaseExecutor來分析。
// BatchExecutor ReuseExecutor SimpleExecutor均繼承了BaseExecutor
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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
selectOne()方法其實是通過調用selectList()實現的,因爲二者本質是完全相同的,只是前者返回一個對象,而後者爲列表List而已。selectList()採用代理模式,使用調度器Executor的query()方法實現。上一節着重講過Executor是SqlSession各個方法的具體實現,是mybatis運行的核心,通過調度StatementHandler ParameterHandler ResultSetHandler三個組件來完成sqlSession操作數據庫的整個過程。Executor的實現類有SimpleExecutor ReuseExecutor BatchExecutor等,它們的基類都是BaseExecutor。下面來分析BaseExecutor的query
2.2 BaseExecutor的query() 調度器開始數據庫query
// BaseExecutor的查找方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 從MappedStatement中找到boundSql成員變量,前面SqlSessionFactory創建部分講到過Mapper解析時的三大組件:MappedStatement SqlSource BoundSql
// 其中BoundSql通過sql執行語句和入參,來組裝最終查詢數據庫用到的sql。
BoundSql boundSql = ms.getBoundSql(parameter);
// 創建CacheKey,用作緩存的key,不用深入理解。
// sql的id,邏輯分頁rowBounds的offset和limit,boundSql的sql語句均相同時(主要是動態sql的存在),也就是組裝後的SQL語句完全相同時,才認爲是同一個cacheKey
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 {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
// 調度器已經close了則報錯
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// flush cache, 即寫入並清空cache。之後就只能從數據庫中讀取了,這樣可以防止髒cache
// localCache和localOutputParameterCache爲BaseExecutor的成員變量,它們構成了mybatis的一級緩存,也就是sqlSession級別的緩存,默認是開啓的。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
// 從緩存或數據庫中查詢結果list
List<E> list;
try {
// queryStack用來記錄當前有幾條同樣的查詢語句在同時執行,也就是併發
queryStack++;
// 未定義resultHandler時,先嚐試從緩存中取。
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) {
// statement級別的緩存,只緩存id相同的sql。當所有查詢語句和延遲加載的查詢語句均執行完畢後,可清空cache。這樣可節約內存
clearLocalCache();
}
}
return list;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
調度器的query方法先從MappedStatement中獲取BoundSql,它包含了sql語句和入參對象等變量,再構造緩存的key,即cacheKey。然後先嚐試從緩存中取,緩存未命中則直接從數據庫中查詢。最後處理延遲加載,直接從緩存中取出查詢數據即可。下面我們着重分析直接從數據庫中查詢的過程,也即queryFromDatabase()方法。
// 直接從數據庫中查詢
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 先利用佔位符將本次查詢設置到本地cache中,個人理解是防止後面延遲加載時cache爲空
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 真正的數據庫查詢,後面詳細分析
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 查到了結果後,將前面的佔位符的cache刪掉
localCache.removeObject(key);
}
// 將查詢結果放到本地cache中緩存起來
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
doQuery()進行真正的數據庫查詢,它由SimpleExecutor等具體類來實現。我們以SimpleExecutor爲例分析。
// 數據庫查詢
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,用來執行sql語句。SimpleExecutor創建的是RoutingStatementHandler。
// 它的是一個門面類,幾乎所有方法都是通過代理來實現。代理則由配置XML settings節點的statementType區分。故僅僅是一個分發和路由。後面詳細分析
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 構造Statement,後面詳細分析
stmt = prepareStatement(handler, ms.getStatementLog());
// 通過語句執行器的query方法進行查詢, 查詢結果通過resultHandler處理後返回。後面詳細分析
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
doQuery流程爲
- 先創建StatementHandler語句處理器。前面講過StatementHandler是mybatis四大組件之一,負責sql語句的執行。根據XML配置文件的settings節點的statementType子元素,來創建不同的實現類,如SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler。他們的基類統一爲BaseStatementHandler,外觀類爲RoutingStatementHandler(後面詳細分析)。
- 創建完StatementHandler後,調用prepareStatement進行初始化,
- 然後調用實現類的query方法進行查詢。
2.3 StatementHandler的query(), 語句處理器進行查詢
下面我們來看StatementHandler是如何執行的,先看StatementHandler的創建過程。
2.3.1 StatementHandler的創建過程
// 創建RoutingStatementHandler,它是StatementHandler的外觀類,也是StatementHandler的一個實現類
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 直接構造一個RoutingStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 將statementHandler,添加爲插件的目標執行器。插件通過配置XML文件的plugins節點設置。
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
// RoutingStatementHandler的構造器,根據statementType變量來創建不同的StatementHandler實現,作爲它的代理
// RoutingStatementHandler的幾乎所有方法都是通過這些代理實現的,典型的代理模式。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根據statementType創建不同的StatementHandler實現類。statementType是在xml配置文件的settngs節點的statementType子元素中設置的。
switch (ms.getStatementType()) {
case STATEMENT:
// 直接操作sql,不進行預編譯。此時直接進行字符串拼接構造sql String
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());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
2.3.2 StatementHandler的初始化
StatementHandler的初始化如下
// 通過事務構造sql執行語句statement,如JdbcTransaction
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 開啓數據庫連接,創建Connection對象。JdbcTransaction事務直接通過JDBC創建connection
Connection connection = getConnection(statementLog);
// 初始化statement並設置期相關變量,不同的StatementHandler實現不同。後面以RoutingStatementHandler爲例分析
stmt = handler.prepare(connection, transaction.getTimeout());
// 設置parameterHandler,對於SimpleStatementHandler來說不用處理
handler.parameterize(stmt);
return stmt;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
StatementHandler初始化步驟如下:
- 先開啓一個數據庫連接connection,
- 然後初始化statementHandler,
- 最後進行參數預處理。
先開啓數據庫連接connection,直接獲取數據源dataSource的connection,即通過數據庫本身來開啓連接。
// JdbcTransaction和ManagedTransaction都是直接調用dataSource的getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
再進行初始化statementHandler,調用基類BaseStatementHandler的prepare方法完成
// 初始化statementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
// 典型的代理模式,不同的statementType創建不同的Statement,但這兒他們都調用到他們的基類BaseStatementHandler中的prepare方法
return delegate.prepare(connection, transactionTimeout);
}
// BaseStatementHandler初始化statement並設置期相關變量,不同的StatementHandler實現不同。
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 初始化statement,由具體的StatementHandler來實現。比如SimpleStatementHandler通過JDBC connection的createStatement來創建
statement = instantiateStatement(connection);
// 設置timeout(超時時間)和fetchSize(獲取數據庫的行數)
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
最後進行參數預處理。不同的statementHandler實現類有不同的參數預處理方式。
- SimpleStatementHandler不進行任何參數預處理,它的sql直接通過字符串拼接而成。
- PreparedStatementHandler進行預處理,會將#轉換爲?,然後設置對應的變量到sql String中。
// RoutingStatementHandler的parameterize方法,通過代理模式實現
public void parameterize(Statement statement) throws SQLException {
// 又是代理模式,由具體的statementHandler實現類來實現
delegate.parameterize(statement);
}
// SimpleStatementHandler不做參數預處理
public void parameterize(Statement statement) throws SQLException {
// N/A
}
// PreparedStatementHandler進行參數預處理,通過parameterHandler實現
public void parameterize(Statement statement) throws SQLException {
// parameterHandler可以由用戶通過插件方式實現,mybatis默認爲DefaultParameterHandler。這個方法我們不進行詳細分析了。
parameterHandler.setParameters((PreparedStatement) statement);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
2.3.3 statementHandler的query()進行數據庫查詢
創建和初始化statementHandler後,就可以調用它的query()方法來執行語句查詢了。先看SimpleStatementHandler的query過程。
// SimpleStatementHandler的query操作過程
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 獲取存放在boundSql中的sql執行語句
String sql = boundSql.getSql();
// 通過JDBC sql的statement,直接執行sql語句。入參在statement預編譯時進行了轉換並設置到statement中了。
statement.execute(sql);
// resultSetHandler處理查詢結果,並返回。這一部分十分複雜,但也體現了mybatis的設計精巧之處,可以兼容很多複雜場景下的數據庫結果轉換。如數據庫列名和Java POJO屬性名不同時的映射,關聯數據庫的映射等。
return resultSetHandler.<E>handleResultSets(statement);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
PrepareStatementHandler的query操作過程如下
// PrepareStatementHandler的query操作過程
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// PREPARE方式下,sql statement進行了預編譯,並注入了入參。它是一個PreparedStatement類型
PreparedStatement ps = (PreparedStatement) statement;
// 直接調用JDBC PreparedStatement的execute方法操作數據庫。大家應該對這兒很熟悉了,JDBC的操作
ps.execute();
// 結果集處理,後面詳細分析
return resultSetHandler.<E> handleResultSets(ps);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
query先從boundSql中獲取具體執行語句,然後通過JDBC的statement直接執行SQL語句。這兩步完成後,就從數據庫中查找到了結果集了。從這兒可見,mybatis最底層還是通過JDBC來操作數據庫的。
mybatis對結果集的處理十分複雜,我們下面詳細分析。
2.4 ResultSetHandler處理數據庫結果集
通過JDBC完成數據庫的操作後,我們就拿到了原始的數據庫結果了。此時要將數據庫結果集ResultSet轉換爲Java POJO。這一步通過mybatis四大組件之一的ResultSetHandler來實現。ResultSetHandler默認實現類爲DefaultResultSetHandler,用戶也可以通過插件的方式覆蓋它。插件在xml配置文件的plugins子節點下添加。下面詳細分析DefaultResultSetHandler的handleResultSets()方法。
// DefaultResultSetHandler通過handleResultSets處理數據庫結果集,處理後作爲真正的結果返回。此處的關鍵是處理resultMap映射
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
// 1 從JDBC操作數據庫後的statement中取出結果集ResultSet
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 2 獲取resultMaps, mapper.xml中設置,並在mybatis初始化階段存入mappedStatement中。
// resultMap定義了jdbc列到Java屬性的映射關係,可以解決列名和Java屬性名不一致,關聯數據庫映射等諸多問題。
// 它是mybatis中比較複雜的地方,同時也大大擴展了mybatis的功能
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 3 一條條處理resultSet
while (rsw != null && resultMapCount > resultSetCount) {
// 取出一條resultMap,即結果映射
ResultMap resultMap = resultMaps.get(resultSetCount);
// 進行數據庫列到Java屬性的映射,後面詳細分析
handleResultSet(rsw, resultMap, multipleResults, null);
// 取出下一條resultSet
rsw = getNextResultSet(stmt);
// 清空nestedResultObjects,即嵌套的Result結果集
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 4 處理嵌套的resultMap,即映射結果中的某些子屬性也需要resultMap映射時
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
// 取出父ResultMapping,用於嵌套情況
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
// 通過嵌套ResultMap的id,取出ResultMap
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
// 處理ResultSet,後面詳細分析
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
// 5 構造成List,將處理後的結果集返回
return collapseSingleResultList(multipleResults);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
handleResultSets流程如下
- 從JDBC操作數據庫後的statement中取出結果集ResultSet
- 獲取resultMaps, 它們定義了數據庫結果集到Java POJO的映射關係
- 一條條處理resultSet,調用handleResultSet做數據庫列到Java屬性的映射
- 處理嵌套的resultMap,即映射結果中的某些子屬性也需要resultMap映射時
- 構造成List,將處理後的結果集返回
這其中的關鍵是handleResultSet()方法進行數據庫列到Java屬性的映射,也是ORM關鍵所在。我們接着分析。
// 通過resultMap映射,處理數據庫結果集resultSet
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
// parentMapping不爲空,表示處理的是嵌套resultMap中的子resultMap。handleRowValues後面詳細分析
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
// 非嵌套resultMap
if (resultHandler == null) {
// 用戶沒有自定義resultHandler時,採用DefaultResultHandler。並將最終的處理結果添加到multipleResults中
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 用戶定義了resultHandler時,採用用戶自定義的resultHandler
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
handleResultSet針對嵌套resultMap和非嵌套resultMap做了分別處理,但都是調用的handleRowValues()方法,接着看。
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
// 有嵌套resultMap時
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 無嵌套resultMap時
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
嵌套resultMap的處理比較麻煩,這兒不分析了,我們看非嵌套的,即handleRowValuesForSimpleResultMap。
// 非嵌套ResultMap的處理方法。根據resultMap一行行處理數據庫結果集到Java屬性的映射
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
// mybatis的邏輯分頁規則爲跳過rowBounds中offset之前的部分,取limit行數的數據
// skipRows方法會跳過rowBounds中offset之前的部分。
skipRows(rsw.getResultSet(), rowBounds);
// 一行行處理數據庫結果集,直到取出的行數等於rowBounds的limit變量(邏輯分頁),或者所有行都取完了。
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
// 處理resultMap中的discriminator,使用結果值來決定使用哪個結果映射。可以將不同的數據庫結果映射成不同的Java類型。此處不詳細分析了
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
// 處理這一行數據, 得到映射後的Java結果
Object rowValue = getRowValue(rsw, discriminatedResultMap);
// 使用resultHandler處理得到的Java結果,這纔是最終返回的Java屬性值。
// 用戶可自定義resultHandler,否則使用DefaultResultHandler。
// 用戶可使用ResultSetHandler插件來自定義結果處理方式,此處體現了mybatis設計精巧之處
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
// 邏輯分頁rowBounds。skipRows方法會跳過rowBounds中offset之前的部分
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
rs.absolute(rowBounds.getOffset());
}
} else {
for (int i = 0; i < rowBounds.getOffset(); i++) {
rs.next();
}
}
}
// 邏輯分頁rowBounds。shouldProcessMoreRows取limit條數據庫查詢結果
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) throws SQLException {
return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
mybatis的邏輯分頁規則爲跳過rowBounds中offset之前的部分,取limit行數的數據。通過skipRows()和shouldProcessMoreRows()兩個方法共同完成這個功能。遍歷resultSet,通過getRowValue()方法處理一行行數據。最後調用resultHandler來處理轉換後的結果。
storeObject結果處理的代碼如下
// 利用resultHandler處理經過resultMap映射的Java結果
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
if (parentMapping != null) {
// 嵌套的resultMap,也就是子resultSet結果。鏈接到父resultSet中,由父resultMap一起處理。不詳細分析了
linkToParents(rs, parentMapping, rowValue);
} else {
// 不是嵌套時,直接調用resultHandler進行最後的處理,後面詳細看
callResultHandler(resultHandler, resultContext, rowValue);
}
}
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
// 構建resultContext上下文,然後利用resultHandler處理。後面以DefaultResultHandler來分析
resultContext.nextResultObject(rowValue);
((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}
// ResultHandler對映射後的結果做最後的處理
public void handleResult(ResultContext<? extends Object> context) {
// DefaultResultHandler對經過resultMap映射後的Java結果不做任何處理,僅僅添加到list中,最後將list返回給selectList()等方法。
list.add(context.getResultObject());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
3 總結
mybatis操作數據庫的流程,也是它的四大組件Executor StatementHandler ParameterHandler ResultSetHandler的執行過程。其中Executor是調度器,StatementHandler爲SQL語句執行器,ParameterHandler爲入參執行器,ResultSetHandler爲結果集映射執行器。四大組件分層合理,運行流程清晰,都有默認實現,同時用戶也可以利用plugin來覆蓋它。這些無一不體現了mybatis的靈活和設計精巧,值得我們平時設計構架時學習。