业务上做了水平分表,公司基础架构没有提供分表中间件,开源的中间件用起来门槛高,有较大的学习成本。自己基于springboot+mybatis+jsqlparser实现了一个简单的分表插件。
笼统来说就是拦截SQL,分析SQL,替换tableName,返回重写后的SQL给Mybatis,支持简单的CRUD分表,支持join分表。
项目里引入maven依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>x.x.x</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>x.x.x</version>
</dependency>
1.分表注解
首先需要一个注解,用来标识需要分表的类或方法。
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE, ElementType.METHOD})
public @interface TableSplit {
//是否分表
boolean split() default true;
//表名 该字段暂未使用。使用注解会对SQL中所有的表名进行替换
String table() default "";
//分表字段
String field() default "";
//分表策略
String strategy() default "";
}
2.分表策略
用策略模式设计一个分表的策略,在后续的mybatis拦截器中使用
2.1策略接口
public interface Strategy {
String TABLE_NAME = "table_name";
String SPLIT_FIELD = "split_field";
String EXECUTE_PARAM_DECLARE = "execute_param_declare";
String EXECUTE_PARAM_VALUES = "execute_param_values";
/**
* convert sql
* @param params
* @return
* @throws Exception
*/
String convert(String sql, Map<String, Object> params) throws Exception;
/**
*
* @param params
* @return
* @throws Exception
*/
Integer getTableIndex(Map<String, Object> params) throws Exception;
}
2.2 分表策略管理类
管理类维护一个map,可以通过key获取不同的分表策略。
@Slf4j
public class StrategyManager {
private Map<String, Strategy> strategies = new ConcurrentHashMap<>(10);
public Strategy getStrategy(String key) {
return strategies.get(key);
}
public Map<String, Strategy> getStrategies() {
return strategies;
}
public void setStrategies(Map<String, String> strategies) {
for (Map.Entry<String, String> entry : strategies.entrySet()) {
try {
this.strategies.put(entry.getKey(), (Strategy) Class.forName(entry.getValue()).newInstance());
} catch (Exception e) {
log.error("实例化策略出错", e);
}
}
}
}
2.3 分表策略的实现
分表策略的具体实现可以根据实际场景和业务自行实现,比如下面这实现类就是我在项目中使用的,用userId字段对8取模得到表的下标值。
public class RemainderStrategy implements Strategy {
/**
* 取模基数
*/
private static final int DIVIDER = 8;
@Override
public String convert(String sql, Map<String, Object> params) throws Exception {
MySqlParserFactory sqlParserFactory = new MySqlParserFactory();
Integer reminder = this.getIndex(params);
return sqlParserFactory.parser(sql, reminder);
}
@Override
public Integer getTableIndex(Map<String, Object> params) throws Exception {
Integer reminder = this.getIndex(params);
return reminder;
}
private Integer getIndex(Map<String, Object> params) throws IllegalAccessException