Mycat 多租户方案,kafka 选举原理
return scheme;
}
public static final void remove() {
tenanThreadLocal.remove();
}
}
那控制器层代码的伪代码如下:
public Map findDepts( String tenant, String businessP1 ) {
Map result = new HashMap();
try {
TenantContextHolder.setTenant(tenant);
//调用 service 层代码
} catch(Throw e) {
e.printStackTrace();
result.put("msg", "系统异常");
result.put("code", 1);
} finally {
TenantContextHolder.remove();
System.out.println("控制器层面,,移除 tenant。。。");
}
}
如果每个控制器层代码,都需要用上面的模板来做,未免有点。。。所以为了统一处理 Tenant ,目前提供一个给予 Spring AOP 的拦截器。代码如下:
package persistent.prestige.modules.common.tenant;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.annotation.Autowired;
import persistent.prestige.modules.edu.service.UserSchemeService;
public class TenantControlInteceper implements MethodInterceptor {
@Autowired
private UserSchemeService userScemeService;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
if("login".equals(invocation.getMethod().getName())) {
return invocation.proceed();
}
System.out.println("控制器层面,,计算 tenant。。。");
Object[] args = invocation.getArguments();
String tenant = "";
if( args != null && args.length > 0) {
tenant = (String)args[0];
}
TenantContextHolder.setTenant(tenant);
return invocation.proceed();
}finally {
TenantContextHolder.remove();
System.out.println("控制器层面,,移除 tenant。。。");
}
}
}
统一处理 Tenant 的设置为移除;此处与代码中的有点差别,是因为,,根据用户登录名获取 tenant 的逻辑放在了上面登录接口中。
只要遵循这样一种编码规范,action 方法的第一个参数的值为 tenant 就好。配置一下拦截器【基于 Spring AOP】

2.2.3、业务承载方法
业务方法无需改变;但是要利用 Mybatis 拦截器改写 SQL。代码和配置如下:
1、工具类
package persistent.prestige.platform.mybatis.Interceptor;
import java.lang.reflect.Field;
import org.apache.commons.lang.reflect.FieldUtils;
public class ReflectHelper {
public static Object getFieldValue(Object obj , String fieldName ){
if(obj == null){
return null ;
}
Field targetField = getTargetField(obj.getClass(), fieldName);
try {
return FieldUtils.readField(targetField, obj, true ) ;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null ;
}
public static Field getTargetField(Class<?> targetClass, String fieldName) {
Field field = null;
try {
if (targetClass == null) {
return field;
}
if (Object.class.equals(targetClass)) {
return field;
}
field = Fie
《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
ldUtils.getDeclaredField(targetClass, fieldName, true);
if (field == null) {
field = getTargetField(targetClass.getSuperclass(), fieldName);
}
} catch (Exception e) {
}
return field;
}
public static void setFieldValue(Object obj , String fieldName , Object value ){
if(null == obj){return;}
Field targetField = getTargetField(obj.getClass(), fieldName);
try {
FieldUtils.writeField(targetField, obj, value) ;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
SQL 拦截类
package persistent.prestige.platform.mybatis.Interceptor;
import java.sql.Connection;
import java.util.Properties;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.kahadb.page.Page;
import org.springframework.beans.factory.annotation.Autowired;
import persistent.prestige.modules.common.tenant.TenantContextHolder;
import persistent.prestige.modules.edu.dao.TeacherUserDao;
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class TenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
String tenant = TenantContextHolder.getTenant();
if(tenant == null || tenant == "") {
System.out.println("tenant 为空,不需要改写 sql 语句");
return invocation.proceed();
}
if (invocation.getTarget() instanceof RoutingStatementHandler) {
System.out.println("aaaaaaa");
RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation
.getTarget();
StatementHandler delegate = (StatementHandler) ReflectHelper
.getFieldValue(statementHandler, "delegate");
BoundSql boundSql = delegate.getBoundSql();
Object obj = boundSql.getParameterObject();
// 通过反射获取 delegate 父类 BaseStatementHandler 的 mappedStatement 属性
MappedStatement mappedStatement = (MappedStatement) ReflectHelper
.getFieldValue(delegate, "mappedStatement");
// 拦截到的 prepare 方法参数是一个 Connection 对象
Connection connection = (Connection) invocation.getArgs()[0];
// 获取当前要执行的 Sql 语句,也就是我们直接在 Mapper 映射语句中写的 Sql 语句
String sql = boundSql.getSql();
// 给当前的 page 参数对象设置总记录数
System.out.println("处理之前" + sql);
//对 sql 增加 mycat 注解
sql = "/*!mycat:schema=" + tenant + " */" + sql;
System.out.println("加入处理后:" + sql);
ReflectHelper.setFieldValue(boundSql, "sql", sql);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// TODO Auto-generated method stub
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
}
}
配置如下:

2.2.4 方案优缺点
优点:
对业务代码侵入少,开发人员无需关注数据在哪个逻辑库上,隔离性好。
缺点
如果需要对所有租户的数据进行汇聚的话,需要业务上去实现。
该方案代码:请关注如下代码:
最后
对于很多 Java 工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。
整理的这些资料希望对 Java 开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
再分享一波我的 Java 面试真题+视频学习详解+技能进阶书籍

评论