mybatis-plus还可以这样分表
为什么要分表
Mysql是当前互联网系统中使用非常广泛的关系数据库,具有ACID的特性。
但是mysql的单表性能会受到表中数据量的限制,主要原因是B+树索引过大导致查询时索引无法全部加载到内存。读取磁盘的次数变多,而磁盘的每次读取对性能都有很大的影响。
这时一个简单可行的方案就是分表(当然土豪也可以堆硬件),将一张数据量庞大的表的数据,拆分到多个表中,这同时也减少了B+树索引的大小,减少磁盘读取次数,提高性能。
两种基础分表逻辑
说完了为什么要分表,下面聊聊业务开发中常见的两种基础的分表逻辑。
按日期分表
这种方式通常会在表名的最后加上年月日,主要适用于按日期划分的统计数据或操作记录。在线实时展示的只有最近表中的数据,其他数据用于离线统计等。
按id取模分表
这种方式需要一个id生成器,例如snowflake id或分布式id服务。它保证了相同id的数据都在一张表中,主要适用于保存用户基础信息,系统中的资源信息,购买记录等。当然这种分表方式扩展性较差,后期数据持续增多后需要按id大小分库再分表处理。
下面看下这两种分表逻辑在mybatis-plus中的实现。
Mybatis-plus中的分表实现
说到java的分表中间件,可能有人会想到sharding-jdbc,作为使用很广泛的一个分表中间件,功能也比较完善,但是使用它需要引入额外的jar包和增加学习成本。
实际上mybatis-plus本身就提供了一个分表的解决方案,配置使用都很简单,适合快速开发系统。
动态表名处理器
没错,mybatis-plus提供了动态表名处理器接口TableNameHandler,只需要在系统中实现该接口,并作为插件加载到mybatis-plus中就可以使用,下面来看下详细的步骤。
3.4版本之前的动态表名接口是ITableNameHandler,需要和分页插件配合使用。
3.4版本新增了
TableNameHandler,在方法参数上取消了MetaObject。这里用最新的版本为例,使用方式差别不大。
假设我们的系统中有两种分表方式,按日期分表和按id取模分表。通过四个步骤来看下具体的使用示例。
1.创建日期表名处理器
先来看下日期处理的表名处理器,实现TableNameHandler接口后,在dynamicTableName方法中实现动态生成表名的逻辑,方法的返回值就是查询时要使用的表名。
/**
* 按天分表解析
*/
public class DaysTableNameParser implements TableNameHandler {
@Override
public String dynamicTableName(String sql, String tableName) {
String dateDay = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
return tableName + "_" + dateDay;
}
}
2.创建id取模表名处理器
再来看下按id取模表名处理器的实现,这个处理器相对日期处理就要复杂一些,主要原因为需要动态传入用于分表的id值。
在之前的版本中可以在方法中通过解析MetaObject中带有的sql查询信息,获取分表使用的值。但是这种方式比较复杂,对于不同的QueryMapper分析的方式不同,比较容易出错。新版本中的方法取消了MetaObject参数,需要使用其他方式传入。
需要注意的是,表名处理器是作为mybatis-plus的插件,在项目启动时实例化的。这意味着,在运行过程中只有一个对象,多线程处理过程中,一个线程对参数的修改,会影响到其他线程。为了解决这个问题,可以使用ThreadLocal来定义参数。
由于现在的框架中大部分会使用线程池,例如springboot web项目中的tomcat。所以在每次使用后,需要手动清除本次数据,防止线程复用时的影响。
具体实现如下:
/**
* 按id取模分表处理器
*/
public class IdModTableNameParser implements TableNameHandler {
private Integer mod;
//使用ThreadLocal防止多线程相互影响
private static ThreadLocal<Integer> id = new ThreadLocal<Integer>();
public static void setId(Integer idValue) {
id.set(idValue);
}
IdModTableNameParser(Integer modValue) {
mod = modValue;
}
@Override
public String dynamicTableName(String sql, String tableName) {
Integer idValue = id.get();
if (idValue == null) {
throw new RuntimeException("请设置id值");
} else {
String suffix = String.valueOf(idValue % mod);
//这里清除ThreadLocal的值,防止线程复用出现问题
id.set(null);
return tableName + "_" + suffix;
}
}
}
3.加载表名处理器
表名处理器实际是mybatis-plus的插件,需要在初始化时创建实例并加载。因为系统中存在两种分表类型,在初始化时可以指定每张表使用的表名处理器。具体实现如下:
@Configuration
@MapperScan(basePackages = "com.yourcom.proname.repository.mapper.mainDb*", sqlSessionFactoryRef = "mainSqlSessionFactory")
public class MainDb {
@Bean(name = "mainDataSource")
@ConfigurationProperties(prefix = "dbconfig.maindb")
public DataSource druidDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "mainTransactionManager")
public DataSourceTransactionManager masterTransactionManager(@Qualifier(value = "mainDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "mainSqlSessionFactory")
@ConfigurationPropertiesBinding()
public SqlSessionFactory sqlSessionFactory(@Qualifier(value = "mainDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
//加载插件
factoryBean.setPlugins(mybatisPlusInterceptor());
return factoryBean.getObject();
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
HashMap<String, TableNameHandler> map = new HashMap<String, TableNameHandler>();
//这里为不同的表设置对应表名处理器
map.put("user_daily_record", new DaysTableNameParser());
map.put("user_consume_flow", new IdModTableNameParser(10));
dynamicTableNameInnerInterceptor.setTableNameHandlerMap(map);
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
return interceptor;
}
}
4.在controller中使用
下面通过controller中的三个接口,展示下使用方式:
@RestController
public class TableTestController {
@Resource
IUserDailyRecordService userDailyRecordService;
@Resource
IUserConsumeFlowService userConsumeFlowService;
@GetMapping("user/record/today")
public CommonResVo<UserDailyRecord> getRecordToday(Integer userId) throws Exception {
//这里在查询时,会根据系统当前时间,自动生成当天的表名
UserDailyRecord userDailyRecord = userDailyRecordService.getOne(new LambdaQueryWrapper<UserDailyRecord>().eq(UserDailyRecord::getUserId, userId));
return CommonResVo.success(userDailyRecord);
}
@GetMapping("user/consume/flow")
public CommonResVo<List<UserConsumeFlow>> getConsumeFlow(Integer userId) throws Exception {
//设置用于分表的id值
IdModTableNameParser.setId(userId);
List<UserConsumeFlow> userConsumeFlowList = userConsumeFlowService.list(new LambdaQueryWrapper<UserConsumeFlow>().eq(UserConsumeFlow::getUserId, userId));
return CommonResVo.success(userConsumeFlowList);
}
/**
* 新增数据
*/
@PostMapping("user/consume/flow")
public CommonResVo<Boolean> addConsumeFlow(@RequestBody UserConsumeFlow userConsumeFlow) throws Exception {
Integer userId = userConsumeFlow.getUserId();
//设置用于分表的id值
IdModTableNameParser.setId(userId);
userConsumeFlowService.save(userConsumeFlow);
return CommonResVo.success(true);
}
}
这篇对mybatis-plus动态表名处理器的介绍,通过实现TableNameHandler接口,可以按实际情况灵活定义表名的生成规则,希望对大家有帮助。
项目完整示例地址:https://gitee.com/dothetrick/web-demo/tree/tabel-shading
以上内容属个人学习总结,如有不当之处,欢迎在评论中指正
mybatis-plus还可以这样分表的更多相关文章
- sharding-jdbc结合mybatis实现分库分表功能
最近忙于项目已经好久几天没写博客了,前2篇文章我给大家介绍了搭建基础springMvc+mybatis的maven工程,这个简单框架已经可以对付一般的小型项目.但是我们实际项目中会碰到很多复杂的场景, ...
- 基于mybatis拦截器分表实现
1.拦截器简介 MyBatis提供了一种插件(plugin)的功能,但其实这是拦截器功能.基于这个拦截器我们可以选择在这些被拦截的方法执行前后加上某些逻辑或者在执行这些被拦截的方法时执行自己的逻辑. ...
- 分表需要解决的问题 & 基于MyBatis 的轻量分表落地方案
分表:垂直拆分.水平拆分 垂直拆分:根据业务将一个表拆分为多个表. 如:将经常和不常访问的字段拆分至不同的表中.由于与业务关系密切,目前的分库分表产品均使用水平拆分方式. 水平拆分:根据分片算法将一个 ...
- Mybatis中分表插件shardbatis使用说明
Mybatis中实现分表,有个很简单的插件,叫shardbatis,使用maven构建的工程,可以在pom.xml中添加依赖性即可: <!-- 分库分表插件 --> <depende ...
- MySQL性能优化(五):分表
原文:MySQL性能优化(五):分表 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/vbi ...
- php面试专题---MySQL分表
php面试专题---MySQL分表 一.总结 一句话总结: 分库分表要数据达到一定的量级才用,这样才有效率,不然利不一定大于弊,可能会增加一次I/O消耗 1.分库分表的使用量级是多少? 单表行数超过 ...
- MyBatis实现Mysql数据库分库分表操作和总结
前言 作为一个数据库,作为数据库中的一张表,随着用户的增多随着时间的推移,总有一天,数据量会大到一个难以处理的地步.这时仅仅一张表的数据就已经超过了千万,无论是查询还是修改,对于它的操作都会很耗时,这 ...
- 数据库分表之Mybatis+Mysql实践(含部分关键代码)
2018年01月31日 随着我们系统用户数量的日增,业务数据处于一个爆发前,增长的数据量已经给我们的系统造成了很大的不确定.在上个周末用户量较多,并发较大的情况下,读写频繁的验证码表,数据量 ...
- Mybatis的分表实战
前言: 以前写代码, 关于mysql的分库分表已被中间件服务所支持, 业务代码涉及的sql已规避了这块. 它对扩展友好, 你也不知道到底他分为多少库, 多少表, 一切都是透明的. 不过对于小的团队/工 ...
随机推荐
- 算法图解...pdf
电子书资源:算法图解 书籍简介 本书示例丰富,图文并茂,以让人容易理解的方式阐释了算法,旨在帮助程序员在日常项目中更好地发挥算法的能量.书中的前三章将帮助你打下基础,带你学习二分查找.大O表示法. ...
- .NET6 平台系列2 .NET Framework框架详解
系列目录 [已更新最新开发文章,点击查看详细] 什么是 .NET Framework? .NET Framework 是 Windows 的托管执行环境,可为其运行的应用提供各种服务. 它包括 ...
- Pandas——Series and DataFrane
数据科学--pandas库 pandas中有两个主要的数据结构,一个是Series,另一个是DataFrame.通过这两类数据,可以下载数据.可视化数据.和分析数据. Pandas安装:pip ins ...
- JMeter日志查看
- 【面试题2020-03-30】面试官:对并发熟悉吗?说说Synchronized及实现原理
一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法. Synchronized的作用主要有三个: 1.确保线程互斥的访问同 ...
- Windows核心编程 第27章 硬件输入模型和局部输入状态
第27章 硬件输入模型和局部输入状态 这章说的是按键和鼠标事件是如何进入系统并发送给适当的窗口过程的.微软设计输入模型的一个主要目标就是为了保证一个线程的动作不要对其他线程的动作产生不好的影响. 27 ...
- Ext.MessageBox.alert()弹出对话框详解
Ext.MessageBox是一个工具类,他继承自Obiect对象,用来生成各种风格的信息提示对话框,Ext.Msg是该类的别名,使用Ext.MessageBox和用Ext.Msg效果是一样的,而后者 ...
- springboot开发浅谈 2021/05/11
学习了这么久,本人希望有时间能分享一下,这才写下这篇浅谈,谈谈软件,散散心情. 这是本人的博客园账号,欢迎关注,一起学习. 一开始学习springboot,看了好多网站,搜了好多课程.零零落落学了一些 ...
- v-bind的使用
v-bind v-bind的引入 内容的绑定可以通过mustache语法,而属性的绑定往往需要通过v-bind 如动态绑定img的src属性 如动态绑定div的class属性 如动态绑定li元素的 ...
- Java容器 | 基于源码分析List集合体系
一.容器之List集合 List集合体系应该是日常开发中最常用的API,而且通常是作为面试压轴问题(JVM.集合.并发),集合这块代码的整体设计也是融合很多编程思想,对于程序员来说具有很高的参考和借鉴 ...