MyBatis Interceptor

Spring+MyBatis實現(xiàn)讀寫分離四種實現(xiàn)方案整理

如果你的后臺結(jié)構(gòu)是spring+mybatis,可以通過spring的AbstractRoutingDataSource和mybatis Plugin攔截器實現(xiàn)非常友好的讀寫分離六剥,原有代碼不需要任何改變峰伙。推薦第四種方案。

一策彤、通過Spring+MyBatis實現(xiàn)數(shù)據(jù)庫讀寫分離的實現(xiàn)來引入MyBatis的Intercepter

上面網(wǎng)站的第四種方案非常的奈斯,只要引入方案中的五個類裹刮,然后在spring配置文件里做點配置就能完美的實現(xiàn)讀寫分離庞瘸。先來介紹一下實現(xiàn)的原理。

實現(xiàn)的重點是下面這兩個:
1塔橡、org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
spring提供的這個類能夠讓我們實現(xiàn)運行時多數(shù)據(jù)源的動態(tài)切換霜第,但是數(shù)據(jù)源是需要事先配置好的泌类,無法動態(tài)的增加數(shù)據(jù)源。但是對于小項目來說已經(jīng)足夠了刃榨。
2枢希、MyBatis提供的@Intercepts、@Signature注解和org.apache.ibatis.plugin.Interceptor接口苞轿。

另外還有一個就是ThreadLocal類搬卒,用于保存每個線程正在使用的數(shù)據(jù)源。ThreadLocal的原理之前已經(jīng)分析過了契邀。

運行流程差不多是這樣的:
1坯门、我們自己寫的MyBatis的Interceptor按照@Signature的規(guī)則攔截下Executor.class的update和query方法
2、判斷是讀還是寫方法欠橘,然后在一個Holder類中的靜態(tài)不可變的ThreadLocal里保存一個讀或者寫數(shù)據(jù)源對應(yīng)的key允瞧,每個線程持有自己的變量。
3痹升、線程再根據(jù)這個變量作為key畦韭,借助SPRING提供的AbstractRoutingDataSource重寫determineCurrentLookupKey()方法,從我們配置好的全局靜態(tài)的HashMap中取出當前要用的讀或者寫數(shù)據(jù)源
4察郁、返回對應(yīng)數(shù)據(jù)源的connection去做相應(yīng)的數(shù)據(jù)庫操作

二转唉、MyBatis的Intercepter(Plugin)

1、做上面的讀寫分離的時候?qū)崿F(xiàn)Intercepter的類加了如下的注解

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

@Intercepts({
@Signature(type = Executor.class, method = "update", args = {
        MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args = {
        MappedStatement.class, Object.class, RowBounds.class,
        ResultHandler.class }) })
public class DynamicPlugin implements Interceptor {

@Intercepts 在實現(xiàn)Interceptor接口的類聲明,使該類注冊成為攔截器
Signature[] value //定義需要攔截哪些類的哪些方法
@Signature 要攔截的類(如下四種),攔截方法砖织,方法對應(yīng)參數(shù)
Class<?> type() //ParameterHandler,ResultSetHandler,StatementHandler,Executor
String method()
Class<?>[] args()

實現(xiàn)Interceptor
每一個攔截器都必須實現(xiàn)上面的三個方法侧纯,其中:
1、 Object intercept(Invocation invocation)是實現(xiàn)攔截邏輯的地方眶熬,內(nèi)部要通過invocation.proceed()顯式地推進責任鏈前進聋涨,也就是調(diào)用下一個攔截器攔截目標方法。
2脊凰、Object plugin(Object target) 就是用當前這個攔截器生成對目標target的代理茂腥,實際是通過Plugin.wrap(target,this) 來完成的,把目標target和攔截器this傳給了包裝函數(shù)帕胆。
3般渡、setProperties(Properties properties)用于設(shè)置額外的參數(shù)芙盘,參數(shù)配置在攔截器的Properties節(jié)點里脸秽。
注解里描述的是指定攔截方法的簽名 [type,method,args] (即對哪種對象的哪種方法進行攔截)记餐,它在攔截前用于決斷。

在結(jié)合spring的項目中要將我們寫的攔截器注入SqlSessionFactoryBean中


spring配置

因為在創(chuàng)建ParameterHandler,ResultSetHandler,StatementHandler,Executor這四個類的實現(xiàn)類的時候囚衔,我們配置的攔截器鏈會先判斷是否要進行攔截雕沿,需要攔截的話返回代理包裝后的對象,否則直接返回原對象鞠鲜。
因此可以在實現(xiàn)plugin方法時判斷一下目標類型断国,是本插件要攔截的類才執(zhí)行Plugin.wrap方法,否則直接返回目標本身霞捡。


image.png

雖然在Plugin.wrap生成代理對象的方法中有做了判斷碧信,如果有包含我們要攔截的接口就進行包裝代理街夭,否則返回原對象。但是提前自己進行判斷就不用在進入wrap方法呈枉。


Plugin.wrap

總結(jié)

image.png

Mybatis的攔截器實現(xiàn)機制,使用的是JDK的InvocationHandler.
當我們調(diào)用ParameterHandler,ResultSetHandler,StatementHandler,Executor的對象的時候,
實際上使用的是Plugin這個代理類的對象,這個類實現(xiàn)了InvocationHandler接口.
接下來我們就知道了,在調(diào)用上述被代理類的方法的時候,就會執(zhí)行Plugin的invoke方法.
Plugin在invoke方法中根據(jù)@Intercepts的配置信息(方法名,參數(shù)等)動態(tài)判斷是否需要攔截該方法.
再然后使用需要攔截的方法Method封裝成Invocation,并調(diào)用Interceptor的proceed方法.
這樣我們就達到了攔截目標方法的結(jié)果.
例如Executor的執(zhí)行大概是這樣的流程:
攔截器代理類對象->攔截器->目標方法
Executor->Plugin->Interceptor->Invocation
Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke

http://blog.csdn.net/hupanfeng/article/details/9247379

/** 
  * 每一個攔截器對目標類都進行一次代理 
  * @paramtarget 
  * @return 層層代理后的對象 
  */  
 public Object pluginAll(Object target) {  
     for(Interceptor interceptor : interceptors) {  
         target= interceptor.plugin(target);  
     }  
     returntarget;  
}  

每一個攔截器對目標類都進行一次代理,原對象如果是X砚殿,那么第一個攔截器代理后為P(X)似炎,第二個代理后P(P(X))......最后返回這樣的多重代理對象并執(zhí)行悯姊。所以先配置的攔截器會后執(zhí)行贩毕,因為先配置的先被包裝成代理對象。

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)) {  
            //調(diào)用代理類所屬攔截器的intercept方法,  
           return interceptor.intercept(new Invocation(target, method, args));  
        }  
        return method.invoke(target, args);  
    } catch(Exception e) {  
        throw ExceptionUtil.unwrapThrowable(e);  
    }  
}

