Mybatis的分表实战
前言:
以前写代码, 关于mysql的分库分表已被中间件服务所支持, 业务代码涉及的sql已规避了这块. 它对扩展友好, 你也不知道到底他分为多少库, 多少表, 一切都是透明的.
不过对于小的团队/工作室而言, 可能就没有那么强大的分布式中间件的基础设施支持了, 而当数据库上去的时候, 分库分表就需要客户端client这边去支持维护了. 如何优雅地使用mybatis支持分表, 这就是本文的主题.
系列相关文章:
1. spring+mybatis的多源数据库配置实战
参考的博文:
1. MyBatis拦截器原理探究
2. SpringMVC + 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的拦截器给开发者很大的自由度, 像这边的分表实践是很好的例子. 但分表的策略有很多, 很多都是基于特定的维度进行散列, 总觉得在拦截器中实现, 多少有些侵入性, 要做到无感透明, 其实还是挺难的.
Mybatis的分表实战的更多相关文章
- SpringMVC + MyBatis分库分表方案
mybatis作为流行的ORM框架,项目实际使用过程中可能会遇到分库分表的场景.mybatis在分表,甚至是同主机下的分库都可以说是完美支持的,只需要将表名或者库名作为动态参数组装sql就能够完成.但 ...
- Mybatis中分表插件shardbatis使用说明
Mybatis中实现分表,有个很简单的插件,叫shardbatis,使用maven构建的工程,可以在pom.xml中添加依赖性即可: <!-- 分库分表插件 --> <depende ...
- 如何用Mybatis分库分表
分库 在分库的时候 有时候为了方便 一些表需要存放所有库的信息,称为全局库.如:用户表存放所有的用户. 此时分库的思路 数据库分为全局库和业务库,其中业务库又分为N多个库,全局库只放个别表方便开发. ...
- mysql分表实战
本文主要讲述如何使用存储过程完成本表.并不讨论其他问题.首先我们得看看手册上关于meger引擎的说明: MERGE存储引擎,也被认识为MRG_MyISAM引擎,是一个相同的可以被当作一个来用的MyIS ...
- MyBatis实现Mysql数据库分库分表操作和总结
前言 作为一个数据库,作为数据库中的一张表,随着用户的增多随着时间的推移,总有一天,数据量会大到一个难以处理的地步.这时仅仅一张表的数据就已经超过了千万,无论是查询还是修改,对于它的操作都会很耗时,这 ...
- Java实战:教你如何进行数据库分库分表
摘要:本文通过实际案例,说明如何按日期来对订单数据进行水平分库和分表,实现数据的分布式查询和操作. 本文分享自华为云社区<数据库分库分表Java实战经验总结 丨[绽放吧!数据库]>,作者: ...
- 数据库分表之Mybatis+Mysql实践(含部分关键代码)
2018年01月31日 随着我们系统用户数量的日增,业务数据处于一个爆发前,增长的数据量已经给我们的系统造成了很大的不确定.在上个周末用户量较多,并发较大的情况下,读写频繁的验证码表,数据量 ...
- sharding-jdbc结合mybatis实现分库分表功能
最近忙于项目已经好久几天没写博客了,前2篇文章我给大家介绍了搭建基础springMvc+mybatis的maven工程,这个简单框架已经可以对付一般的小型项目.但是我们实际项目中会碰到很多复杂的场景, ...
- spring+mybatis的插件【shardbatis2.0】+mysql+java自定义注解实现分表
一.业务场景分析 只有大表才需要分表,而且这个大表还会有经常需要读的需要,即使经过sql服务器优化和sql调优,查询也会非常慢.例如共享汽车的定位数据表等. 二.实现步骤 1.准备pom依赖 < ...
随机推荐
- Linux中安装tomcat后,window中访问不到tomcat的欢迎界面问题
首先,可以通过xftp把下载的tomcat的tar.gz包传输到Linux中. 然后进行解压,tar -zxvf tomcat的压缩包名称(可以使用tab键快速补齐) 解压后,可以使用修改/con ...
- 信用卡欺诈数据的分析-excel篇
本篇文章为大家提供了数据集分析的思路和步骤,同时也分享了自己的经验. 一.背景 反欺诈是一项识别服务,是对交易诈骗.网络诈骗.电话诈骗.盗卡盗号等行为的一项风险识别.其核心是通过大数据的收集.分析和处 ...
- AngelToken——富有价值的区块链服务平台
关于我们 Angel Token,简称ANG,是基于ETH代币的去中心化数字交易平台. 行业现状 截至2017年12月,全球数字货币总市值已经触及6000亿美元.而2016年12月31日,这个数字才仅 ...
- http短连接与长连接简介
1.HTTP协议与TCP/IP协议的关系 HTTP的长连接和短连接本质上是TCP长连接和短连接.HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议.IP协议主要解决网络路由和寻址问题 ...
- Event Loop
Event Loop 是 JavaScript 异步编程的核心思想,也是前端进阶必须跨越的一关.同时,它又是面试的必考点,特别是在 Promise 出现之后,各种各样的面试题层出不穷,花样百出.这篇文 ...
- [Codeforces543D]Road Improvement
Problem 刚开始每条边都是坏的,现在要选取一个点使得其他点到这个点的路径上最多只有一条坏路,问至少要修好多少条边 Solution 如果以1为根,那么是个简单的树形DP 设根从u转移到v,那么u ...
- R语言最优化(一维)
最优化问题是普遍存在的,以前上运筹学课的时候也接触过最优化相关的问题,当时主要是理论课,并且关注的重点是单纯形法.运输问题以及图论等,这里指的最优化是指函数的最优化,即函数的极值,由于寻找一个局部最优 ...
- Ubuntu16.04 ionic(jdk,sdk,gradle)环境搭建完全攻略
在Ubuntu16.04当中搭建一个ionic环境还是按照官方教程的来,主要问题是首先要把JDK,SDK搭好,环境变量配好.本文中给的包的下载请不要直接用浏览器下载,很慢,尽量用wget 下载,重要的 ...
- pgmpy安装
1.anaconda安装,可能在cmd中输入conda会报错,是因为在安装的时候没有配置好环境变量,在安装的时候有一个框框没选(当时是not recommand), 解决办法:需要在系统环境变量中添加 ...
- Docker桥接宿主机网络与配置固定IP地址
有些需求是把这个容器与宿主机在同一个网段,但是本人不建议这样子去操作,因为一个容器本身就是一个封装好的服务.建议去按默认的网络去实现. 临时设置 [root@linux-docker01 ~]# vi ...