mybatis整合mycat实现分库

在这里插入图片描述

前言

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

如何获取当前线程的租户

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

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

之前看过阿里规范有这条:
在这里插入图片描述

实现

首先来一个接口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数据库。

  1. 解压修改配置文件, 首先是conf目录下的server.xml文件,修改mycat的用户名及密码。默认端口号是8066。
<user name="root">
		<property name="password">batman</property>
        <property name="schemas">t1,t2</property>
	</user>
  1. 修改conf目录下的schema.xml,将下面配置拷贝过去即可。
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 设置dataNode 对应的数据库,及 mycat 连接的地址dataHost -->
    <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"/>

    <!-- mycat 逻辑主机dataHost对应的物理主机.其中也设置对应的mysql登陆信息 -->
    <dataHost name="dh" maxCon="1000" minCon="10" balance="0" writeType="0"
              dbType="mysql" dbDriver="native" switchType="2" slaveThreshold="100">
        <!--<heartbeat>select user()</heartbeat>-->
       <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) {
    }

    /**
     * <p>
     * 获得真正的处理对象,可能多层代理.
     * </p>
     */
    @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这张表。测试下面接口,会出现不同的结果。
在这里插入图片描述
github地址:https://github.com/fafeidou/fast-cloud-nacos/tree/master/fast-common-examples/fast-common-tenant-example

参考

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值