MyBatis 學習筆記(七)---源碼分析篇---SQL的執行過程(一)

      網友投稿 1001 2025-03-31

      前言


      接上一篇,今天我們接著來分析MyBatis的源碼。今天的分析的核心是SQL的執行過程。主要分為如下章節進行分析

      代理類的生成

      SQL的執行過程

      處理查詢結果

      mapper 接口的代理類的生成過程分析

      首先我們來看看mapper 接口的代理類的生成過程,如下是一個MyBatis查詢的調用實例。

      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List studentList = mapper.selectByName("點點");

      1

      2

      上述方法sqlSession.getMapper(StudentMapper.class) 返回的其實是StudentMapper的代理類。

      接著我們來看看調用的時序圖。

      如上時序圖我們可知,接口的代理類(MapperProxy)最終由MapperProxyFactory通過JDK動態代理生成。接著我們一步步分析下。

      //DefaultSqlSession @Override public T getMapper(Class type) { //最后會去調用MapperRegistry.getMapper return configuration.getMapper(type, this); }

      1

      2

      3

      4

      5

      6

      如上,DefaultSqlSession直接請求拋給Configuration。

      //Configuration public T getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }

      1

      2

      3

      4

      同樣,Configuration也是一個甩手掌柜,將請求直接拋給了MapperRegistry 這個接盤俠。

      接下來我們來看看接盤俠MapperRegistry。

      //*MapperRegistry public T getMapper(Class type, SqlSession sqlSession) { final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      如上,在MapperRegistry的getMapper的方法中,首先根據配置的Mapper 獲取其對應的MapperProxyFactory。接著調用newInstance方法返回MapperProxy。最后我們來看看MapperProxyFactory

      //*MapperProxyFactory protected T newInstance(MapperProxy mapperProxy) { //用JDK自帶的動態代理生成映射器 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      如上,通過JDK自帶的動態代理生成映射器,PS: JDK 動態代理需要接口。

      分析完了MapperProxy的生成過程,接下來我們來分析下SQL的執行過程。

      SQL的執行過程

      SQL 的執行過程是從MapperProxy的invoke方法開始。按照慣例我們還是先看看相關的時序圖。

      如上圖,在MapperProxy的invoke方法里調用了MapperMethod的execute方法,該方法是真正執行SQL,返回結果的方法。接下來我們來看看。

      //*MapperProxy public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //代理以后,所有Mapper的方法調用時,都會調用這個invoke方法 //并不是任何一個方法都需要執行調用代理對象進行執行,如果這個方法是Object中通用的方法(toString、hashCode等)無需執行 if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } //這里優化了,去緩存中找MapperMethod final MapperMethod mapperMethod = cachedMapperMethod(method); //執行 return mapperMethod.execute(sqlSession, args); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      如上,這里MyBatis做了個優化,如果緩存中有MapperMethod,則取緩存中的,如果沒有則new一個MapperMethod實例。

      //*MapperProxy //去緩存中找MapperMethod private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { //找不到才去new mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }

      1

      2

      3

      4

      5

      MyBatis 學習筆記(七)---源碼分析篇---SQL的執行過程(一)

      6

      7

      8

      9

      10

      11

      我們接著來看看MapperMethod 中的execute方法,該方法主要是通過區分各種CURD操作(insert|update|delete|select),分別調用sqlSession中的4大類方法。源碼如下:

      public Object execute(SqlSession sqlSession, Object[] args) { Object result; //可以看到執行時就是4種情況,insert|update|delete|select,分別調用SqlSession的4大類方法 if (SqlCommandType.INSERT == command.getType()) { // 對用戶傳入的參數進行轉換,下同 Object param = method.convertArgsToSqlCommandParam(args); // 執行插入操作,rowCountResult方法用于處理返回值 result = rowCountResult(sqlSession.insert(command.getName(), param)); } else if (SqlCommandType.UPDATE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); } else if (SqlCommandType.DELETE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); } // 根據目標返回方法的返回類型進行相應的查詢操作。 else if (SqlCommandType.SELECT == command.getType()) { if (method.returnsVoid() && method.hasResultHandler()) { /* 如果方法返回值為void,但參數列表中包含ResultHandler, 想通過ResultHandler的方式獲取查詢結果,而非通過返回值獲取結果 * */ executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { //如果結果有多條記錄 result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { //如果結果是map result = executeForMap(sqlSession, args); } else { //否則就是一條記錄 Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } } else { throw new BindingException("Unknown execution method for: " + command.getName()); } // 如果方法的返回值是基本類型,而返回值卻為null,此種情況下應拋出異常 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }

      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

      如上,代碼注釋比較詳細。前面也說過了,不同的操作調用sqlSession中不同的方法。這里我重點分析下查詢操作。查詢的情況分為四種:

      返回值為空

      返回多條記錄

      返回map

      返回單條記錄。

      返回值為空的情況下,直接返回 result 為null。其余幾種情況內部都調用了sqlSession 中的selectList 方法。下面我就以返回單條記錄為例進行分析。

      //DefaultSqlSession public T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. //轉而去調用selectList,很簡單的,如果得到0條則返回null,得到1條則返回1條,得到多條報TooManyResultsException錯 // 特別需要主要的是當沒有查詢到結果的時候就會返回null。因此一般建議在mapper中編寫resultType的時候使用包裝類型 //而不是基本類型,比如推薦使用Integer而不是int。這樣就可以避免NPE List list = this.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; } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      如果所示,如果selectList查詢返回1條,則直接返回,如果返回多條則拋出異常,否則直接返回null。我們接著往下看.

      //DefaultSqlSession public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { //根據statement id找到對應的MappedStatement MappedStatement ms = configuration.getMappedStatement(statement); //轉而用執行器來查詢結果,注意這里傳入的ResultHandler是null 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

      如上,在selectList 內部最終調用的是SimpleExecutor (執行器)的query方法來執行查詢結果。我們接著往下找

      根據類圖我們不難發現SimpleExecutor是BaseExecutor類的子類。在BaseExecutor 類中我們找到了query 方法。

      public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //得到綁定sql BoundSql boundSql = ms.getBoundSql(parameter); //創建緩存Key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //查詢 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }

      1

      2

      3

      4

      5

      6

      7

      8

      該方法主要有兩步,

      得到綁定的SQL,

      調用其重載query方法。

      綁定SQL的過程,我們稍后分析。我們接著來看看其重載的query方法。

      public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ····· 省略部分代碼 try { //加一,這樣遞歸調用到上面的時候就不會再清局部緩存了 queryStack++; //先根據cachekey從localCache去查 list = resultHandler == null ? (List) localCache.getObject(key) : null; if (list != null) { //若查到localCache緩存,處理localOutputParameterCache handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //從數據庫查 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } ···· 省略部分代碼 } return list; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      該方法核心的步驟是,首先根據cacheKey 從localCache 中去查,如果不為空的話則直接取緩存的,否則查詢數據庫。我們主要看看查詢數據庫的queryFromDatabase方法。

      //BaseExecutor //從數據庫查 private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; //先向緩存中放入占位符 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 調用doQuery進行查詢 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { //最后刪除占位符 localCache.removeObject(key); } //加入緩存 localCache.putObject(key, list); //如果是存儲過程,OUT參數也加入緩存 if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; } //query-->queryFromDatabase-->doQuery protected abstract List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      此處doQuery方法是抽象方法,定義了模板供子類實現。此處用到了模板模式。

      首先,此方法首先調用doQuery方法執行查詢,然后將查詢的結果放入緩存中。

      接著我們再來看看SimpleExcutor中的doQuery方法。

      //*SimpleExcutor public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //新建一個StatementHandler //這里看到ResultHandler傳入了 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //準備語句 stmt = prepareStatement(handler, ms.getStatementLog()); //StatementHandler.query(實際調用的是PreparedStatementHandler) return handler.query(stmt, resultHandler); } finally { // 關閉statement closeStatement(stmt); } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      如上,該方法主要有三步:

      新建一個StatementHandler

      獲取Statement

      StatementHandler.query(實際調用的是PreparedStatementHandler)獲取查詢結果。

      第一步比較簡單,我們首先來看看第二步

      //*SimpleExcutor private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 獲取數據庫連接 Connection connection = getConnection(statementLog); //創建Statement stmt = handler.prepare(connection); //為Statement設置IN參數 handler.parameterize(stmt); return stmt; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      對于prepareStatement方法里的相關步驟,相信大家都不會陌生。獲取數據庫連接,創建Statement; 為Statement設置IN參數。都是我們非常熟悉的。我們接著看看第三步。

      public List query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 執行SQL ps.execute(); // 處理執行結果 return resultSetHandler. handleResultSets(ps); }

      1

      2

      3

      4

      5

      6

      7

      這一步到了最終的執行鏈。還是先執行SQL,然后處理執行結果。限于篇幅,在此不展開分析了。

      總結

      本文通過兩個時序圖,為主線來展開分析了Mapper接口代理類的生成過程,以及SQL的執行過程。希望對大家有所幫助。

      MyBatis SQL

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:電動車電池品牌有哪些技術派走俏市場
      下一篇:excel制作曲線圖的方法步驟(excel作曲線圖教程)
      相關文章
      精品韩国亚洲av无码不卡区| 亚洲av无码偷拍在线观看| 亚洲国产成人精品女人久久久| 精品丝袜国产自在线拍亚洲| 亚洲成aⅴ人在线观看| 亚洲精品影院久久久久久| 亚洲成在人线av| 久久久久久a亚洲欧洲aⅴ| 亚洲啪啪AV无码片| 亚洲VA中文字幕无码毛片| 亚洲精品乱码久久久久久中文字幕| 国产aⅴ无码专区亚洲av麻豆| 亚洲国产婷婷香蕉久久久久久| 亚洲成AⅤ人影院在线观看| 亚洲国产日韩成人综合天堂| 亚洲精品国产V片在线观看| 亚洲精品99久久久久中文字幕| 亚洲一区二区三区国产精品| 国内精品99亚洲免费高清| 亚洲三区在线观看无套内射| 国产AV无码专区亚洲AV男同 | 亚洲精品无码永久在线观看你懂的| 亚洲乱亚洲乱妇无码麻豆| 亚洲精品国产精品乱码视色| 久久精品视频亚洲| 日本久久久久亚洲中字幕| 亚洲日本在线观看网址| 亚洲ts人妖网站| 亚洲精品无码专区在线| 在线观看亚洲免费| 亚洲中文无韩国r级电影| 亚洲人成无码网站| 亚洲欧洲日韩不卡| 亚洲图片中文字幕| 亚洲欧美日韩国产成人| 国产99久久亚洲综合精品| 亚洲一区二区三区自拍公司| 亚洲av日韩av激情亚洲| 亚洲黄色网址在线观看| 亚洲伊人久久大香线蕉影院| 亚洲成在人线aⅴ免费毛片|