Mybatis的分表实战

前言:
  以前写代码, 关于mysql的分库分表已被中间件服务所支持, 业务代码涉及的sql已规避了这块. 它对扩展友好, 你也不知道到底他分为多少库, 多少表, 一切都是透明的.
  不过对于小的团队/工作室而言, 可能就没有那么强大的分布式中间件的基础设施支持了, 而当数据库上去的时候, 分库分表就需要客户端client这边去支持维护了. 如何优雅地使用mybatis支持分表, 这就是本文的主题.

系列相关文章:
  1. spring+mybatis的多源数据库配置实战 
  参考的博文:
  1. MyBatis拦截器原理探究 
  2. SpringMVC + MyBatis分库分表方案

  3. 利用Mybatis拦截器对数据库水平分表

mybatis插件机制:
  mybatis支持插件(plugin), 讲得通俗一点就是拦截器(interceptor). 它支持ParameterHandler/StatementHandler/Executor/ResultSetHandler这四个级别进行拦截.
  总体概况为:

  • 拦截参数的处理(ParameterHandler)
  • 拦截Sql语法构建的处理(StatementHandler)
  • 拦截执行器的方法(Executor)
  • 拦截结果集的处理(ResultSetHandler)

  比如sql rewrite, 它属于StatementHandler的阶段. 以分表实践为例, 它可以简单理解为把table名称替换为分表table名称的过程.

模拟实战:
  让我们模拟实战一回, 假定我们有个需求, 就是把重要的业务日志数据, 导入到表tb_record中.

CREATE TABLE `tb_record` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `logs` varchar(128) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  但是现在随着业务数据暴增, 单表支撑不了这么多数据. 因此决定把tb_record做水平切分, 按天来做切分tb_record_{yyyyMMdd}, 比如2018/07/26这天的数据, 就导入到表tb_record_20180726中.
  之前的mapper接口类如下:

public interface RecordMapper {

    @Insert("INSERT INTO tb_record(logs) VALUES(#{logs})")
    int addRecord(@Param("logs") String logs);

}

  在不改变代码的前提下, 如何支持分表的无感知实现.

代码编写:
  由于mybatis的拦截器是全局的, 因此这边引入特定的注解用于区分目标/非目标对象(数据库表).
  定义分表策略接口和具体的实现类:

// 分表的策略类
public interface ITableShardStrategy {

    String tableShard(String tableName);

}

// 按天切分的分表策略类
public class DateTableShardStrategy implements ITableShardStrategy {

    private static final String DATE_PATTERN = "yyyyMMdd";

    @Override
    public String tableShard(String tableName) {
        SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN);
        return tableName + "_" + sdf.format(new Date());
    }

}

  定义注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableShard {

    // 要替换的表名
    String tableName();

    // 对应的分表策略类
    Class<? extends ITableShardStrategy> shardStrategy();

}

  编写具体的mybatis拦截器实现:

@Intercepts({
        @Signature(
        	type = StatementHandler.class,
        	method = "prepare",
        	args = { Connection.class, Integer.class }
        )
})
public class TableShardInterceptor implements Interceptor {

    private static final ReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();

    @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.mappedStatement");

        String id = mappedStatement.getId();
        id = id.substring(0, id.lastIndexOf(‘.‘));
        Class clazz = Class.forName(id);

        // 获取TableShard注解
        TableShard tableShard = (TableShard)clazz.getAnnotation(TableShard.class);
        if ( tableShard != null ) {
            String tableName = tableShard.tableName();
            Class<? extends ITableShardStrategy> strategyClazz = tableShard.shardStrategy();
            ITableShardStrategy strategy = strategyClazz.newInstance();
            String newTableName = strategy.tableShard(tableName);
            // 获取源sql
            String sql = (String)metaObject.getValue("delegate.boundSql.sql");
            // 用新sql代替旧sql, 完成所谓的sql rewrite
            metaObject.setValue("delegate.boundSql.sql", sql.replaceAll(tableName, newTableName));
        }

        // 传递给下一个拦截器处理
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身, 减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
    }

}

  注: 不同mybatis的版本, 具体的api略有出入, 当前mybatis版本为(3.4.6).

  配置plugin标签, 注意要在mybatis-config.xml(mybatis全局属性配置文件)中进行配置

<plugins>
    <plugin interceptor="com.springapp.mvc.mybatis.TableShardInterceptor"></plugin>
</plugins>

  

测试:
  对原来的RecordMapper添加@TableShard注解:

@TableShard(tableName = "tb_record", shardStrategy = DateTableShardStrategy.class)
public interface RecordMapper {

    @Insert("INSERT INTO tb_record(logs) VALUES(#{logs})")
    int addRecord(@Param("logs") String logs);

}

  编写简单的测试代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:application-context.xml"})
public class RecordMapperTest {

    @Resource
    private RecordMapper recordMapper;

    @Test
    public void testAddRecord() {
        String logs = "hello lilei";
        recordMapper.addRecord(logs);
    }

}

  查看数据库进行数据验证:
  

后记:
  总的来说, mybatis的拦截器给开发者很大的自由度, 像这边的分表实践是很好的例子. 但分表的策略有很多, 很多都是基于特定的维度进行散列, 总觉得在拦截器中实现, 多少有些侵入性, 要做到无感透明, 其实还是挺难的.