最后在調(diào)用真實對象方法的時候睛藻,實際上是調(diào)用多重代理的invoke方法邢隧,當符合攔截條件的時候執(zhí)行我們編寫的interceptor.intercept,intercept方法最后必定是調(diào)用invocation.proceed按摘,proceed也是一個method.invoke纫谅,促使攔截鏈往下進行,不符合攔截條件的時候直接調(diào)用method.invoke兰珍,即不執(zhí)行我們的攔截方法询吴,繼續(xù)攔截鏈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末唠摹,一起剝皮案震驚了整個濱河市奉瘤,隨后出現(xiàn)的幾起案子盗温,更是在濱河造成了極大的恐慌,老刑警劉巖肌访,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吼驶,死亡現(xiàn)場離奇詭異店煞,居然都是意外死亡风钻,警方通過查閱死者的電腦和手機骡技,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來囤萤,“玉大人是趴,你說我怎么就攤上這事「谎牛” “怎么了肛搬?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵温赔,是天一觀的道長。 經(jīng)常有香客問我远剩,道長骇窍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任痢掠,我火速辦了婚禮嘲恍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淹辞。我一直安慰自己俘侠,他們只是感情好蔬将,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布霞怀。 她就那樣靜靜地躺著莉给,像睡著了一般。 火紅的嫁衣襯著肌膚如雪徐矩。 梳的紋絲不亂的頭發(fā)上州泊,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天遥皂,我揣著相機與錄音刽漂,去河邊找鬼。 笑死样悟,一個胖子當著我的面吹牛庭猩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播震糖,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼趴腋,長吁一口氣:“原來是場噩夢啊……” “哼优炬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蠢护,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤葵硕,失蹤者是張志新(化名)和其女友劉穎单寂,沒想到半個月后吐辙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昏苏,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年洼专,在試婚紗的時候發(fā)現(xiàn)自己被綠了孵构。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颈墅。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖官还,靈堂內(nèi)的尸體忽然破棺而出毒坛,到底是詐尸還是另有隱情,我是刑警寧澤屯伞,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布愕掏,位于F島的核電站顶伞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏唆貌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钮孵,春花似錦眼滤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至般此,卻和暖如春牵现,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背居扒。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓤摧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像这揣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子机打,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理残邀,服務(wù)發(fā)現(xiàn),斷路器芥挣,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 《 一生所愛》從前現(xiàn)在過去了再不來紅紅落葉長埋塵土內(nèi)開始終結(jié)總是沒變改天邊的你飄泊白云外苦海翻起愛恨在世間難逃避命...
    莫那一魯?shù)?/span>閱讀 2,310評論 8 8
  • MyBatis提供了一種插件(plugin)的功能空另,雖然叫做插件鼓蜒,但其實這是攔截器功能。那么攔截器攔截MyBati...
    七寸知架構(gòu)閱讀 3,253評論 3 54
  • 小花和小草 作者:周憬揚
    周憬揚閱讀 379評論 0 0
  • 殘陽慕月,敗柳惜花畅厢,非是人生無常。 睬夢蝶浦楣,拾夢花咪辱,言道大夢一場振劳。 朽葉亦是朝花,花落花開油狂,彈指已入春...
    瓶蓋_閱讀 198評論 0 5