MyBatis Interceptor

Spring+MyBatis实现读写分离四种实现方案整理

如果你的后台结构是spring+mybatis,可以通过spring的AbstractRoutingDataSource和mybatis Plugin拦截器实现非常友好的读写分离,原有代码不需要任何改变。推荐第四种方案。

一、通过Spring+MyBatis实现数据库读写分离的实现来引入MyBatis的Intercepter

上面网站的第四种方案非常的奈斯,只要引入方案中的五个类,然后在spring配置文件里做点配置就能完美的实现读写分离。先来介绍一下实现的原理。

实现的重点是下面这两个:
1、org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
spring提供的这个类能够让我们实现运行时多数据源的动态切换,但是数据源是需要事先配置好的,无法动态的增加数据源。但是对于小项目来说已经足够了。
2、MyBatis提供的@Intercepts、@Signature注解和org.apache.ibatis.plugin.Interceptor接口。

另外还有一个就是ThreadLocal类,用于保存每个线程正在使用的数据源。ThreadLocal的原理之前已经分析过了。

运行流程差不多是这样的:
1、我们自己写的MyBatis的Interceptor按照@Signature的规则拦截下Executor.class的update和query方法
2、判断是读还是写方法,然后在一个Holder类中的静态不可变的ThreadLocal里保存一个读或者写数据源对应的key,每个线程持有自己的变量。
3、线程再根据这个变量作为key,借助SPRING提供的AbstractRoutingDataSource重写determineCurrentLookupKey()方法,从我们配置好的全局静态的HashMap中取出当前要用的读或者写数据源
4、返回对应数据源的connection去做相应的数据库操作

二、MyBatis的Intercepter(Plugin)

1、做上面的读写分离的时候实现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 在实现Interceptor接口的类声明,使该类注册成为拦截器
Signature[] value //定义需要拦截哪些类的哪些方法
@Signature 要拦截的类(如下四种),拦截方法,方法对应参数
Class<?> type() //ParameterHandler,ResultSetHandler,StatementHandler,Executor
String method()
Class<?>[] args()

实现Interceptor
每一个拦截器都必须实现上面的三个方法,其中:
1、 Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。
2、Object plugin(Object target) 就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this) 来完成的,把目标target和拦截器this传给了包装函数。
3、setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。
注解里描述的是指定拦截方法的签名 [type,method,args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。

在结合spring的项目中要将我们写的拦截器注入SqlSessionFactoryBean中


spring配置

因为在创建ParameterHandler,ResultSetHandler,StatementHandler,Executor这四个类的实现类的时候,我们配置的拦截器链会先判断是否要进行拦截,需要拦截的话返回代理包装后的对象,否则直接返回原对象。
因此可以在实现plugin方法时判断一下目标类型,是本插件要拦截的类才执行Plugin.wrap方法,否则直接返回目标本身。


image.png

虽然在Plugin.wrap生成代理对象的方法中有做了判断,如果有包含我们要拦截的接口就进行包装代理,否则返回原对象。但是提前自己进行判断就不用在进入wrap方法。


Plugin.wrap

总结

image.png

Mybatis的拦截器实现机制,使用的是JDK的InvocationHandler.
当我们调用ParameterHandler,ResultSetHandler,StatementHandler,Executor的对象的时候,
实际上使用的是Plugin这个代理类的对象,这个类实现了InvocationHandler接口.
接下来我们就知道了,在调用上述被代理类的方法的时候,就会执行Plugin的invoke方法.
Plugin在invoke方法中根据@Intercepts的配置信息(方法名,参数等)动态判断是否需要拦截该方法.
再然后使用需要拦截的方法Method封装成Invocation,并调用Interceptor的proceed方法.
这样我们就达到了拦截目标方法的结果.
例如Executor的执行大概是这样的流程:
拦截器代理类对象->拦截器->目标方法
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))......最后返回这样的多重代理对象并执行。所以先配置的拦截器会后执行,因为先配置的先被包装成代理对象。

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)) {  
            //调用代理类所属拦截器的intercept方法,  
           return interceptor.intercept(new Invocation(target, method, args));  
        }  
        return method.invoke(target, args);  
    } catch(Exception e) {  
        throw ExceptionUtil.unwrapThrowable(e);  
    }  
}

最后在调用真实对象方法的时候,实际上是调用多重代理的invoke方法,当符合拦截条件的时候执行我们编写的interceptor.intercept,intercept方法最后必定是调用invocation.proceed,proceed也是一个method.invoke,促使拦截链往下进行,不符合拦截条件的时候直接调用method.invoke,即不执行我们的拦截方法,继续拦截链。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,597评论 18 139
  • 《 一生所爱》从前现在过去了再不来红红落叶长埋尘土内开始终结总是没变改天边的你飘泊白云外苦海翻起爱恨在世间难逃避命...
    莫那一鲁道阅读 2,292评论 8 8
  • MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBati...
    七寸知架构阅读 3,252评论 3 54
  • 小花和小草 作者:周憬扬
    周憬扬阅读 368评论 0 0
  • 残阳慕月,败柳惜花,非是人生无常。 睬梦蝶,拾梦花,言道大梦一场。 朽叶亦是朝花,花落花开,弹指已入春...
    瓶盖_阅读 192评论 0 5