流程
- 定义注解,利用AOP获取进行分库分表的字段属性值,根据分库分表字段的属性值,确定某个元组插入的数据库序号以及表的序号
- 根据数据库序号选择确定使用哪一个数据库
- 根据表序号确定使用哪个表
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();
}
}