前言 SqlSession是Mybatis最重要的構建之一,可以簡單的認為Mybatis一系列的配置目的是生成類似 JDBC生成的Connection對象的SqlSession對象,這樣才能與資料庫開啟“溝通”,通過SqlSession可以實現增刪改查(當然現在更加推薦是使用Mapper介面形式), ...
前言
SqlSession是Mybatis最重要的構建之一,可以簡單的認為Mybatis一系列的配置目的是生成類似 JDBC生成的Connection對象的SqlSession對象,這樣才能與資料庫開啟“溝通”,通過SqlSession可以實現增刪改查(當然現在更加推薦是使用Mapper介面形式),那麼它是如何執行實現的,這就是本篇博文所介紹的東西,其中會涉及到簡單的源碼講解。
瞭解SqlSession的運作原理是學習Mybatis插件的必經之路,因為Mybatis的插件會在SqlSession運行過程中“插入”運行,如果沒有很好理解的話,Mybatis插件可能會覆蓋相應的源碼造成嚴重的問題。鑒於此,本篇博文儘量詳細介紹SqlSession運作原理!
建議:在我之前的博文《Mybatis緩存(1)--------系統緩存及簡單配置介紹》中介紹到SqlSession的產生過程,可以先理解後再讀此博文可能會更加好理解!
註:本篇博文也是我最近真正理解Mybatis才開始編寫的,可能有些地方不太準確,如果有錯誤之處敬請指出,另外創作不易,望轉載告之,謝謝!
參數資料:《深入淺出Mybatis基礎原理與實踐》(我這裡只有電子版PDF,需要的朋友可以聯繫我)
1、SqlSession簡單介紹
(1)SqlSession簡單原理介紹
SqlSession提供select/insert/update/delete方法,在舊版本中使用使用SqlSession介面的這些方法,但是新版的Mybatis中就會建議使用Mapper介面的方法。
映射器其實就是一個動態代理對象,進入到MapperMethod的execute方法就能簡單找到SqlSession的刪除、更新、查詢、選擇方法,從底層實現來說:通過動態代理技術,讓介面跑起來,之後採用命令模式,最後還是採用了SqlSession的介面方法(getMapper()方法等到Mapper)執行SQL查詢(也就是說Mapper介面方法的實現底層還是採用SqlSession介面方法實現的)。
註:以上雖然只是簡單的描述,但實際上源碼相對複雜,下麵將結合源碼進行簡單的介紹!
(2)SqlSession重要的四個對象
1)Execute:調度執行StatementHandler、ParmmeterHandler、ResultHandler執行相應的SQL語句;
2)StatementHandler:使用資料庫中Statement(PrepareStatement)執行操作,即底層是封裝好了的prepareStatement;
3)ParammeterHandler:處理SQL參數;
4)ResultHandler:結果集ResultSet封裝處理返回。
2、SqlSession四大對象
(1)Execute執行器:
執行器起到至關重要的作用,它是真正執行Java與資料庫交互的東西,參與了整個SQL查詢執行過程中。
1)主要有三種執行器:簡易執行器SIMPLE(不配置就是預設執行器)、REUSE是一種重用預處理語句、BATCH批量更新、批量專用處理器
package org.apache.ibatis.session; /** * @author Clinton Begin */ public enum ExecutorType { SIMPLE, REUSE, BATCH }
2)執行器作用:Executor會先調用StatementHandler的prepare()方法預編譯SQL語句,同時設置一些基本的運行參數,然後調用StatementHandler的parameterize()方法(實際上是啟用了ParameterHandler設置參數)設置參數,resultHandler再組裝查詢結果返回調用者完成一次查詢完成預編譯,簡單總結起來就是即先預編譯SQL語句,之後設置參數(跟JDBC的prepareStatement過程類似)最後如果有查詢結果就會組裝返回。
首先,以SimpleExecutor為例,查看源碼我們得到如下幾點重要知識點:
第一:Executor通過Configuration對象中newExecutor()方法中選擇相應的執行器生成
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
(註:最後interceptorChain.pluginAll()中執行層層動態代理,最後在可以在調用真正的Executor前可以修改插件代碼,這也就是為什麼學會Mybatis的插件必須要知道SqlSession的運行過程)
第二:在執行器中StatementHandler是根據Configuration構建的
public SimpleExecutor(Configuration configuration, Transaction transaction) { super(configuration, transaction); } @Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt); } }
第三:Executor會執行StatementHandler的prepare()方法進行預編譯---->填入connection對象等參數---->再調用parameterize()方法設置參數---->完成預編譯
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; }
總結以上繪製簡單思維圖如下:
(2)StatementHanlder資料庫會話器
1)作用:簡單來說就是專門處理資料庫會話。詳細來說就是進行預編譯並且調用ParameterHandler的setParameters()方法設置參數。
2)資料庫會話器主要有三種:SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler,分別對應Executor的三種執行器(SIMPLE、REUSE、BATCH)
我們從上述Executor的prepareStatement()方法中調用了StatementHandler的parameterize()開始一步步地查看源碼,如下得到幾點重要的知識點:
第一:StatementHandler的生成是由Configuration方法中newStatementHandler()方法生成的,但是正在創建的是實現了StatementHandler介面的RoutingStatementHandler對象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
第二:RoutingStatementHandler的通過適配器模式找到對應(根據上下文)的StatementHandler執行的,並且有SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler,分別對應Executor的三種執行器(SIMPLE、REUSE、BATCH)
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: 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()); }
之後主要以PrepareStatementHandler為例,我們觀察到:它是實現BaseStatementHandler介面的,最後BaseStatementHandler又是實現StatementHandler介面的
public class PreparedStatementHandler extends BaseStatementHandler ...... public abstract class BaseStatementHandler implements StatementHandler
它主要有三種方法:prepare、parameterize和query,我們查看源碼:
第三:在BaseStatementHandler中重寫prepare()方法,instantiateStatement()方法完成預編譯,之後設置一些基礎配置(獲取最大行數,超時)
@Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { statement = instantiateStatement(connection); 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); } }
第四:instantiateStatement()預編譯實際上也是使用了JDBC的prepareStatement()完成預編譯
@Override protected Statement instantiateStatement(Connection connection) throws SQLException { String sql = boundSql.getSql(); if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { String[] keyColumnNames = mappedStatement.getKeyColumns(); if (keyColumnNames == null) { return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); } else { return connection.prepareStatement(sql, keyColumnNames); } } else if (mappedStatement.getResultSetType() != null) { return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } else { return connection.prepareStatement(sql); } }
第五:在prepareStatement中重寫parameterize()方法。prepare()預編譯完成之後,Executor會調用parameterize()方法(在上面的Executor部分中已經做了介紹),實際上是調用ParameterHandler的setParameters()方法
@Override public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); }
(3)ParameterHandler參數處理器
作用:對預編譯中參數進行設置,如果有配置typeHandler,自然會對註冊的typeHandler對參數進行處理
查看並學習源碼,得到以下幾點重要知識點:
第一:Mybatis提供了ParamterHandler的預設實現類DefalutParameterHandler
public interface ParameterHandler { Object getParameterObject(); void setParameters(PreparedStatement ps) throws SQLException; }
(其中:getParameterObject是返回參數對象,setParameters()是設置預編譯參數)
第二:從parameterObject中取到參數,然後使用typeHandler(註冊在Configuration中)進行參數處理:
@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); } } } } }
(4)ResultSetHandler結果集處理器
作用:很簡單,就是組裝結果返回結果集
第一:ResultSetHandler介面,handlerResultSets()是包裝並返回結果集的,handleOutputParameters()是處理存儲過程輸出參數的
public interface ResultSetHandler { <E> List<E> handleResultSets(Statement stmt) throws SQLException; <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException; void handleOutputParameters(CallableStatement cs) throws SQLException;
第二:Mybatis提供了預設的ResultSetHandler實現類DefaultResultSetHandler,其中重點是handlerResultSets()的實現,但是其實現過程比較複雜,這裡不過多介紹(emmmmm....個人目前能力還達理解,仍需努力)
第三:在Executor中doQuery()方法返回了封裝的結果集
@Override 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); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
第四:實際上是返回結果是調用了resultSetHandler的handleResultSets()方法
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
3、SqlSession運行總結
(1)文字總結
SqlSession的運行主要是依靠Executor執行器調用(調度)StatementHandler、parameterHanlder、ResultSetHandler,Executor首先通過創建StamentHandler執行預編譯並設置參數運行,而整個過程需要如下幾步才能完成:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; }
1)prepare預編譯SQL
由適配模式生成的RoutingStatementHandler根據上下文選擇生成三種相應的XXXStatementHandler;
在生成的XXXStatementHandler內部instantiateStatement()方法執行底層JDBC的prepareStatement()方法完成預編譯
2)parameterize設置參數
預設是DefaultParameterHandler(實現了parameterHandler介面)中setParameter()方法完成參數配置,其中參數從ParameterObject中取出,交給typeHandler處理
3)doUpdate/doQuery執行SQL
返回的結果通過預設的DefaultResultSetHandler(實現了ResultSetHandler介面)封裝
(2)運行圖總結
1)SqlSession內部總運行圖
2)prepare()方法運行圖: 3)parameterize()方法運行圖