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中
因為在創(chuàng)建ParameterHandler,ResultSetHandler,StatementHandler,Executor這四個類的實現(xiàn)類的時候囚衔,我們配置的攔截器鏈會先判斷是否要進行攔截雕沿,需要攔截的話返回代理包裝后的對象,否則直接返回原對象鞠鲜。
因此可以在實現(xiàn)plugin方法時判斷一下目標類型断国,是本插件要攔截的類才執(zhí)行Plugin.wrap方法,否則直接返回目標本身霞捡。
雖然在Plugin.wrap生成代理對象的方法中有做了判斷碧信,如果有包含我們要攔截的接口就進行包裝代理街夭,否則返回原對象。但是提前自己進行判斷就不用在進入wrap方法呈枉。
總結(jié)
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ù)攔截鏈。