mybatis数据层的多租户拦截


mybatis数据层的多租户拦截

文章插图
互联网厂商在进行公有云saas服务部署的时候 , 往往会面对多租户的场 景 , 多租户场景的设计 , 在架构上一般分为二个层次:
1、计算集群的多租户
计算集群的多租户顾名思义 , 就是针对不同的租户提供单独的服务集群 , 不同租户之间cpu相互独立 , 个别租户的流量洪峰不会影响其它租户正常服务 。
计算集群的多租户实现有很多种方案 , 一般做法是 , 根据不同租户辟出独立集群 , 对外暴露统一的CDN域名或BGP域名 , 经流量调度到计算中心内部 , 由Nginx集群或网关根据URL中的租户参数 , 向租户所在集群转发请求 。
由于多租户的场景的普遍性 , 最近几年新涌现的新框架和平台在设计伊始就考虑了多租户的场景 , 并集成了相关技术 , 比如k8的namespace , 结合calico , 可以实现非常灵活的多租户计算分组 。
2、数据层的多租户
拿MySQL来举例 , 数据层的多租户一般有三种做法:
1)一个租户一个数据库
2)一个租户一个schema
3)多个租户同一个schema
其中区别 , 可以查询相关资料 , 网上很多 , 一般Saas服务从成本等因素考虑 , 大多数采用第3种方案 , 其主要标志为在元数据设计中 , 有多租户字段tenant_id 。
3、多租户的数据层访问拦截
这里以JAVA+mybatis来说明 。
Mybatis plugin插件支持拦截所有提交到Dao层的SQL , 并支持对提交的SQL进行改写 , 原理类似于Spring的Interceptor , 多租户Mybatis插件能够在运行时 , 动态获取到应用上下文中的租户变量 , 在执行CRUD操作时 , 自动将多租户字段(tenant_id)条件附加到sql语句之中 , 如where条件之后 。
1)使用mybatis插件的优点
使用mybatis插件 , 可以大大简化业务代码在多租户改造过程中的开发成本 , 对比硬编码方式 , mApper和dao层无须改造 , service层的代码改造量也可以大大降低 。
【mybatis数据层的多租户拦截】2)原理
通过mybatis plugin拦截sql处理步骤和result处理步骤 。sql预处理 , 自动增加tenant_id相关语句 , result预处理 , 验证数据tenant_id字段符合要求 。包括:
  • insert into , 增加写入tenant_id数据
  • select 自动在where之后附加条件tenant_id = ?
  • update 在where之后附加条件tenant_id
  • delete 在where之后附加条件tenant_id
  • left join
  • 子查询
等等 。
3)实现方式
插件用的是责任链模式 , 由每一个对象对其下家的引用而连接起来形成一条链 , 请求在这个链上传递 , 直到链上的某一个对象决定处理此请求 。
具体实现方式为 , 实现接口Interceptor.java 的3个方法 , 分别是plugin、intercept和setProperties 。
 
mybatis数据层的多租户拦截

文章插图
 
  • intercept:它将直接覆盖你所拦截的对象 , 有个参数Invocation对象 , 通过该对象 , 可以反射调度原来对象的方法;
  • plugin:target是被拦截的对象 , 它的作用是给被拦截对象生成一个代理对象;
  • setProperties:允许在plugin元素中配置所需参数 , 该方法在插件初始化的时候会被调用一次
 
实现类需要添加几个注解 , 来拦截对应的签名:
 
mybatis数据层的多租户拦截

文章插图
然后实现Interceptor接口的方法即可 , 通过Plugin工具类方便生成代理类 , 通过MetaObject工具类方便操作四大对象的属性 , 修改对应的值 。
 
mybatis数据层的多租户拦截

文章插图
然后实现Plugin方法 , 生成代理类的方法是通过MyBatis提供的Plugin工具类 , 它实现了InvocationHandler接口(JDK动态代理的接口) , 看看它的2个方法:
 
mybatis数据层的多租户拦截

文章插图
Plugin提供了静态方法wrap方法 , 它会根据插件的签名配置 , 使用JDK动态代理的方法 , 生成一个代理类 , 当四大对象执行方法时 , 会调用Plugin的invoke方法 , 如果方法包含在声明的签名里 , 就会调用自定义插件的intercept方法 , 传入Invocation对象 。


推荐阅读