MyBatis-接口實現數據庫操作的原理


參考:

https://blog.csdn.net/luoposhushengyizhuce/article/details/83902433

https://www.cnblogs.com/williamjie/p/11188355.html

https://www.cnblogs.com/williamjie/p/11188346.html

 

 

 

 

mybatis如何通過只需要配置接口就能實現數據庫的操作

在用mybatis的時候,我們只需要寫一個接口,然后服務層就能調用,在我們的認知中,是不能直接調接口的方法的,這個其中的原理是什么呢?由於自己比較好奇,就取翻了一下mybatis的源碼,一下是做的一些記錄。
通過一個最簡單的例子來揭開它的面目。

@Test public void testDogSelect() throws IOException { String resource = "allconfig.xml";// ① InputStream inputStream = Resources.getResourceAsStream(resource);// ② SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// ③ SqlSession session = sqlSessionFactory.openSession();// ④ DogMapper dogMapper = session.getMapper(DogMapper.class);// ⑤ List<Dog> dogs = dogMapper.selectDog();//⑥ System.out.println(dogs.size());// ⑦ }


首先就是①②兩行是沒什么要解釋的,就從第三行開始,我們追蹤代碼,進入SqlSessionFactoryBuilder的build(InputStream)的方法
public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }


重要代碼就兩行,其中XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);是構建解析器用的,主要看下一行build(parser.parse()),其中parser.parse()主要就是把我們的mybatis的配置文件進行解析,並把解析的大部分內容保存到Configuration中。然后看build的代碼

public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }


返回了一個DefaultSqlSessionFactory
繼續看④這一句,我們知道sqlSessionFactory實際上是DefaultSqlSessionFactory的一個實例,進入DefaultSqlSessionFactory的openSession()方法

@Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); 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(); } }


其他我們都先不管,我們只要看到這個方法返回的是一個DefaultSqlSession的實例就好
我們繼續看⑤這一行,session是DefaultSqlSession的一個實例。我們進入DefaultSqlSession的getMapper(Class type)方法,

@Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }


其中configuration是Configuration的實例,在解析mybatis的配置文件的時候進行的初始化。繼續追進去

public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }


繼續進到MapperRegistry的getMapper(Class type, SqlSession sqlSession)方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }


此方法根據傳進來的type生成對應的代理,我們進入看看MapperProxyFactory

public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }


到這里已經看到已經完成代理的生成,MapperProxyFactory是個工廠。再繼續看MapperProxy這個類
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }

當我們通過生成的對象調用方法的時候,都會進入這個類的invoke方法方法,我看看到如果調用的是我們自定的方法,直接就是調用的mybatis的實現,通過接口找到配置信息,然后根據我們的配置去操作數據庫。

最后總結一下,mybatis之所以配置接口以后就能執行是因為在生產mapper的時候實質上是生成的一個代理,然后通過mapper調用接口方法的時候直接被MapperProxy的invoke截斷了,直接去調用了mybatis為我們制定的實現,而沒有去回調。

 

 

 

 

 

 

 

 

MyBatis你只寫了接口為啥就能執行SQL啊?

一、靜態代理

又是一年秋招季,很多小伙伴開始去大城市打拼。來大城市第一件事就是租房,免不了和中介打交道,因為很多房東很忙,你根本找不到他。從這個場景中就可以抽象出來代理模式:

  • ISubject:被訪問者資源的抽象

  • SubjectImpl:被訪問者具體實現類(房東)

  • SubjectProxy:被訪問者的代理實現類(中介)

UML圖如下:

舉個例子來理解一下這個設計模式:

老板讓記錄一下用戶服務的響應時間,用代理模式來實現這個功能。

 

一切看起來都非常的美好,老板又發話了,把產品服務的響應時間也記錄一下吧。又得寫如下3個類:

  • IProductService

  • ProductServiceImpl

  • ProductServiceProxy

UserServiceProxy和ProductServiceProxy這兩個代理類的邏輯都差不多,卻還得寫2次。其實這個還好,如果老板說,把現有系統的幾十個服務的響應時間都記錄一下吧,你是不是要瘋了?這得寫多少代理類啊?

二、動態代理

黑暗總是暫時的,終究會迎來黎明,在JDK1.3之后引入了一種稱之為動態代理(Dynamic Proxy)的機制。使用該機制,我們可以為指定的接口在系統運行期間動態地生成代理對象,從而幫助我們走出最初使用靜態代理實現AOP的窘境

動態代理的實現主要由一個類和一個接口組成,即java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口。

讓我們用動態代理來改造一下上面記錄系統響應時間的功能。雖然要為IUserService和IProductService兩種服務提供代理對象,但因為代理對象中要添加的橫切邏輯是一樣的。所以我們只需要實現一個InvocationHandler就可以了。代碼如下

UML圖如下。恭喜你,你現在已經理解了Spring AOP是怎么回事了,就是這么簡單,今天先不展開談Spring

