Mybatis中SqlSession下的四大對(duì)象之執(zhí)行器(executor)
首先我先解釋一下標(biāo)題 四大對(duì)象是指:executor, statementHandler,parameterHandler,resultHandler對(duì)象。(為了方便下面的文章說(shuō)道四大對(duì)象就專(zhuān)指它們)
它們都是sqlSession的底層類(lèi)實(shí)現(xiàn),也是插件能夠攔截的四大對(duì)象。所以這里已經(jīng)觸及了MyBATIS的底層,動(dòng)態(tài)代理,反射隨時(shí)可以看到,如果沒(méi)有第一篇作為基礎(chǔ),你將十分難以理解它。了解他們的協(xié)作,是插件編寫(xiě)的基礎(chǔ)之一,所以這是十分的重要。
Executor在sqlSession中的應(yīng)用
上篇我們談到了一個(gè)問(wèn)題,一個(gè)mapper被執(zhí)行是通過(guò)動(dòng)態(tài)代理來(lái)完成的,然后進(jìn)入到了sqlSession的方法中去。這個(gè)并不難理解,但是sqlSession內(nèi)部是怎么運(yùn)行的呢?答案四大對(duì)象的協(xié)作。在SqlSession它還是一個(gè)接口,mybatis內(nèi)部是通過(guò)DefaultSqlSession這個(gè)實(shí)現(xiàn)類(lèi)為我們提供服務(wù)的,它比較長(zhǎng),但是我們不需要全部看到,我們只看到很常用的selectList方法便可以了。
package org.apache.ibatis.session.defaults; public class DefaultSqlSession implements SqlSession { private Configuration configuration; private Executor executor; private boolean autoCommit; private boolean dirty; ....... @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); 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(); } } ...... }
我們可以看到它是通過(guò)executor去執(zhí)行方法來(lái)完成查詢(xún)的。
初認(rèn)Executor
那么我們對(duì)executor就很感興趣,于是我們看看executor是怎么樣的,首先在MyBATIS中有三種executor:
SimpleExecutor -- SIMPLE 就是普通的執(zhí)行器。
ReuseExecutor -執(zhí)行器會(huì)重用預(yù)處理語(yǔ)句(prepared statements)
BatchExecutor --它是批量執(zhí)行器
這些就是mybatis的三種執(zhí)行器。你可以通過(guò)配置文件的settings里面的元素defaultExecutorType,配置它,默認(rèn)是采用SimpleExecutor如果你在Spring運(yùn)用它,那么你可以這么配置它:
<bean id="sqlSessionTemplateBatch" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> <!--更新采用批量的executor --> <constructor-arg index="1" value="BATCH"/> </bean>
這樣,它便是一個(gè)批量的執(zhí)行器。mybatis的三個(gè)executor都有一個(gè)共同的父類(lèi)——BaseExecutor。
Executor初始化
首先我們先了解一下mybatis是怎么樣生成executor的。我們看到生成Executor的地方(org.apache.ibatis.session.Configuration):
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; }
這里大部分都很好理解,但是有個(gè)地方就好不好理解,它就是:
executor = (Executor) interceptorChain.pluginAll(executor);
這是一段非常重要的代碼,它是采用責(zé)任鏈模式,來(lái)產(chǎn)生代理對(duì)象。我們需要再深入理解它,打開(kāi)它具體的pluginAll方法:
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
我們這里先介紹一下這段代碼:
Interceptor它是mybatis攔截器必須要實(shí)現(xiàn)的接口,換句話(huà)說(shuō),這個(gè)遍歷就是遍歷mybatis的攔截器。
然后調(diào)用plugin方法,這個(gè)方法是為了生成代理對(duì)象(占位)的。
于是可以想象我們的插件的代理對(duì)象將會(huì)是一層層的嵌套,所以當(dāng)中任何一個(gè)插件(Interceptor)都有機(jī)會(huì)攔截這個(gè)真是的服務(wù)對(duì)象(executor),則便是責(zé)任鏈模式,我們完全可以提供插件(Interceptor),進(jìn)入到代理對(duì)象的invoke方法里面,來(lái)改變executor的行為和方法。
但是我要在這里強(qiáng)調(diào),當(dāng)你使用插件的時(shí)候,你將改變mybatis的executor內(nèi)容實(shí)現(xiàn),你必須慎重的使用它。
如果要我們自己編寫(xiě)動(dòng)態(tài)的代理,那么工作量可不小,好在mybatis為我們提供了Plugin.java類(lèi)來(lái)完成我們所需要的功能。
public class Plugin implements InvocationHandler { private Object target; private Interceptor interceptor; private Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } ...... }
這里有一個(gè)wrap方法:它會(huì)為我們生成代理對(duì)象。一旦我們的插件和它綁定,那么我們可以想到就會(huì)進(jìn)入invoke方法里面。
invoke方法:很簡(jiǎn)單,它運(yùn)行首先通過(guò)class和method的過(guò)濾,看看是否需要攔截這個(gè)方法,如果被攔截,那么它就運(yùn)行interceptor的intercept方法。所以當(dāng)我們配置了簽名,就能夠攔截我們的方法。
我們先討論那么多,我們知道后面講插件的時(shí)候我們還會(huì)提及它,這里我們知道它會(huì)根據(jù)插件的個(gè)數(shù)生成一層層的代理對(duì)象就可以了。
executor的執(zhí)行
executor的執(zhí)行是依賴(lài)于Statement對(duì)象來(lái)操作的,讓我們以SimpleExecutor的doQuery方法為例子:
public class SimpleExecutor extends BaseExecutor { ...... @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); } } ...... private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection); handler.parameterize(stmt); return stmt; } }
很顯然這里調(diào)度的是一個(gè)查詢(xún)方法
首先它先生成StatementHandler對(duì)象。
通過(guò)prepareStatement方法調(diào)用prepare方法初始化參數(shù)。
然后使用parameterize方法設(shè)置參數(shù)到運(yùn)行環(huán)境。
然后便通過(guò)handler.<E>query(stmt, resultHandler);方法來(lái)完成結(jié)果組裝。
于是我們的焦點(diǎn)就集中在了StatementHandler對(duì)象上,下章我們將談及它。
總結(jié)
以上所述是小編給大家介紹的Mybatis中SqlSession下的四大對(duì)象之執(zhí)行器(executor),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
servlet之session簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了servlet之session簡(jiǎn)介,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07mybatis-plus中l(wèi)ambdaQuery()與lambdaUpdate()比較常見(jiàn)的使用方法總結(jié)
mybatis-plus是在mybatis的基礎(chǔ)上做增強(qiáng)不做改變,簡(jiǎn)化了CRUD操作,下面這篇文章主要給大家介紹了關(guān)于mybatis-plus中l(wèi)ambdaQuery()與lambdaUpdate()比較常見(jiàn)的使用方法,需要的朋友可以參考下2022-09-09JDBC簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
什么是JDBC?這篇文章就為大家詳細(xì)介紹了Java語(yǔ)言中用來(lái)規(guī)范客戶(hù)端程序如何來(lái)訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)的應(yīng)用程序接口,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Java線(xiàn)程阻塞工具LockSupport用法詳解
Java中的LockSupport是一個(gè)用于線(xiàn)程同步的工具類(lèi),它提供了一種基于線(xiàn)程的阻塞和喚醒機(jī)制,LockSupport可以讓線(xiàn)程在特定條件下阻塞掛起,等待其他線(xiàn)程發(fā)送信號(hào)來(lái)喚醒它,本文將通過(guò)一個(gè)小案例給大家介紹一下LockSupport怎么用,讓你永遠(yuǎn)記住它2023-08-08Spring Boot學(xué)習(xí)入門(mén)之統(tǒng)一異常處理詳解
我們?cè)谧鯳eb應(yīng)用的時(shí)候,請(qǐng)求處理過(guò)程中發(fā)生錯(cuò)誤是非常常見(jiàn)的情況。下面這篇文章主要給大家介紹了關(guān)于Spring Boot學(xué)習(xí)入門(mén)之統(tǒng)一異常處理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。2017-09-09SpringBoot系列教程JPA之基礎(chǔ)環(huán)境搭建的方法
這篇文章主要介紹了SpringBoot系列教程JPA之基礎(chǔ)環(huán)境搭建的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06