使用Mybatis拦截器实现数据分表
Posted c.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Mybatis拦截器实现数据分表相关的知识,希望对你有一定的参考价值。
文章目录
使用Mybatis拦截器实现数据分表
在项目中我们是用Mybatis + TKMapper + mysql存储了一些消息日志,但是现在随着业务数据暴增, 单表支撑不了这么多数据. 因此决定把表做水平切分, 按照月份来给表进行切分。这样当我们需要housekeep数据的时候,就可以直接drop掉表了,不论是备份还是删除效率都会比较高
那我们就会是用到Mybaits的拦截器
Mybatis插件机制:
Mybatis支持插件(plugin), 讲得通俗一点就是拦截器(interceptor). > 它支持ParameterHandler/StatementHandler/Executor/ResultSetHandler这四个级> 别进行拦截.
总体概况为:
- 拦截参数的处理(ParameterHandler)
- 拦截Sql语法构建的处理(StatementHandler)
- 拦截执行器的方法(Executor)
- 拦截结果集的处理(ResultSetHandler)
分表注解
首先我们想要的效果是只有指定的表才需要分表,并不是全部表都需要分表,因为拦截器是全局的,所以我们需要做特殊处理,当只有指定的表我们才进行拦截和处理。
第二点,我们的分表策略目前是按照年月份来进行分表,以后可能会按照天来分表,也就是分表策略是可变的,所以我们的代码是可扩展的,能够自定义分表策略的。
基于以上两点,我们会构造一个注解,作用于表上,为我们指定分表策略,以及标记当前表是需要做分表的。
@Target(TYPE)
@Retention(RUNTIME)
public @interface TableShard
Class<? extends TableShardStrategy> shardStrategy();
分表策略接口
public interface TableShardStrategy
String getTableShardName(String tableName);
从方法名就可以看出来,返回的是一个分表后的表名
目前只有一个实现类,也就是按照年月份分表
public class DateTableShardStrategy implements TableShardStrategy
public static final String PATTERN = "yyyyMM";
@Override
public String getTableShardName(String tableName)
YearMonth yearMonth = Optional.ofNullable(ProcessContextHolder.get()).orElse(ProcessInfo.builder().yearMonth(TimeUtils.getYearMonth()).build()).getYearMonth();
String date = DateTimeFormatter.ofPattern(PATTERN).format(yearMonth);
return tableName + "_" + date;
具体的实现其实就是根据当前实现获取月份,然后在当前的表名后面加上年月。 至于为什么中间从ProcessContextHolder.get()
获取时间呢,其实是当有消息进来的时候,我们会在ThreadLocal存一个当前时间作为当前消息的全局时间。为什么需要这么做呢,就是担心一条消息在处理的过程中,出现跨月的情况,所以导致同一条消息的数据,存储在了两个不同的表。所以需要维护一个全局的时间。
之后我们就可以在表的实体类上加上该注解了@TableShard(shardStrategy = DateTableShardStrategy.class)
Mybaits的拦截器实现
最后就是Mybatis的拦截器实现了。
@Intercepts(
@Signature(
type = StatementHandler.class,
method = "prepare",
args = Connection.class, Integer.class
)
)
@Slf4j
@Component
@ConditionalOnProperty(value = "table.shard.enabled", havingValue = "true") //加上了table.shard.enabled 该配置才会生效
public class MybatisStatementInterceptor implements Interceptor
private static final ReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();
public static final String DELEGATE_BOUND_SQL_SQL = "delegate.boundSql.sql";
public static final String DELEGATE_MAPPED_STATEMENT = "delegate.mappedStatement";
@Override
public Object intercept(Invocation invocation) throws Throwable
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(statementHandler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
defaultReflectorFactory
);
MappedStatement mappedStatement = (MappedStatement)
metaObject.getValue(DELEGATE_MAPPED_STATEMENT);
Class<?> clazz = getTableClass(mappedStatement);
if (clazz == null)
return invocation.proceed();
TableShard tableShard = clazz.getAnnotation(TableShard.class); //获取表实体类上的注解
Table table = clazz.getAnnotation(Table.class);
if (tableShard != null && table != null) //如果注解存在就执行分表策略
String tableName = table.name();
Class<? extends TableShardStrategy> strategyClazz = tableShard.shardStrategy();
TableShardStrategy strategy = strategyClazz.getDeclaredConstructor().newInstance();
String tableShardName = strategy.getTableShardName(tableName);
String sql = (String) metaObject.getValue(DELEGATE_BOUND_SQL_SQL);
metaObject.setValue(DELEGATE_BOUND_SQL_SQL, sql.replaceAll(tableName, tableShardName.toUpperCase())); //替换表名
return invocation.proceed();
private Class<?> getTableClass(MappedStatement mappedStatement) throws ClassNotFoundException
String className = mappedStatement.getId();
className = className.substring(0, className.lastIndexOf('.')); //获取到BaseMapper的实现类
Class<?> clazz = Class.forName(className);
if (BaseMapper.class.isAssignableFrom(clazz))
return (Class<?>) ((ParameterizedType) (clazz.getGenericInterfaces()[0])).getActualTypeArguments()[0]; //获取表实体类
//public interface XXXMapper extends BaseMapper<XXX> 其实就是获取到泛型中的具体表实体类
return null;
@Override
public Object plugin(Object target)
if (target instanceof StatementHandler)
return Plugin.wrap(target, this);
else
return target;
好了,这样就实现了使用Mybatis拦截器进行数据分表
参考
通过MyBatis拦截器实现增删改查参数的加/解密(已上线项目)
mybatis自定义拦截器拦截sql,处理createTime,updateTime,createBy,updateBy等问题
以上是关于使用Mybatis拦截器实现数据分表的主要内容,如果未能解决你的问题,请参考以下文章
玩转SpringBoot之整合Mybatis拦截器对数据库水平分表
springboot~mybatis-plus的DynamicTableNameInnerInterceptor实现分表