mybatis整合mycat实现分库

mybatis整合mycat实现分库_第1张图片

文章目录

  • 前言
  • 如何获取当前线程的租户
    • 实现
  • Mycat 服务搭建
  • 添加`mybatis`的拦截器
  • 测试
  • 参考

前言

工作中我们可能会遇到的一个问题,可能会出现多租户场景,这种情况下,我们不得不对我们的系统分库,对于每一个租户来说都是一个数据库,这个我们可能考虑到多数据源去解决,也是一个思路,这几天调研了mycat做分库,下面慢慢分享这一个过程。

如何获取当前线程的租户

我们首先要解决这个问题,今天首先要出场的是ThreadLocal,对于这个类我的解释是:

  • 保存线程上下文信息,在任意需要的地方可以获取!!!
  • 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!

之前看过阿里规范有这条:
mybatis整合mycat实现分库_第2张图片

实现

首先来一个接口BatmanTenant,这个接口主要封装两个方法。

public interface BatmanTenant {
    void setBatmanTenantId(String var1);

    String getBatmanTenantId();
}

接着创建一个实现类TenantStore去实现BatmanTenant,一个静态变量CONTEXT存取上下文的租户信息。

public class TenantStore implements BatmanTenant {
    private static final ThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>();

    private static boolean isApplicationTenant = false;

    private static String applicationTenantId;

    private static final String TENANT_DEFAULT_ID = "t0";

    public static void setTenantId(String tenantId) {
        CONTEXT.set(tenantId);
    }

    public static String getTenantId() {
        if (isApplicationTenant) {
            return applicationTenantId;
        }

        String tenantId = CONTEXT.get();
        if (tenantId == null || "".equals(tenantId)) {
            tenantId = TENANT_DEFAULT_ID;
        }
        return tenantId;
    }

    public static void clear() {
        CONTEXT.remove();
    }

    public static boolean isApplicationTenant() {
        return isApplicationTenant;
    }

    public static void setApplicationTenant(boolean applicationTenant) {
        isApplicationTenant = applicationTenant;
    }

    public static String getApplicationTenantId() {
        return applicationTenantId;
    }

    public static void setApplicationTenantId(String applicationTenantId) {
        TenantStore.applicationTenantId = applicationTenantId;
    }

    @Override
    public void setBatmanTenantId(String s) {
        setTenantId(s);
    }

    @Override
    public String getBatmanTenantId() {
        return getTenantId();
    }
}

Mycat 服务搭建

mycat是一个数据库中间件,也可以理解为是数据库代理。在架构体系中是位于数据库和应用层之间的一个组件,并且对于应用层是透明的,即数据库 感受不到mycat的存在,认为是直接连接的mysql数据库。

  • 下载链接 http://dl.mycat.io/1.6.7.4/,现在对应的版本
  1. 解压修改配置文件, 首先是conf目录下的server.xml文件,修改mycat的用户名及密码。默认端口号是8066。
<user name="root">
		<property name="password">batmanproperty>
        <property name="schemas">t1,t2property>
	user>
  1. 修改conf目录下的schema.xml,将下面配置拷贝过去即可。


<mycat:schema xmlns:mycat="http://io.mycat/">

    <schema name="t1" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn_t1" />
    <schema name="t2" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn_t2" />

    <dataNode name="dn_t1" dataHost="dh" database="t1"/>
    <dataNode name="dn_t2" dataHost="dh" database="t2"/>

    
    <dataHost name="dh" maxCon="1000" minCon="10" balance="0" writeType="0"
              dbType="mysql" dbDriver="native" switchType="2" slaveThreshold="100">
        
       <heartbeat>select user()heartbeat>
       <writeHost host="tenant_db" url="localhost:3306" user="root" password="root">
            <readHost host="tenant_db" url="localhost:3306" user="root" password="root"/>
        writeHost>
    dataHost>
mycat:schema>

  1. 测试
启动:
./mycat start
 
 
查看启动状态:
./mycat status
 
 
停止:
./mycat stop
 
 
重启(改变上面的 xml 配置不用重启,管理端可以重新载入):
./mycat restart
 
 
查看 logs/ 下的 wrapper.log 和 mycat.log 可以查看运行时问题和异常。
mycat 启动日志:
cat ./logs/wrapper.log
 
mycat 应用日志:
cat ./logs/mycat.log

添加mybatis的拦截器

创建TenantInterceptor这个文件,从StatementHandler获取到BoundSql对象,这样就获取到要执行的sql,把mycat的配置和租户信息数据库配置好,利用反射写回BoundSqlsql属性。

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class TenantInterceptor implements Interceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(TenantInterceptor.class);

    private static final String SCHEMA_START = "/*mycat:schema=";

    private static final String SCHEMA_END = "*/";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        String tenant = TenantStore.getTenantId();

        if (tenant == null || "".equals(tenant)) {
            return invocation.proceed();
        }
        StatementHandler statementHandler = realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        String sql = boundSql.getSql();
        //LOGGER.debug("TenantInterceptor before sql:" + sql);

        //add sql mycat hits for sql route
        //sql = "/*!mycat:schema=" + tenant + "*/" + sql;
        if (!sql.startsWith(SCHEMA_START)) {
            StringBuilder stringBuilder = new StringBuilder(sql.length() + 30);
            stringBuilder.append(SCHEMA_START);
            stringBuilder.append(tenant);
            stringBuilder.append(SCHEMA_END);
            stringBuilder.append(sql);
            sql = stringBuilder.toString();
        }

        LOGGER.debug("TenantInterceptor after sql:" + sql);
        ReflectHelper.setFieldValue(boundSql, "sql", sql);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 

* 获得真正的处理对象,可能多层代理. *

*/
@SuppressWarnings("unchecked") private static <T> T realTarget(Object target) { if (Proxy.isProxyClass(target.getClass())) { MetaObject metaObject = SystemMetaObject.forObject(target); return realTarget(metaObject.getValue("h.target")); } return (T) target; } }

配置拦截器,创建MultiTenantMyBatisConfiguration,给每个SqlSessionFactory对象添加拦截。

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, Interceptor.class})
public class MultiTenantMyBatisConfiguration {

    @Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;

    @PostConstruct
    public void addPageInterceptor() {
        for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
            sqlSessionFactory.getConfiguration().addInterceptor(new TenantInterceptor());
        }
    }
}

测试

创建两个数据库分别为t1,t2。两个数据库有都有demo这张表。测试下面接口,会出现不同的结果。
mybatis整合mycat实现分库_第3张图片
github地址:https://github.com/fafeidou/fast-cloud-nacos/tree/master/fast-common-examples/fast-common-tenant-example

参考

  • https://blog.csdn.net/lone7dehao/article/details/99584699
  • https://www.cnblogs.com/sweetchildomine/p/7819424.html

你可能感兴趣的:(mybatis)