原文地址:https://www.cnblogs.com/mumuxinfei/p/9371986.html

时间: 2024-10-07 22:38:22

Mybatis的分表实战的相关文章

谈谈利用Mybatis分库分表

分库 在分库的时候 有时候为了方便 一些表需要存放所有库的信息,称为全局库.如:用户表存放所有的用户. 此时分库的思路 数据库分为全局库和业务库,其中业务库又分为N多个库,全局库只放个别表方便开发. 这个时候 就需要一个全局DAO,此时我们的Mybatis就需要支持两个DAO 两个DAO(bizDao和globalDao)就需要有两个sqlSessionFactory,bizSqlSessionFactory和globalSqlSessionFactory和两个事物管理器transactionM

mysql分表实战

本文主要讲述如何使用存储过程完成本表.并不讨论其他问题.首先我们得看看手册上关于meger引擎的说明: MERGE存储引擎,也被认识为MRG_MyISAM引擎,是一个相同的可以被当作一个来用的MyISAM表的集合.“相同”意味着所有表同样的列和索引信息.你不能合并列被以不同顺序列于其中的表,没有恰好同样列的表,或有不同顺序索引的表.而且,任何或者所有的表可以用myisampack来压缩. 首先看看要分的表t3,总共有5000000条记录.t3表的结构如下: CREATE TABLE `t3` (

MyBatis实现Mysql数据库分库分表操作和总结

阅读目录 前言 MyBatis实现分表最简单步骤 分离的方式 分离的策略 分离的问题 分离的原则 实现分离的方式 总结 前言 作为一个数据库,作为数据库中的一张表,随着用户的增多随着时间的推移,总有一天,数据量会大到一个难以处理的地步.这时仅仅一张表的数据就已经超过了千万,无论是查询还是修改,对于它的操作都会很耗时,这时就需要进行数据库切分的操作了. MyBatis实现分表最简单步骤 既然文章的标题都这么写了,不如直接上干货来的比较实际,我们就先来看看如何实现最简单的分表. 1.我们模拟用户表数

sharding-jdbc结合mybatis实现分库分表功能

最近忙于项目已经好久几天没写博客了,前2篇文章我给大家介绍了搭建基础springMvc+mybatis的maven工程,这个简单框架已经可以对付一般的小型项目.但是我们实际项目中会碰到很多复杂的场景,比如数据量很大的情况下如何保证性能.今天我就给大家介绍数据库分库分表的优化,本文介绍mybatis结合当当网的sharding-jdbc分库分表技术(原理这里不做介绍) 首先在pom文件中引入需要的依赖 <dependency> <groupId>com.dangdang</gr

企业Shell实战-MySQL分库分表备份脚本

本文来自http://www.xuliangwei.com/xubusi/252.html 免费视频讲解见 http://edu.51cto.com/course/course_id-5064.html 企业Shell实战-MySQL分库分表备份 今天是2015年的最后一天,大家都开心的跨年,而我还在苦逼的呵呵-省略 此处内容来自老男孩教育oldboy以及老男孩26期王续精彩分享整理而来  为表示感谢,特整理此篇博文分享给大家! 项目联系笔者QQ:572891887   也可以加入架构师交流群:

Java Mybatis Plugin插件实现分表路由规则

Mybatis Plugin插件种类 mybatis支持对于Executor.StatementHandler.PameterHandler.ResultSetHandler做拦截.要想通过拦截器做分表路由可以在Executor或StatementHandler两个阶段进行拦截.本次的路由实现是在StatementHandler拦截Sql在通过Rule修改Sql的表名,这样系统原有的Sql不用修改表名会自动替换成路由计算出的表名. 定义mybatis-config.xml配置文件 <plugin

mybatis的sharding分表

最近业务系统处理数据量比较大,需要分表,由于用的mybatis做持久化,有sharding插件可以直接用,下面是自己做的demo部分 一.Pom.xml添加maven依赖, <dependency> <groupId>org.shardbatis</groupId> <artifactId>shardbatis</artifactId> <version>2.0.0D</version> </dependency&g

(转)企业Shell实战-MySQL分库分表备份脚本

本文来自http://www.xuliangwei.com/xubusi/252.html 免费视频讲解见 http://edu.51cto.com/course/course_id-5064.html 企业Shell实战-MySQL分库分表备份 今天是2015年的最后一天,大家都开心的跨年,而我还在苦逼的呵呵-省略 此处内容来自老男孩教育oldboy以及老男孩26期王续精彩分享整理而来  为表示感谢,特整理此篇博文分享给大家! 项目联系笔者QQ:572891887   也可以加入架构师交流群:

Sharding-Sphere 3.X 与spring与mybatis集成(分库分表)demo

最近在弄这个sharding-sphere,公司内部分库分表是在此业务代码上进行逻辑分库分表,但是这种总是不好,也调研了几款分库分表中间件.mycat.网易cetus.阿里DRDS.这几种就是背景强大,大公司经过大量的实战,成熟度很高,而框架sharding-sphere比较轻量级,最近比较火,它是以jar包形式提供服务,可以无缝连接ORM框架,并不需要额外的部署,不需要依赖,运维可以不需要改动,很多人都把sharding-sphere当成增强版的jdbc驱动,迁移代码其实没那么复杂.对于巨头公