MyBatis原始碼分析-SQL語句執行的完整流程
MyBatis 是支援定製化 SQL、儲存過程以及高階對映的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或註解,將介面和 Java 的 POJOs(Plain Old Java Objects,普通的 Java物件)對映成資料庫中的記錄。如何新建MyBatis原始碼工程請點選MyBatis原始碼分析-IDEA新建MyBatis原始碼工程。
MyBatis框架主要完成的是以下2件事情:
- 根據JDBC規範建立與資料庫的連線。
- 通過反射打通Java物件與資料庫引數互動之間相互轉換的關係。
MyBatis框架是一種典型的互動式框架,先準備好互動的必要條件,然後構建一個互動的環境,在互動環境中劃分會話,在會話中與資料庫進行互動資料。
1 MyBatis主要的類
- Configuration MyBatis所有的配置資訊都維持在Configuration物件之中。
- SqlSession 作為MyBatis工作的主要頂層API,表示和資料庫互動的會話,完成必要資料庫增刪改查功能
- Executor MyBatis執行器,是MyBatis 排程的核心,負責SQL語句的生成和查詢快取的維護
- StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定引數、將Statement結果集轉換成List集合。
- ParameterHandler 負責對使用者傳遞的引數轉換成JDBC Statement 所需要的引數,
- ResultSetHandler 負責將JDBC返回的ResultSet結果集物件轉換成List型別的集合;
- TypeHandler 負責java資料型別和jdbc資料型別之間的對映和轉換
- MappedStatement MappedStatement維護了一條<select|update|delete|insert>節點的封裝,
- SqlSource 負責根據使用者傳遞的parameterObject,動態地生成SQL語句,將資訊封裝到BoundSql物件中,並返回
- BoundSql 表示動態生成的SQL語句以及相應的引數資訊
以上幾個類在SQL操作中都會涉及,在SQL操作中重點關注下SQL引數什麼時候寫入和結果集怎麼轉換為Java物件,這兩個過程正好對應的類是PreparedStatementHandler和ResultSetHandler類。
2 SQL執行流程
MyBatis主要設計目的還是為了讓我們在執行SQL時對輸入輸出的資料的管理更加方便,所以方便的讓我們寫出SQL和方便的獲取SQL的執行結果是MyBatis的核心競爭力。下面就用一個例子來從原始碼角度看一下SQL的完整執行流程。
新建配置檔案conf.xml:
conf.xml首先建立資料表,這裡就以user表為例 :
DROP TABLE IF EXISTS user; CREATE TABLE user ( id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL, password VARCHAR(32) NOT NULL, sex int, email VARCHAR(32), phone VARCHAR(16), admin VARCHAR(16) );
然後新建與資料表對應的類User:
User再新建usre表的配置檔案:
userMapper.xml最後新建測試類:
/** * MyBatis測試類 */ public class TestMain { public static void main(String[] args) throws IOException { String resouce = "conf.xml"; InputStream is = Resources.getResourceAsStream(resouce); // 構建sqlSession工廠 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); // 獲取sqlSession SqlSession session = sqlSessionFactory.openSession(); User user; try { /** * 第一種方式: 直接執行已對映的 SQL 語句 */ String statement = "com.luoxn28.dao.UserDao.getById"; user = session.selectOne(statement, 1); System.out.println(user); } finally { session.close(); } /** * 第二種方式: 執行更清晰和型別安全的程式碼 */ // UserDao userDao = session.getMapper(UserDao.class); // user = userDao.getById(1); // System.out.println(user); } }
由於我們分析的是SQL的執行流程,那就重點關注下 user = session.selectOne(statement, 1); 這行程式碼~ 注意,傳進去的引數是1。
session是DefaultSqlSession型別的,因為sqlSessionFactory預設生成的SqlSession是DefaultSqlSession型別。selectOne()會呼叫selectList()。
// DefaultSqlSession類 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); // CURD操作是交給Excetor去處理的 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(); } }
在DefaultSqlSession.selectList中的各種CURD操作都是通多Executor進行的,這裡executor的型別是CachingExecutor,接著跳轉到其中的query方法中。
// CachingExecutor 類 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); // 獲取繫結的sql命令,比如"SELECT * FROM xxx" CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
getBoundSql為了獲取繫結的sql命令,在建立完cacheKey之後,就進入到CachingExecutor 類中的另一個query方法中。
// CachingExecutor 類 @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
這裡真正執行query操作的是SimplyExecutor代理來完成的,接著就進入到了SimplyExecutor的父類BaseExecutor的query方法中。
// SimplyExecutor的父類BaseExecutor類 @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++; /** * localCache是一級快取,如果找不到就呼叫queryFromDatabase從資料庫中查詢 */ 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; }
因為是第一次SQL查詢操作,所以會呼叫queryFromDatabase方法來執行查詢。
// SimplyExecutor的父類BaseExecutor類 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; }
從資料庫中查詢資料,進入到SimplyExecutor中進行操作。
// SimplyExecutor類 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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 子流程1:SQL查詢引數的設定 stmt = prepareStatement(handler, ms.getStatementLog()); // StatementHandler封裝了Statement // 子流程2:SQL查詢操作和結果集的封裝 return handler.<E>query(stmt); } finally { closeStatement(stmt); } }
注意,在prepareStatement方法中會進行SQL查詢引數的設定,也就是咱們最開始傳遞進來的引數,其值為1。handler.<E>query(stmt)方法中會進行實際的SQL查詢操作和結果集的封裝(封裝成Java物件)。當流程走到這裡時,程式已經壓棧有一定深度了,因為接下來程式分析會兵分兩路,一方面深入到SQL查詢及結果集的設定子流程1中,然後再深入到SQL查詢操作和結果集的封裝子流程2,因為還會回到這裡,所以就來一張呼叫棧的特寫吧:
子流程1:SQL查詢引數的設定
// SimplyExecutor類 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 獲取一個Connection Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); // 設定SQL查詢中的引數值 return stmt; }
通過getConnection方法來獲取一個Connection,呼叫prepare方法來獲取一個Statement(這裡的handler型別是RoutingStatementHandler,RoutingStatementHandler的prepare方法呼叫的是PrepareStatementHandler的prepare方法,因為PrepareStatementHandler並沒有覆蓋其父類的prepare方法,其實最後呼叫的是BaseStatementHandler中的prepare方法。是不是繞暈了,那就再看一遍吧 :) )。呼叫parameterize方法來設定SQL的引數值(這裡最後呼叫的是PrepareStatementHandler中的parameterize方法,而PrepareStatementHandler.parameterize方法呼叫的是DefaultParameterHandler中的setParameters方法)。
// PrepareStatementHandler類 @Override public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); }
// DefaultParameterHandler類 @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 = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { 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); } } } } }
到這裡為止,已經給Statement設定了最初傳遞進去的引數(值為1)了,那麼接著分析流程2:
流程2:SQL查詢及結果集的設定
// RoutingStatementHandler類 @Override public <E> List<E> query(Statement statement) throws SQLException { return delegate.<E>query(statement); }
// RoutingStatementHandler類 @Override public <E> List<E> query(Statement statement) throws SQLException { // 這裡就到了熟悉的PreparedStatement了 PreparedStatement ps = (PreparedStatement) statement; // 執行SQL查詢操作 ps.execute(); // 結果交給ResultHandler來處理 return resultSetHandler.<E> handleResultSets(ps); }
// DefaultResultSetHandler類(封裝返回值,將查詢結果封裝成Object物件) @Override 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; 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.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); }
ResultSetWrapper是ResultSet的包裝類,呼叫getFirstResultSet方法獲取第一個ResultSet,同時獲取資料庫的MetaData資料,包括資料表列名、列的型別、類序號等,這些資訊都儲存在ResultSetWrapper類中了。然後呼叫handleResultSet方法來來進行結果集的封裝。
// DefaultResultSetHandler類 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) closeResultSet(rsw.getResultSet()); } }
這裡呼叫handleRowValues方法來進行值的設定:
// DefaultResultSetHandler類 public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { if (resultMap.hasNestedResultMaps()) { ensureNoRowBounds(); checkResultHandler(); handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { // 封裝資料 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } }
// DefaultResultSetHandler類 // 封裝資料 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>(); skipRows(rsw.getResultSet(), rowBounds); while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); Object rowValue = getRowValue(rsw, discriminatedResultMap); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } }
// DefaultResultSetHandler類 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // createResultObject為新建立的物件,資料表對應的類 Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(resultObject); boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty(); if (shouldApplyAutomaticMappings(resultMap, false)) { // 這裡把資料填充進去,metaObject中包含了resultObject資訊 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; resultObject = foundValues ? resultObject : null; return resultObject; } return resultObject; }
相關推薦
MyBatis原始碼分析-SQL語句執行的完整流程
MyBatis 是支援定製化 SQL、儲存過程以及高階對映的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或註解,將介面和 Java 的 POJOs(Plain
精盡MyBatis原始碼分析 - SQL執行過程(一)之 Executor
> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址
精盡MyBatis原始碼分析 - SQL執行過程(二)之 StatementHandler
> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址
精盡MyBatis原始碼分析 - SQL執行過程(三)之 ResultSetHandler
> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址
精盡MyBatis原始碼分析 - SQL執行過程(四)之延遲載入
> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址
mysql優化–explain分析sql語句執行效率
Explain命令在解決資料庫效能上是第一推薦使用命令,大部分的效能問題可以通過此命令來簡單的解決,Explain可以用來檢視SQL語句的執行效 果,可以幫助選擇更好的索引和優化查詢語句,寫出更好的優化語句。 Explain語法:explain select … from …
Mysql explain分析sql語句執行效率
mysql優化–explain分析sql語句執行效率 Explain命令在解決資料庫效能上是第一推薦使用命令,大部分的效能問題可以通過此命令來簡單的解決,Explain可以用來檢視SQL語句的執行效 果,可以幫助選擇更好的索引和優化查詢語句,寫出更好的優化語句。 Explain語法:explain sel
Explain分析sql語句執行效率
使用Explain命令會有以下屬性輸出: 1》id:這是SELECT的查詢序列號 2》select_type:select_type就是select的型別: &n
mysql優化(三)–explain分析sql語句執行效率
mushu 釋出於 11個月前 (06-04) 分類:Mysql 閱讀(651) 評論(0) Explain命令在解決資料庫效能上是第一推薦使用命令,大部分的效能問題可以通過此命令來簡單的解決,Explain可以用來檢視SQL語句的執行效 果,可以幫助選擇更好的索引和優化查詢語句,寫出
實現分析sql語句執行過程和編譯時間的方法
有時候我們經常為我們的sql語句執行效率低下發愁,反覆優化後,可還是得不到提高。 那麼你就用這條語句找出你sql到底是在哪裡慢了 示例: SET STATISTICS io ON SET STATISTICS time ON
精盡MyBatis原始碼分析 - SqlSession 會話與 SQL 執行入口
> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址
Mybatis原始碼分析(3)—— 從Mybatis的視角去看Bean的初始化流程
不涉及Spring完整的啟動流程,僅僅從Mybatis的視角去分析幾個關鍵的方法,找到Mybatis是如何通過這幾個擴充套件點植入進去的,反過來看Spring是如何設計,埋下這些伏筆,實現其可擴充套件性。 springContext-mybatis.xml的配置: <!--
MyBatis學習總結(六)---使用log4j2將sql語句執行記錄輸出控制檯和檔案中
在上一篇部落格中我簡單的介紹了在MyBatis中如何使用日誌,並給出了一個在MyBatis中使用log4j的示例。 MyBatis中日誌的使用及使用log4j示例 下面介紹在MyBatis中如何使用log4j2將sql語句執行記錄輸出控制
標準sql語句執行分析
標準sql語句執行分析 SELECT count() as “人數” ,round(avg(reseau),2) as “平均工資” FROM ( select t.,serial,shopaddress ,rownum as rn FROM ( SELECT openid,account,
MyBatis原始碼分析---呼叫SqlSession增刪改查到底執行了什麼步驟
SqlSession建立流程: sqlSession可謂是MyBatis中最為核心的物件,相當於與資料庫的一次會話,是程式和資料庫的橋樑。 SqlSession物件由SqlSessionFactory工廠物件建立 SqlSessionFactory工廠物件提供了一大堆過
從原始碼分析struts框架執行流程
struts 原始碼解析 ActionServlet 的執行流程 •Tomcat 及封裝配置 2 // web.xml 檔案的 標籤,配置則伺服器啟動則建立ActionServlet,否則訪問時建立 Tomcat 一啟動就將 web.xml 檔案讀取到記憶體,
springmvc 專案完整示例04 整合mybatis mybatis所需要的jar包 mybatis配置檔案 sql語句 mybatis應用
百度百科: MyBatis 本是apache的一個開源專案iBatis, 2010年這個專案由apache software foundation 遷移到了google code,並且改名為MyBatis 。2013年11月遷移到Github。 iBATIS一詞來源於“internet”和“abatis”
通過分析SQL語句的執行計劃優化SQL(總結)
如何幹預執行計劃 - - 使用hints提示 基於代價的優化器是很聰明的,在絕大多數情況下它會選擇正確的優化器,減輕了DBA的負擔。但有時它也聰明反被聰明誤,選擇了很差的執行計劃,使某個語句的執行變得奇慢無比。此時就需要DBA進行人為的干預,告訴優化器使用我們指定的存取路徑或連線型別生成執行計劃,從而使
SprignMVC+myBatis整合+mybatis原始碼分析+動態代理實現流程+如何根據mapper介面生成其實現類
首先熟悉三個概念: SqlSessionFactoryBean –為整合應用提供SqlSession物件資源 MapperFactoryBean –根據指定的Mapper介面生成Bean例項 MapperScannerConfigurer –根據指定
mybatis ${}導致mysql資料庫按照SQL語句執行的和程式執行的結果不一致
今天遇到一個問題,某段執行SQL的程式在我這裡執行正常,但是到了到了我同事那裡就不正常。我倆連的不是同一個資料庫地址,但是資料庫的內容是一致的。後來把xml檔案裡面處理引數的${}換成#{}就正常了,雖然問題解決了但是原因還是不知道。