分析mybatis運行原理
JDK實現首先我們需要提供一個接口 , 這個接口是對我們程序員的一個抽象。 擁有編碼和改BUG的本領
public interface Developer { /** * 編碼 */ void code(); /** * 解決問題 */ void debug();}
關于這兩種本領每個人處理方式不同。這里我們需要一個具體的實例對象
public class JavaDeveloper implements Developer { @Override public void code() {System.out.println('java code'); } @Override public void debug() {System.out.println('java debug'); }}
我們傳統的調用方式是通過java提供的new 機制創造一個JavaDeveloper對象出來。而通過動態代理是通過java.lang.reflect.Proxy對象創建對象調用實際方法的。
通過newProxyInstance方法獲取接口對象的。而這個方法需要三個參數
ClassLoader loader : 通過實際接口實例對象獲取ClassLoader Class<?>[] interfaces : 我們抽象的接口 InvocationHandler h : 對我們接口對象方法的調用。在調用節點我們可以進行我們的業務攔截JavaDeveloper jDeveloper = new JavaDeveloper();Developer developer = (Developer) Proxy.newProxyInstance(jDeveloper.getClass().getClassLoader(), jDeveloper.getClass().getInterfaces(), (proxy, method, params) -> { if (method.getName().equals('code')) {System.out.println('我是一個特殊的人,code之前先分析問題');return method.invoke(jDeveloper, params); } if (method.getName().equals('debug')) {System.out.println('我沒有bug'); } return null;});developer.code();developer.debug();
CGLIB動態代理
cglib動態代理優點在于他不需要我們提前準備接口。他代理的實際的對象。這對于我們開發來說就很方便了。
public class HelloService { public HelloService() {System.out.println('HelloService構造'); } final public String sayHello(String name) {System.out.println('HelloService:sayOthers>>'+name);return null; } public void sayHello() {System.out.println('HelloService:sayHello'); }}
下面我們只需要實現cglib提供的MethodInterceptor接口,在初始化設置cglib的時候加載這個實例化對象就可以了
public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println('======插入前置通知======');Object object = methodProxy.invokeSuper(o, objects);System.out.println('======插入后者通知======');return object; }}
下面我們就來初始化設置cglib
public static void main(String[] args) { //代理類class文件存入本地磁盤方便我們反編譯查看源代碼 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, '/root/code'); //通過CGLIB動態代理獲取代理對象過程 Enhancer enhancer = new Enhancer(); //設置enhancer對象的父類 enhancer.setSuperclass(HelloService.class); // 設置enhancer的回調對象 enhancer.setCallback(new MyMethodInterceptor()); //創建代理對象 HelloService helloService = (HelloService) enhancer.create(); //通過代理對象調用目標方法 helloService.sayHello();}
仔細看看cglib和spring的aop特別像。針對切點進行切面攔截控制。
總結:
通過對比兩種動態代理我們很容易發現,mybatis就是通過JDK代理實現Mapper調用的。我們Mapper接口實現通過代理到xml中對應的sql執行邏輯
1.2、反射 相信有一定經驗的Java工程師都對反射或多或少有一定了解。其實從思想上看不慣哪種語言都是有反射的機制的。 通過反射我們就擺脫了對象的限制我們調用方法不再需要通過對象調用了??梢酝ㄟ^Class對象獲取方法對象。從而通過invoke方法進行方法的調用了。二、Configuration對象作用Configuration對象存儲了所有Mybatis的配置。主要初始化一下參數
properties settings typeAliases typeHandler ObjectFactory plugins environment DatabaseIdProvider Mapper映射器三、映射器結構首先我們看看我們平時開發的Mapper接口是如何動態代理的。這就需要提到MapperProxyFactory這個類了。該類中的newInstance方法
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
通過上滿代碼及上述對jdk動態代理的表述。我們可以知道mapperProxy是我們代理的重點。MapperProxy是InvocationHandler的實現類。他重寫的invoke方法就是代理對象執行的方法入口。
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); }} catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t);}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}
private boolean isDefaultMethod(Method method) {return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC && method.getDeclaringClass().isInterface();}
通過源碼發現。invoke內部首先判斷對象是否是類 。 通過打斷點發現最終會走到cacheMapperMethod這個方法去創建MapperMethod對象。繼續查看MapperMethod中execute方法我們可以了解到內部實現其實是一個命令行模式開發。通過判斷命令從而執行不同的語句。判斷到具體執行語句然后將參數傳遞給sqlsession進行sql調用并獲取結果。到了sqlsession就和正常jdbc開發sql進行關聯了。sqlsession中Executor、StatementHandler、ParameterHandler、Resulthandler四大天王
4.1、Executor顧名思義他就是一個執行器。將java提供的sql提交到數據庫。Mybatis提供了三種執行器。
Configuration.class中newExecutor源碼
根據uml我們不難看出mybatis中提供了三類執行器分別SimpleExecutor、ReuseExecutor、BatchExecutor
public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 得到configuration 中的environment final Environment environment = configuration.getEnvironment(); // 得到configuration 中的事務工廠 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 獲取執行器 final Executor executor = configuration.newExecutor(tx, execType); // 返回默認的SqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException('Error opening session. Cause: ' + e, e); } finally { ErrorContext.instance().reset(); } }
通過上述源碼我們知道在sqlsession獲取一個數據庫session對象時我們或根據我們的settings配置加載一個Executor對象。在settings中配置也很簡單
<settings><!--取值范圍 SIMPLE, REUSE, BATCH --><setting name='defaultExecutorType' value='SIMPLE'/></settings>
我們也可以通過java代碼設置
factory.openSession(ExecutorType.BATCH);4.2、StatementHandler
顧名思義,StatementHandler就是專門處理數據庫回話的。這個對象的創建還是在Configuration中管理的。
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; }
很明顯Mybatis中StatementHandler使用的是RoutingStatementHandler這個class
關于StatementHandler和RoutingStatementHandler之間的關系我們通過源碼可以看出這里和Executor一樣都是適配器模式。采用這種模式的好處是方便我們對這些對象進行代理。這里讀者可以猜測一下是使用了哪種動態代理。給點提示 這里使用了接口哦
在查看BaseStatementHandler結構我們會發現和Executor一模一樣。同樣的Mybatis在構造RoutingStatementHandler的時候會根據setting中配置來加載不同的具體子類。這些子類都是繼承了BaseStatementHandler.
前一節我們跟蹤了Executor。 我們知道Mybatis默認的是SimpleExecutor。 StatementHandler我們跟蹤了Mybaits默認的是PrePareStatementHandler。在SimpleExecutor執行查詢的源碼如下
我們發現在executor查詢錢會先讓statementHandler構建一個Statement對象。最終就是StatementHandler中prepare方法。這個方法在抽象類BaseStatmentHandler中已經封裝好了。
這個方法的邏輯是初始化statement和設置連接超時等一些輔助作用
然后就是設置一些參數等設置。最后就走到了執行器executor的doquery
PrepareStatement在我們jdbc開發時是常見的一個類 。 這個方法執行execute前我們需要設置sql語句,設置參數進行編譯。這一系列步驟就是剛才我們說的流程也是PrepareStatementHandler.prepareStatement幫我們做的事情。那么剩下的我們也很容易想到就是我們對數據結果的封裝。正如代碼所示下馬就是resultSetHandler幫我們做事情了。
4.3、結果處理器(ResultSetHandler)@Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity('handling results').object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping);}rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++; } } return collapseSingleResultList(multipleResults); }
這個方法我們可以導出來是結果xml中標簽配置對結果的一個封裝。
4.4、總結SqlSession在一個查詢開啟的時候會先通過CacheExecutor查詢緩存。擊穿緩存后會通過BaseExector子類的SimpleExecutor創建StatementHandler。PrepareStatementHandler會基于PrepareStament執行數據庫操作。并針對返回結果通過ResultSetHandler返回結果數據
以上就是分析mybatis運行原理的詳細內容,更多關于mybatis運行原理的資料請關注好吧啦網其它相關文章!
相關文章: