一、JDBC執行過程
1.1預編譯的三種執行器
簡單執行器(Statement)存在sql注入問題,發送一條一條靜態sql語句(包含參數),傳輸體量比較大。
預處理執行器(PreparedStatement)可以防止sql注入問題,發送一條sql語句包含若干組參數,傳輸體量比較小。
存儲過程處理器(CallableStatement)支持調用存儲過程,提供了對輸出和輸入/輸出參數(INOUT)的支持。
1.2簡單執行器與預處理執行器的區別?
1)簡單執行器
- 執行靜態sql存在sql注入問題,不需要設置參數。
- 相同的sql查詢參數不同時會組裝成不同參數的多條靜態sql進行多次發送,從而多次編譯多次執行。
2)預處理執行器
- 執行動態sql可以防止sql注入問題,需要設置預編譯參數。
- 相同的sql查詢參數不同時會組裝成一條動態sql,多組不同參數進行發送,從而一次編譯多次執行。
二、mybatis執行過程
SqlSession設計采用的是門面模式-----方便調用。
這里主要講述一下mybatis執行器
2.1mybatis的Executor體系
CachingExecutor類中是MyBatis關於二級緩存相關的邏輯。
BaseExecutor抽象類中是MyBatis關於一級緩存相關的邏輯。
SimpleExecutor、ReuseExecutor、BatchExecutor是繼承自BaseExecutor具體的執行器類,具體執行數據庫的增刪改查。
1)SimpleExecutor簡單執行器
簡單執行每次都會創建一個新的預處理器(PrepareStatement),廢話不多說上源碼分析
這里先介紹一個類ConnectionLogger,該類是mybatis調用jdbc三種預處理器Statement進行交互的一個類(后面會介紹mybatis是如何調用jdbc框架的)。
主要代碼如下:
2)ReuseExecutor可重用執行器
相同的sql只進行一次預處理,執行結果多次使用,廢話不多說上源碼分析
由以下源碼可知:ReuseExecutor通過屬性statementMap 緩存Statement來實現可重用性。
1 public class ReuseExecutor extends BaseExecutor { 2 private final Map<String, Statement> statementMap = new HashMap(); 3 4 5 //判斷如果Statement存在了直接從緩存中獲取,若不存在創建並添加到緩存中 6 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { 7 BoundSql boundSql = handler.getBoundSql(); 8 String sql = boundSql.getSql(); 9 Statement stmt; 10 if (this.hasStatementFor(sql)) { 11 stmt = this.getStatement(sql); 12 this.applyTransactionTimeout(stmt); 13 } else { 14 Connection connection = this.getConnection(statementLog); 15 stmt = handler.prepare(connection, this.transaction.getTimeout()); 16 this.putStatement(sql, stmt); 17 } 18 handler.parameterize(stmt); 19 return stmt; 20 } 21 //判斷緩存是否存在且有效 22 private boolean hasStatementFor(String sql) { 23 try {//緩存存在且緩存未失效(會話未關閉) 24 return this.statementMap.keySet().contains(sql) && !((Statement)this.statementMap.get(sql)).getConnection().isClosed(); 25 } catch (SQLException var3) { 26 return false; 27 } 28 } 29 30 private Statement getStatement(String s) { 31 return (Statement)this.statementMap.get(s); 32 } 33 34 private void putStatement(String sql, Statement stmt) { 35 this.statementMap.put(sql, stmt); 36 } 37 }
3)BatchExecutor批處理執行器
BatchExecutor批處理執行器顧名思義支持批量處理,廢話不多說上源碼分析
由以下源碼可知,BatchExecutor通過屬性statementList存儲Statement和屬性batchResultList存儲返回的結果集BatchResult(此時還沒有真正填充結果集,只是new了一個對象),從而實現批量處理的功能。
三、mybatis究竟是如何調用jdbc的呢(那我們來探究一下)?
在此之前我們要先大致介紹一下StatementHandler體系
下面我們主要看一下RoutingStatementHandler類的源碼
由以下源碼可知該類是通過MappedStatement的屬性statementType來判斷mybatis執行時使用的哪個Handler
假設MyBatis使用的SimpleStatementHandler執行器,MappedStatement的statementType屬性值為PREPARED,我們以此為例進行講解MyBatis具體調用JDBC預處理器Statement的流程
首先MyBatis通過MappedStatement配置的statementType屬性值PREPARED,由RoutingStatementHandler路由類分配得到PreparedStatementHandler,而后執行PreparedStatementHandler的instantiateStatement()方法具體代碼如下:
進一步走入ConnectionLogger代理類的invoke()方法,會根據上一步的方法名創建對應的PreparedStatement
此處ConnectionLogger類是一個代理類,其主要功能是對instantiateStatement()方法中調用的prepareStatement、prepareCall、createStatement方法進行判斷分別創建jdbc對應的Statement的代理日志類,完成mybatis與jdbc的交互。
以上各個Statement的代理日志類其核心功能還是Statement對象,只是通過代理模式增強添加了日志打印;由此,可以借鑒系統 與系統或接口與接口之間的交互在某種程度上可以使用代碼模式進行交互以方便增強添加日志打印監控功能。