基于mycat实现多租户

以前公司的一个项目需要实现多租户,这里简单记录一下实现的流程,希望有需要的朋友们能用的上。

原理:基于mycat的注解动态切换schema,在数据库设计的时候分为公共库、租户库,公共库放公共表,比如用户表、租户配置表。租户库放租户自己的数据表。

基于mycat实现多租户_第1张图片
mycat动态切换schema流程.png

1.接口请求:用户登陆后,每个接口请求均会有tenant_id参数

2.controller拦截器:拦截每一个请求,根据tenant_id参数获取schema(库名称,冲公共库、或者redis中获取),并存入ThreadLocal中,以便后续使用

3.sql拦截器:拦截每个sql执行语句,从ThreadLocal中获取schema,在sql语句中拼接上mycat注解,然后再执行,这样sql在执行的时候就会动态切换schema

controller拦截器 代码:


public class MyCatFilter implements Filter {
    private static final ThreadLocal SCHEMA_LOCAL = new ThreadLocal<>();
    private static final ThreadLocal TENANT_ID_LOCAL = new ThreadLocal<>();
    public static String getSchema() {
        String schema = SCHEMA_LOCAL.get();
        return StringUtils.isEmpty(schema) ? "":schema;
    }
    public static String getTenantId(){
        String tenantId=TENANT_ID_LOCAL.get();
        return StringUtils.isEmpty(tenantId) ? "":tenantId;
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        try {
            HttpServletRequest req = (HttpServletRequest) request;
            String tenantId = req.getParameter("tenant_id");
            //根据租户id查询租户对应的数据库
            if (!StringUtils.isEmpty(tenantId)) {
                // 静态的本地线程变量来存储租户信息
                TENANT_ID_LOCAL.set(tenantId);
                // TODO 根据租户id查询schema逻辑数据库  从redis或者数据库中查询
                String schema = null;
                // 静态的本地线程变量来存储数据库信息
                SCHEMA_LOCAL.set(schema);
                // chain
                chain.doFilter(request, response);
            } else {
                chain.doFilter(request, response);
            }
        } finally {
            SCHEMA_LOCAL.remove();
        }
    }
    @Override
    public void destroy() {
    }
}

sql拦截器 代码:

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

    private final static String[] scrm_public = { "public_tenant"};
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        String schema = MyCatFilter.getSchema();
        if (invocation.getTarget() instanceof RoutingStatementHandler) {
            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");
            // 获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
            String sql = boundSql.getSql();
            String NowSql = "/*!mycat:schema = " + schema + " */  " + sql;
            System.out.println(NowSql);
            // 利用反射设置当前BoundSql对应的sql属性为我们修改完的Sql语句
            ReflectHelper.setFieldValue(boundSql, "sql", NowSql);
        }
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }

}

ReflectHelper.class

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 = FieldUtils.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();  
        }   
    }   
}  

demo传送门

你可能感兴趣的:(基于mycat实现多租户)