利用Springboot注解方式实现Mysql的分库分表, 避免在配置文件中添加每个表,只需在Mapper方法中添加注解

395 阅读2分钟

流程

  1. 定义注解,利用AOP获取进行分库分表的字段属性值,根据分库分表字段的属性值,确定某个元组插入的数据库序号以及表的序号
  2. 根据数据库序号选择确定使用哪一个数据库
  3. 根据表序号确定使用哪个表

1. 自定义注解,利用AOP获取进行分库分表的编号

根据上一步,我们确定了需要插入哪一个库和表中

2. 对Mybatis数据源进行修改,调整为需要使用的数据库

根据第一步获取的db_id和table_id放入到DBTableContext环境中,下面是确定使用哪一个数据源,如:db00、db01

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return "db" + DBTableContext.getDBId();
    }
}
@Bean
public DataSource dataSource() {
    // 读取配置文件,将db0 与db1 的信息读取到DataSource中
    Map<Object, Object> targetDataSources = new HashMap<>();
    for (String dbInfo : dataSourceMap.keySet()) {
        Map<String, Object> objMap = dataSourceMap.get(dbInfo);
        targetDataSources.put(dbInfo, new DriverManagerDataSource(objMap.get("url").toString(), objMap.get("username").toString(), objMap.get("password").toString()));
    }

    // 设置数据源
    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    dynamicDataSource.setTargetDataSources(targetDataSources);
    return dynamicDataSource;
}

所使用的数据库会根据路由自动取选择

3. 使用Mybatis 的拦截器对所插入数据的表进行修改,确定使用哪一个表

从DBTableContext环境中获取需要插入表的编号,如:User00、User01、User02

// 继承Interceptor接口, 在@Intercepts 拦截方法签名
// 目前可拦截的类型有4中,分别为newExecutor、StatementHandler、ParameterHandler、ResultSetHandler
// 它们执行的顺序是 newExecutor -> StatementHandler -> ParameterHandler -> ResultSetHandler -> StatementHandler
// Executor:拦截执行器的方法。
// StatementHandler:拦截Sql语法构建的处理。
// ParameterHandler:拦截参数的处理。
// ResultSetHandler:拦截结果集的处理。
// 由于是拦截sql语句的构建,修改表的序号,所以使用了类型的为StatementHandler
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisInterceptor implements Interceptor {

    // 正则表达式,需要对表编号进行修改
    private Pattern pattern = Pattern.compile("(from|into|update)[\s]{1,}(\w{1,})", Pattern.CASE_INSENSITIVE);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取StatementHandler

        // 获取 PreparedHandler 方法
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        // 获取SQL
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();

        // 替换SQL表名 USER 为 USER01
        Matcher matcher = pattern.matcher(sql);
        String tableName = null;
        if (matcher.find()) {
            tableName = matcher.group().trim();
        }
        assert null != tableName;
        String replaceSql = matcher.replaceAll(tableName + "_" + DBContextHolder.getTBKey());

        // 通过反射修改SQL语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, replaceSql);
        field.setAccessible(false);

        return invocation.proceed();
    }

}