Mybatis拦截器实现数据分表

 更新时间:2024年01月26日 15:09:04   作者:c.  
当数据量比较多时,放在一个表中的时候会影响查询效率,本文主要介绍了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 @Intercepts的用法

MyBatis拦截器原理探究

mybatis自定义拦截器拦截sql,处理createTime,updateTime,createBy,updateBy等问题

Mybatis的分表实战

基于mybatis拦截器分表实现

mybatis拦截器实现数据库表水平切分

到此这篇关于Mybatis拦截器实现数据分表的文章就介绍到这了,更多相关Mybatis拦截器分表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java简单模拟实现一个线程池

    Java简单模拟实现一个线程池

    本文主要介绍了Java简单模拟实现一个线程池,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-01-01
  • Java计时新姿势StopWatch详解

    Java计时新姿势StopWatch详解

    这篇文章主要介绍了Java计时新姿势StopWatch,最近公司来了个大佬,从他那里学到不少东西,其中一个就是计时的新姿势「StopWatch」,需要的朋友可以参考下
    2019-07-07
  • spring boot Rabbit高级教程(最新推荐)

    spring boot Rabbit高级教程(最新推荐)

    RabbitMQ的消息过期是基于追溯方式来实现的,也就是说当一个消息的TTL到期以后不一定会被移除或投递到死信交换机,而是在消息恰好处于队首时才会被处理,本篇文章给大家介绍spring boot Rabbit高级教程,感兴趣的朋友一起看看吧
    2023-10-10
  • Java代码性能优化的35个方法总结

    Java代码性能优化的35个方法总结

    本篇文章主要介绍了Java代码性能优化的35个方法,具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • java GUI实现加法计算器

    java GUI实现加法计算器

    这篇文章主要为大家详细介绍了java GUI实现加法计算器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • java汉字转拼音工具类分享

    java汉字转拼音工具类分享

    这篇文章主要为大家详细介绍了java汉字转拼音工具类,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • SpringAOP中基于注解实现通用日志打印方法详解

    SpringAOP中基于注解实现通用日志打印方法详解

    这篇文章主要介绍了SpringAOP中基于注解实现通用日志打印方法详解,在日常开发中,项目里日志是必不可少的,一般有业务日志,数据库日志,异常日志等,主要用于帮助程序猿后期排查一些生产中的bug,需要的朋友可以参考下
    2023-12-12
  • Java中如何保证缓存一致性问题

    Java中如何保证缓存一致性问题

    这篇文章主要介绍了Java中如何保证缓存一致性问题,文章将通过主题提出的问题展开一些解决方案分析,需要的小伙伴可以参考一下
    2022-04-04
  • java File类的基本使用方法总结

    java File类的基本使用方法总结

    这篇文章主要介绍了java File类的基本使用方法总结,为大家分享了java实现上传代码,感兴趣的小伙伴们可以参考一下
    2016-04-04
  • Java中的CopyOnWriteArrayList深入解读

    Java中的CopyOnWriteArrayList深入解读

    这篇文章主要介绍了Java中的CopyOnWriteArrayList深入解读,在 ArrayList 的类注释上,JDK 就提醒了我们,如果要把 ArrayList 作为共享变量的话,是线程不安全的,需要的朋友可以参考下
    2023-12-12

最新评论