mybatis 自动分表
参考:
https://blog.csdn.net/qq_37751454/article/details/81630100
https://blog.csdn.net/Dwade_mia/article/details/77371871
相关源码已上传至我的 github
欢迎转载,转载请注明出处,尊重作者劳动成果:https://www.cnblogs.com/li-mzx/p/9963312.html
前言
小弟才疏学浅,可能很多问题也没有考虑到,权当抛砖引玉,希望各位大神指点
项目背景:
希望做一个功能,能在sql操作数据库时,根据某个字段的值,或者说某种策略, 自动操作对应的表
比如 user表
user_oa,其中userid 为 oa000001、oa000002、oa123456
user_bz,其中userid 为 bz000002
user_sr, 其中userid 为 sr654321
根据业务人员所使用的系统,将user表细分为3个
分表规则为业务人员所注册的系统,比如上面的, sr oa bz
当dao层操作数据库时,系统自动根据userid 或指明分表名,自动去操作对应的表,即1个查询,对应多个数据库相同结构的表
实现思路
1、在需要分表的实体类中, 实现接口,提供分表所需要的分表策略,否则需要在dao的操作数据库方法中,加入表名参数
2、在需要分表的Dao接口中,添加注解,声明一个需要分表的操作,供拦截器拦截
3、定义拦截器,注册到mybatis中,在mybatis使用sql语句操作数据库之前,拦截添加了注解的dao方法,修改sql语句,将其中的表名,全部添加 从参数中或实体类中取得的表名后缀
代码环境
IntelliJ IDEA 2018.2.5 + jdk1.8.0 + Spring Boot 1.5.17 + MySql 5.7 + MyBatis 1.3.2 + Druid 1.1.3
代码
maven依赖:
<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version><druid.version>1.1.3</druid.version><swagger.version>2.7.0</swagger.version></properties><dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.45</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${swagger.version}</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>${swagger.version}</version></dependency><dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>1.1</version></dependency>
maven 依赖
application.yml
server:port: 8021spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.jdbc.Driverdruid:url: jdbc:mysql://localhost:3306/local?useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8username: limzpassword: 123456initial-size: 10max-active: 100min-idle: 10max-wait: 60000pool-prepared-statements: truemax-pool-prepared-statement-per-connection-size: 20time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000validation-query: SELECT 1test-while-idle: truetest-on-borrow: falsetest-on-return: falsestat-view-servlet:enabled: trueurl-pattern: /druid/*filter:stat:log-slow-sql: trueslow-sql-millis: 1000merge-sql: truewall:config:multi-statement-allow: trueproxy-filters:list:ref: logFilter#开启debug模式,用于打印sql logging:level:com.limz.mysql.dsmysql.Dao: debug
application.yml
声明一个接口,提供获取表名后缀的方法
/*** 需要分表的实体类,必须实现的接口*/ public interface ShardEntity {/*** 需要分表的类,需要实现此方法, 提供分表后缀名的获取* @return*/String getShardName(); }
实体类实现此接口
@Data public class User implements Serializable, ShardEntity {private String userId;@NotNull(message = "用户名不能为空")private String userName;private String msg;private List<Telephone> telephones;//提供获取后缀名的方法 此处为userid 的前两位,代表所在的系统public String getShardName(){return userId != null ? userId.substring(0,2) : null;} }
声明一个注解,加此注解的dao表示需要分表
/*** 需要分表的 Dao 添加此注解,标记为需要分表*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface TableShard {//默认分表, 为false时, 此注解无效boolean split() default true; }
Dao层接口添加此注解,并在参数中传递shardName或ShardEntity实现类的对象
/*** 需要分区的dao 需要加上 @TableShard 注解*/ @TableShard public interface UserDao{@Insert({"insert into user(userId, userName, msg) values(#{userId}, #{userName}, #{msg})"})@Options(keyProperty = "userId",keyColumn = "userId")void save(User user);/*** 需要分区的方法参数中, 必须存在 @Param("shardName") 的参数, 或者 存在实体类参数 实现了 ShardEntity 接口 如下面的 User* @param user* @param shardName* @return*/@Select("<script> select *, #{shardName} as shardName from user where userId=#{user.userId} <if test=\"user.userName != null\"> and userName = #{user.userName}</if> </script>")@Results({@Result(property = "userId",column = "userId"),@Result(property = "userName",column = "userName"),@Result(property = "msg",column = "msg"),@Result(property = "telephones", javaType = List.class, column = "{userId=userId, shardName=shardName}", many = @Many(select = "com.limz.mysql.dsmysql.Dao.TelephoneDao.findTelephoneByUserId"))})List<User> query(@Param("user") User user, @Param("shardName") String shardName); }
此处副表也同样分表
@Data public class Telephone implements Serializable, ShardEntity{private Long id;private String userId;private String telephone;public String getShardName(){return userId != null ? userId.substring(0,2) : null;} }
Telephone
@TableShard public interface TelephoneDao{@Insert("insert into telephone (userId, telephone) values(#{userId},#{telephone})")void save(Telephone t);@Select("select * from telephone where userId = #{userId}")List<Telephone> findTelephoneByUserId(@Param("shardName") String shardName, String userId);@Select("select * from telephone where id = #{id}")Telephone get(Telephone t); }
TelephoneDao
核心功能,声明一个拦截器,注册到Mybatis中, 拦截sql语句,
/*** 分表查询 拦截器 核心功能*/ @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class TableSegInterceptor implements Interceptor {private Logger logger = LoggerFactory.getLogger(this.getClass());//SQL解析工厂private final SqlParserFactory parserFactory = new JSqlParserFactory();//sql语句存储字段private final Field boundSqlField;public TableSegInterceptor() {try {boundSqlField = BoundSql.class.getDeclaredField("sql");boundSqlField.setAccessible(true);} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic Object intercept(Invocation invocation) throws Throwable {if (invocation.getTarget() instanceof Executor) {return invocation.proceed();}System.out.println("进入拦截器:====================");StatementHandler statementHandler = (StatementHandler) invocation.getTarget();MetaObject mo = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());MappedStatement mappedStatement = (MappedStatement) mo.getValue("delegate.mappedStatement");//解析出MappedStatement的ID 从中获取Dao类信息String id = mappedStatement.getId();String clzName = id.substring(0,id.lastIndexOf("."));Class<?> clzObj = Class.forName(clzName);//是否添加 @TableShard注解TableShard ts = clzObj.getAnnotation(TableShard.class);if (ts != null && ts.split()){// 进行SQL解析,如果未找到表名,则跳过BoundSql boundSql = statementHandler.getBoundSql();SqlParser sqlParser = parserFactory.createParser(boundSql.getSql());List<Table> tables = sqlParser.getTables();if (tables.isEmpty()) {return invocation.proceed();}//获取分表后缀名String shardName = null;Object v2 = mo.getValue("delegate.boundSql.parameterObject");if (v2 instanceof Map){Map pm = (Map) v2;//一定先从参数中查询,是否有 @Param("shardName") 的参数, 如果有,当做分表后缀,// 如果没有, 将遍历参数, 找到实现了ShardEntity接口的参数shardName = (String) pm.get("shardName");if (shardName == null){Collection values = pm.values();for (Object o : values) {if (o instanceof ShardEntity){ShardEntity se = (ShardEntity) o;shardName = se.getShardName();break;}}}//如果只有一个参数,为实体类,则直接从中获取属性}else {if (v2 instanceof ShardEntity) {ShardEntity se = (ShardEntity) v2;shardName = se.getShardName();}}//如果参数中 未包含 shardName 相关参数, 则抛出异常if (shardName == null)throw new ShardException("shardName must be not empty!");// 设置实际的表名for (int index = 0; index < tables.size(); index++) {Table table = tables.get(index);//替换所有表名,为表名添加后缀String targetName = table.getName() + "_" + shardName;logger.info("Sharding table, {}-->{}", table, targetName);table.setName(targetName);}// 修改实际的SQLString targetSQL = sqlParser.toSQL();boundSqlField.set(boundSql, targetSQL);}return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
其中解析sql用的工具位jsqlparser 具体代码见我的github
然后将拦截器注册到mybatis中
@Beanpublic Interceptor getInterceptor(){Interceptor interceptor = new TableSegInterceptor();return interceptor;}
OK 试一下
可以看到,根据userid 前两位, 自动将表名更改
扩展:
如果需要别的分表策略,只需要在实现ShardEntity时,将返回分表名后缀的方法换一种实现,比如根据创建时间,或者根据区域等
拦截器中返回结果处,可以扩展为, 如果不存在shardName 则获取所有叫 user_* 的表,查询所有表结果然后 union 拼接,只不过这样会使效率降低
转载于:https://www.cnblogs.com/li-mzx/p/9963312.html
mybatis 自动分表相关推荐
- mybatis mysql 分表_Mybatis的分表实战
前言: 以前写代码, 关于mysql的分库分表已被中间件服务所支持, 业务代码涉及的sql已规避了这块. 它对扩展友好, 你也不知道到底他分为多少库, 多少表, 一切都是透明的. 不过对于小的团队/工 ...
- mysql 自动分表_Mysql Event 自动分表
create table TempComments Like dycomments; 上述 SQL语句创建的新表带有原表的所有属性,主键,索引等. 自动分表怎么做呢? 使用上述语句自动创建分表. 那么 ...
- mysql mybatis分表查询_mybatis 自动分表
参考: 相关源码已上传至我的 github 欢迎转载,转载请注明出处,尊重作者劳动成果:https://www.cnblogs.com/li-mzx/p/9963312.html 前言 小弟才疏学浅, ...
- 如何用Mybatis分库分表
分库 在分库的时候 有时候为了方便 一些表需要存放所有库的信息,称为全局库.如:用户表存放所有的用户. 此时分库的思路 数据库分为全局库和业务库,其中业务库又分为N多个库,全局库只放个别表方便开发. ...
- 支持MySql的数据库自动分表工具DBShardTools发布
前段时间参与了公司的一个项目,这个项目的特点是数据量.访问量都比较大,考虑使用数据库水平分表策略,Google了大半天,竟然没有找到分表工具.于是自己写了个数据库水平分表工具,支持MS Sql Ser ...
- mybatis+mysql分库分表_一种简单易懂的 MyBatis 分库分表方案
数据库分库分表除了使用中间件来代理请求分发之外,另外一种常见的方法就是在客户端层面来分库分表 -- 通过适当地包装客户端代码使得分库分表的数据库访问操作代码编写起来也很方便.本文的分库分表方案基于 M ...
- SQLAlchemy 自动分表
将数据库中的表自动映射ORM类 在 SQLAlchemy 中提供了将数据库中的表自动映射为ORM类的扩展(sqlalchemy.ext.automap). 基本用法 最简单用例是将一个已存在的数据库映 ...
- python 查看excel 多少行_13行代码实现对Excel自动分表(python)
我们工作中经常要遇到这种情况:需要把一个总的excel工作薄,按'部门'字段分成N个工作薄,单独发给不同的部门. 通过网上搜索,有方方格子等插件能实现部分功能.但遇到工作薄下有多个工作表时仍然不好操作 ...
- Sharding-Proxy安装_以及_sharding-proxy自动分表配置_Sharding-Sphere,Sharding-JDBC分布式_分库分表工作笔记019
上一节我们说了,sharding-proxy的安装,现在我们继续来说,我们安装以后,并且把sharding-proxy配置好,实现分表操作,首先我们看一下他的配置文件. 我们打开这个server.ya ...
最新文章
- git的一些知识梳理以及命令操作
- 创业公司,怎么用人更划算?
- oracle 提示存在lob,案例:Oracle数据库临时文件特别大 commit后lob字段使用临时表空...
- 对一句正则表达式的理解
- Hbase JMX 监控 - Region
- 华为mate10pro测试软件,华为Mate10和华为Mate10Pro的区别在哪里?华为Mate10和华为Mate10Pro对比测评告诉你(附全文)...
- Git学习之路(6)- 分支操作
- Python中的jquery PyQuery库使用小结
- python遗传算法程序_python 如何实现遗传算法
- 华为交换机关闭网口_华为交换机如何关闭网络端口号
- cada0图纸框_CAD怎么画图纸框?cad图纸框的绘制方法
- 利用VS软件生成可执行的文件(.exe文件)
- 光纤交换机 序列号_FAQ-如何查询设备的SN号
- STM32F103单片机使用ULN2003驱动步进电机
- python flask服务器假死_IE浏览器访问Flask自带服务器假死问题解决方法 - digwtx
- mysql按键精灵接口,mysql,按键精灵,读取写入
- Allegro导出dxf
- python大气模型算法_[学习笔记][Python机器学习:预测分析核心算法][利用Python集成方法工具包构建梯度提升模型]...
- 表白神器-摩斯密码1121311233321113212313323332113
- C#Winform的DataGridView控件使用详解2—DataGridView表格样式设置及表格操作