先簡單談談動態代理在Mybatis中是如何被大佬玩的出神入化的

三、Mybatis核心設計思路

相信用過mybatis的小伙伴都能理解下面這段代碼,通過roleMapper這個接口直接從數據庫中拿到一個對象

Role role = roleMapper.getRole(3L);

直覺告訴我,一個接口是不能運行的啊,一定有接口的實現類,可是這個實現類我自己沒寫啊,難道mybatis幫我們生成了?你猜的沒錯,mybatis利用動態代理幫我們生成了接口的實現類,這個類就是:

org.apache.ibatis.binding.MapperProxy,

我先畫一下UML圖,MapperProxy就是下圖中的SubjectProxy類

和上面的UML類圖對比一下,發現不就少了一個SubjectImpl類嗎?那應該就是SubjectProxy類把SubjectImple類要做的事情做了唄,猜對了。SubjectProxy通過SubjectImple和SubjectImple.xml之間的映射關系知道自己應該執行什么SQL。所以mybatis最核心的思路就是這么個意思,細節之類的可以看源碼,理清最主要的思路,看源碼就能把握住重點。

關於源碼相關的內容,更進一步的解釋動態代理在MyBatis中的使用,可以參考以前的一篇文章:《動態代理之投鞭斷流!看一下MyBatis的底層實現原理!

 

附錄:

https://mp.weixin.qq.com/s/ToMvAD0-QyUJgg_1eFvcaA

 
 
 
 
 
 
 
 
 

動態代理之投鞭斷流!看一下MyBatis的底層實現原理

轉:https://mp.weixin.qq.com/s?__biz=MzI1NDQ3MjQxNA==&mid=2247486856&idx=1&sn=d430be5d14d159fd36b733c83369d59a&chksm=e9c5f439deb27d2f60b69d7f09b240eb43a8b1de2d07f7511e1f1fecdf9d49df1cb7bc6e1ab5&scene=21#wechat_redirect

一日小區漫步,我問朋友:Mybatis中聲明一個interface接口,沒有編寫任何實現類,Mybatis就能返回接口實例,並調用接口方法返回數據庫數據,你知道為什么不?朋友很是詫異:是啊,我也很納悶,我們領導告訴我們按照這個模式編寫就好了,我同事也感覺很奇怪,雖然我不知道具體是怎么實現的,但我覺得肯定是……(此處略去若干的漫天猜想),但是也不對啊,難道是……(再次略去若干似懂非懂)。

這激發了我寫本篇文章的沖動。

 


動態代理的功能:通過攔截器方法回調,對目標target方法進行增強。

言外之意就是為了增強目標target方法。上面這句話沒錯,但也不要認為它就是真理,殊不知,動態代理還有投鞭斷流的霸權,連目標target都不要的科幻模式。

注:本文默認認為,讀者對動態代理的原理是理解的,如果不明白target的含義,難以看懂本篇文章,建議先理解動態代理。

一、自定義JDK動態代理之投鞭斷流實現自動映射器Mapper

首先定義一個pojo

再定義一個接口UserMapper.java

接下來我們看看如何使用動態代理之投鞭斷流,實現實例化接口並調用接口方法返回數據的。

自定義一個InvocationHandler。

上面代碼中的target,在執行Object.java內的方法時,target被指向了this,target已經變成了傀儡、象征、占位符。在投鞭斷流式的攔截時,已經沒有了target。

寫一個測試代碼:

output:

這便是Mybatis自動映射器Mapper的底層實現原理。

可能有讀者不禁要問:你怎么把代碼寫的像初學者寫的一樣?沒有結構,且缺乏美感。

必須聲明,作為一名經驗老道的高手,能把程序寫的像初學者寫的一樣,那必定是高手中的高手。這樣可以讓初學者感覺到親切,舒服,符合自己的Style,讓他們或她們,感覺到大牛寫的代碼也不過如此,自己甚至寫的比這些大牛寫的還要好,從此自信滿滿,熱情高漲,認為與大牛之間的差距,僅剩下三分鍾。

二、Mybatis自動映射器Mapper的源碼分析

首先編寫一個測試類:

Mapper長這個樣子:

org.apache.ibatis.binding.MapperProxy.java部分源碼。

org.apache.ibatis.binding.MapperProxyFactory.java部分源碼。

這便是Mybatis使用動態代理之投鞭斷流。

三、接口Mapper內的方法能重載(overLoad)嗎?(重要)

類似下面:

Answer:不能。

原因:在投鞭斷流時,Mybatis使用package+Mapper+method全限名作為key,去xml內尋找唯一sql來執行的。類似:key=x.y.UserMapper.getUserById,那么,重載方法時將導致矛盾。對於Mapper接口,Mybatis禁止方法重載(overLoad)。

 
 
 
 
 
 
 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM