mybatis delete返回值_面试:谈谈你对MyBatis执行过程之SQL执行过程理解
前言
在了解了MyBatis初始化加載過程后,我們也應該研究看看SQL執行過程是怎樣執行?這樣我們對于Mybatis的整個執行流程都熟悉了,在開發遇到問題也可以很快定位到問題。
更重要的,在面試中遇到面試官咨詢Mybatis的知識點的時候,可以很順暢的把這一套流程講出來,面試也會覺得你已掌握Mybatis知識點了。
SQL執行過程簡介
經過MyBatis初始化加載Sql執行過程所需的信息后,我們就可以通過 SqlSessionFactory 對象得倒忙SqlSession ,然后執行 SQL 語句了,接下來看看Sql執行具體過程,SQL大致執行流程圖如下所示:
接下來我們來看看每個執行鏈路中的具體執行過程,
SqlSession
SqlSession 是 MyBatis 暴露給外部使用的統一接口層,通過 SqlSessionFactory 創建,且其包含和數據庫打交道所有操作接口。
下面通過時序圖描述 SqlSession 對象的創建流程:
在生成 SqlSession 的同時,基于 executorType 初始化好 Executor 實現類。
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;}最頂層的SqlSession接口已生成,那我們可以來看看sql的執行過程下一步是怎樣的呢? 怎樣使用代理類 MapperProxy 。
MapperProxy
MapperProxy 是 Mapper 接口與SQL 語句映射的關鍵,通過 MapperProxy 可以讓對應的 SQL 語句跟接口進行綁定的,具體流程如下:
- MapperProxy 代理類生成流程
- MapperProxy 代理類執行操作
MapperProxy 代理類生成流程
其中, MapperRegistry 是 Configuration 的一個屬性,在解析配置時候會在 MapperRegistry 中緩存了 MapperProxyFactory 的 knownMappers 變量 Map 集合。
MapperRegistry 會根據 mapper 接口類型獲取已緩存的 MapperProxyFactory , MapperProxyFactory 會基于 SqlSession 來生成 MapperProxy 代理對象,
public T getMapper(Class type, SqlSession sqlSession) { MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } }當調用 SqlSession 接口時, MapperProxy 怎么是實現的呢? MyBatis 的 Mapper 接口 是通過動態代理實現的,調用 Mapper 接口的任何方法都會執行 MapperProxy::invoke() 方法,
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //Object類型執行 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); }//接口默認方法執行 if (method.isDefault()) { if (privateLookupInMethod == null) { return this.invokeDefaultMethodJava8(proxy, method, args); } return this.invokeDefaultMethodJava9(proxy, method, args); } } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); }但最終會調用倒忙mapperMethod::execute() 方法執行,主要是判斷是 INSERT 、 UPDATE 、 DELETE 、 SELECT 語句去操作,其中如果是查詢的話,還會判斷返回值的類型。
public Object execute(SqlSession sqlSession, Object[] args) { Object result; Object param; switch(this.command.getType()) { case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); } if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }通過以上的分析,總結出
- Mapper 接口實際對象為代理對象 MapperProxy ;
- MapperProxy 繼承 InvocationHandler ,實現 invoke 方法;
- MapperProxyFactory::newInstance() 方法,基于 JDK 動態代理的方式創建了一個 MapperProxy 的代理類;
- 最終會調用倒忙mapperMethod::execute() 方法執行,完成操作。
- 而且更重要一點是, MyBatis 使用的動態代理和普遍動態代理有點區別,沒有實現類,只有接口,MyBatis 動態代理類圖結構如下所示:
以 SELECT 為例, 調用會 SqlSession ::selectOne() 方法。繼續往下執行,會執行 Executor::query() 方法。
public List selectList(String statement, Object parameter, RowBounds rowBounds) { List var5; try { MappedStatement ms = this.configuration.getMappedStatement(statement); var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception var9) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9); } finally { ErrorContext.instance().reset(); } return var5; }執行倒忙Executor 類,那么我們來看看其究竟有什么?
E x ecutor
Executor 對象為SQL 的執行引擎,負責增刪改查的具體操作,頂層接口 SqlSession 中都會有一個 Executor 對象,可以理解為 JDBC 中 Statement 的封裝版。
Executor 是最頂層的是執行器,它有兩個實現類,分別是 BaseExecutor 和 CachingExecutor
- BaseExecutor 是一個抽象類,實現了大部分 Executor 接口定義的功能,降低了接口實現的難度。 BaseExecutor 基于適配器設計模式之接口適配會有三個子類,分別是 SimpleExecutor 、 ReuseExecutor 和 BatchExecutor 。
- BatchExecutor : 批處理執行器,用于執行update(沒有select,JDBC批處理不支持select將多個 SQL 一次性輸出到數據庫,
- ReuseExecutor : 可重用執行器, 執行 update 或 select ,以sql作為 key 查找 Statement 對象,存在就使用,不存在就創建,用完后,不關閉 Statement 對象,而是放置于 Map 內,供下一次使用。簡言之,就是重復使用 Statement 對象
- SimpleExecutor : 是 MyBatis 中 默認 簡單執行器,每執行一次 update 或 select ,就開啟一個 Statement 對象,用完立刻關閉 Statement 對象
- CachingExecutor : 緩存執行器,為 Executor 對象增加了 二級緩存 的相關功:先從緩存中查詢結果,如果存在就返回之前的結果;如果不存在,再委托給 Executor delegate 去數據庫中取, delegate 可以是上面任何一個執行器。
在 Mybatis 配置文件中,可以指定默認的 ExecutorType 執行器類型,也可以手動給 DefaultSqlSessionFactory 的創建 SqlSession 的方法傳遞 ExecutorType 類型參數。
看完 Exector 簡介之后,繼續跟蹤執行流程鏈路分析, SqlSession 中的 JDBC 操作部分最終都會委派給 Exector 實現, Executor::query() 方法,看看在 Exector 的執行是怎樣的?
每次查詢都會先經過 CachingExecutor 緩存執行器, 會先判斷二級緩存中是否存在查詢 SQL ,如果存在直接從二級緩存中獲取,不存在即為第一次執行,會直接執行SQL 語句,并創建緩存,都是由 CachingExecutor::query() 操作完成的。
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql); return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //獲取查詢語句對應的二級緩存 Cache cache = ms.getCache(); //sql查詢是否存在在二級緩存中 if (cache != null) { //根據 節點的配置,判斷否需要清空二級緩存 this.flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, boundSql); //查詢二級緩存 List list = (List)this.tcm.getObject(cache, key); if (list == null) { //二級緩存沒用相應的結果對象,調用封裝的Executor對象的 query() 方法 list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //將查詢結果保存到二級緩存中 this.tcm.putObject(cache, key, list); } return list; } }//沒有啟動二級緩存,直接調用底層 Executor 執行數據數據庫查詢操作 return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }如果在經過 CachingExecutor 緩存執行器(二級緩存)沒有返回值的話,就會執行 BaseExecutor 以及其的實現類,默認為 SimpleExecutor ,首先會在一級緩存中獲取查詢結果,獲得不到,最終會通過 SimpleExecutor:: () 去數據庫中查詢。
public List 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 (this.closed) { throw new ExecutorException("Executor was closed."); } else { //是否清除本地緩存 if (this.queryStack == 0 && ms.isFlushCacheRequired()) { this.clearLocalCache(); } List list; try { ++this.queryStack; //從一級緩存中,獲取查詢結果 list = resultHandler == null ? (List)this.localCache.getObject(key) : null; //獲取到結果,則進行處理 if (list != null) { this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //獲得不到,則從數據庫中查詢 list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; } if (this.queryStack == 0) { //執行延遲加載 Iterator var8 = this.deferredLoads.iterator(); while(var8.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next(); deferredLoad.load(); } this.deferredLoads.clear(); if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; } }那么 SimpleExecutor::doQuery() 如何去數據庫中查詢獲取到結果呢? 其實執行到這邊mybatis的執行過程就從 Executor 轉交給 StatementHandler 處理,
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; List var9; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = this.prepareStatement(handler, ms.getStatementLog()); var9 = handler.query(stmt, resultHandler); } finally { this.closeStatement(stmt); } return var9; }這樣我們的執行鏈路分析已到 StatementHandler 了,現在讓我們去一探究竟其原理
StatementHandler
StatementHandler 負責處理 Mybatis 與JDBC之間 Statement 的交互,即 Statement 對象與數據庫進行交互,其為頂級接口,有4個實現類,其中三個是 Statement 對象與數據庫進行交互類, 另外一個是路由功能的,
- RoutingStatementHandler : 對 Statement 對象沒有實際操作,主要負責另外三個 StatementHandler 的創建及調用, 而且在 MyBatis 執行時,使用的 StatementHandler 接口對象實際上就是 RoutingStatementHandler 對象。
- SimpleStatementHandler : 管理 Statement 對象, 用于簡單SQL的處理 。
- PreparedStatementHandler : 管理 Statement 對象,預處理SQL的接口 。
- CallableStatementHandler :管理 Statement 對象,用于執行存儲過程相關的接口 。
在經歷過 Executor 后,基于初始化加載到 MapperState 中的 StatementType 的類型通 過Configuration.newStatementHandler() 方法中的 RoutingStatementHandler 生成 StatementHandler 實際處理類。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch(ms.getStatementType()) { case STATEMENT: this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } }現在先以 PreparedStatementHandler 預處理為例,接著Sql的執行鏈路來分析, StatementHandler::query() 到 StatementHandler::execute() 真正執行Sql查詢操作。
public List query(Statement statement, ResultHandler resultHandler) throws SQLException { String sql = boundSql.getSql(); statement.execute(sql); return resultSetHandler.handleResultSets(statement);}但執行真正查詢操作之前,還進行哪些處理呢? 還會進行 ParameterHandler 對 SQL 參數的預處理: 對參數進行動態Sql映射,那么 ParameterHandler 又如何實現對參數進行動態映射的呢?
ParameterHandler
ParameterHandler 參數處理器, 用來設置參數規則的,負責為sql 語句參數動態賦值,其有兩個接口
- getParameterObject:用于讀取參數
- setParameters: 用于對 PreparedStatement 的參數賦值
當 SimpleExecutor 執行構造 PreparedStatementHandler 完,會調用 parameterize() 方法將 PreparedStatement 對象里SQL轉交 ParameterHandler 實現類 DefaultParameterHandler::setParameters() 方法 設置 PreparedStatement 的占位符參數 。
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;}DefaultParameterHandler::setParameters() 如何對SQL進行動態賦值呢? 在執行前將已裝載好的BoundSql對象信息進行使用
public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); //獲取待動態賦值參數列表的封裝parameterMappings List 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); } 在通過 SqlSource 的parse 方法得到parameterMappings 的具體實現中,我們會得到parameterMappings 的 typeHandler TypeHandler typeHandler = parameterMapping.getTypeHandler(); //獲取jdbc數據類型 JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { // typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException | SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } }}執行完SQL 參數的預處理,當 StatementHandler::execute() 真正執行查詢操作執行完后,有返回結果,需要對返回結果進行 ResultSetHandler 處理,現在看看最后的結果的處理流程。
ResultSetHandler
ResultSetHandler 結果解析器,將查詢結果的 ResultSet 轉換成映射的對應結果( java DTO 等),其有三接口
- handleResultSets() :處理結果集
- handleCursorResultSets() :批量處理結果集
- handleOutputParameters() :處理存儲過程返回的結果集
其默認的實現為 DefaultResultSetHandler ,主要功能為:
- 處理 Statement 執行后產生的結果集生成相對的輸出結果、
- 處理存儲過程執行后的輸出參數
那看看 DefaultResultSetHandler::handleResultSets() 如何處理?
- 當有多個 ResultSet 的結果集合,每個ResultSet對應一個Object 對象,如果不考慮存儲過程,普通的查詢只有一個ResultSet
- ResultSetWrapper 封裝了 ResultSet 結果集,其屬性包含 ResultSet , ResultMap 等
其實在 ResultSetHandler 結果集處理是比較復雜的,這里只是簡單的介紹一下,有興趣的可以再深入研究一下,后期有空也會寫。
執行到這邊,Mybatis SQL執行基本完了,會把轉換后的結果集返回到操作者。
結論
在SQL執行過程主要涉及了 SqlSession , MapperProxy , Executor , StatementHandler , ParameterHandler 以及 ResultSetHandler ,包括參數動態綁定,Sql執行查詢數據庫數據,結果返回集映射等,而且每個環節涉及的內容都很多,每個接口都可以抽出單獨分析,后續有時間再一一詳細的看看。后面還是再分析一下插件的應用。
總結
以上是生活随笔為你收集整理的mybatis delete返回值_面试:谈谈你对MyBatis执行过程之SQL执行过程理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 按键精灵安卓教程下载(按键精灵安卓教程)
- 下一篇: ddos只能攻击网站吗(ddos攻击不了