参考:

相关源码已上传至我的 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依赖:

UTF-8

UTF-8

1.8

1.1.3

2.7.0

com.alibaba

fastjson

1.2.45

org.springframework.boot

spring-boot-starter-web

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.3.2

org.springframework.boot

spring-boot-devtools

true

mysql

mysql-connector-java

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

com.alibaba

druid-spring-boot-starter

${druid.version}

io.springfox

springfox-swagger2

${swagger.version}

io.springfox

springfox-swagger-ui

${swagger.version}

com.github.jsqlparser

jsqlparser

1.1

maven 依赖

application.yml

server:

port: 8021

spring:

datasource:

type: com.alibaba.druid.pool.DruidDataSource

driverClassName: com.mysql.jdbc.Driver

druid:

url: jdbc:mysql://localhost:3306/local?useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8

username: limz

password: 123456

initial-size: 10

max-active: 100

min-idle: 10

max-wait: 60000

pool-prepared-statements: true

max-pool-prepared-statement-per-connection-size: 20

time-between-eviction-runs-millis: 60000

min-evictable-idle-time-millis: 300000

validation-query: SELECT 1

test-while-idle: true

test-on-borrow: false

test-on-return: false

stat-view-servlet:

enabled: true

url-pattern: /druid/*

filter:

stat:

log-slow-sql: true

slow-sql-millis: 1000

merge-sql: true

wall:

config:

multi-statement-allow: true

proxy-filters:

list:

ref: logFilter

#开启debug模式,用于打印sql

logging:

level:

com.limz.mysql.dsmysql.Dao: debug

application.yml

声明一个接口,提供获取表名后缀的方法

/**

* 需要分表的实体类,必须实现的接口

*/

public interface ShardEntity {

/**

* 需要分表的类,需要实现此方法, 提供分表后缀名的获取

* @return

*/

String getShardName();

}

实体类实现此接口

@Datapublic class User implementsSerializable, ShardEntity {privateString userId;

@NotNull(message= "用户名不能为空")privateString userName;privateString msg;private Listtelephones;

//提供获取后缀名的方法 此处为userid 的前两位,代表所在的系统publicString getShardName(){return userId != null ? userId.substring(0,2) : null;

}

}

声明一个注解,加此注解的dao表示需要分表

/*** 需要分表的 Dao 添加此注解,标记为需要分表*/@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)public @interfaceTableShard {//默认分表, 为false时, 此注解无效

boolean split() default true;

}

Dao层接口添加此注解,并在参数中传递shardName或ShardEntity实现类的对象

/*** 需要分区的dao 需要加上 @TableShard 注解*/@TableShardpublic interfaceUserDao{

@Insert({"insert into user(userId, userName, msg) values(#{userId}, #{userName}, #{msg})"})

@Options(keyProperty= "userId",keyColumn = "userId")voidsave(User user);/*** 需要分区的方法参数中, 必须存在 @Param("shardName") 的参数, 或者 存在实体类参数 实现了 ShardEntity 接口 如下面的 User

*@paramuser

*@paramshardName

*@return

*/@Select("")

@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 query(@Param("user") User user, @Param("shardName") String shardName);

}

此处副表也同样分表

@Datapublic class Telephone implementsSerializable, ShardEntity{privateLong id;privateString userId;privateString telephone;publicString getShardName(){return userId != null ? userId.substring(0,2) : null;

}

}

Telephone

@TableShardpublic interfaceTelephoneDao{

@Insert("insert into telephone (userId, telephone) values(#{userId},#{telephone})")voidsave(Telephone t);

@Select("select * from telephone where userId = #{userId}")

List 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 implementsInterceptor {private Logger logger = LoggerFactory.getLogger(this.getClass());//SQL解析工厂

private final SqlParserFactory parserFactory = newJSqlParserFactory();//sql语句存储字段

private finalField boundSqlField;publicTableSegInterceptor() {try{

boundSqlField= BoundSql.class.getDeclaredField("sql");

boundSqlField.setAccessible(true);

}catch(Exception e) {throw newRuntimeException(e);

}

}

@Overridepublic Object intercept(Invocation invocation) throwsThrowable {if (invocation.getTarget() instanceofExecutor) {returninvocation.proceed();

}

System.out.println("进入拦截器:====================");

StatementHandler statementHandler=(StatementHandler) invocation.getTarget();

MetaObject mo= MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, newDefaultReflectorFactory());

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

}//获取分表后缀名

String shardName = null;

Object v2= mo.getValue("delegate.boundSql.parameterObject");if (v2 instanceofMap){

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 instanceofShardEntity){

ShardEntity se=(ShardEntity) o;

shardName=se.getShardName();break;

}

}

}//如果只有一个参数,为实体类,则直接从中获取属性

}else{if (v2 instanceofShardEntity) {

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);

}//修改实际的SQL

String targetSQL =sqlParser.toSQL();

boundSqlField.set(boundSql, targetSQL);

}returninvocation.proceed();

}

@OverridepublicObject plugin(Object target) {return Plugin.wrap(target, this);

}

@Overridepublic voidsetProperties(Properties properties) {

}

其中解析sql用的工具位jsqlparser  具体代码见我的github

然后将拦截器注册到mybatis中

@BeanpublicInterceptor getInterceptor(){

Interceptor interceptor= newTableSegInterceptor();returninterceptor;

}

OK 试一下

可以看到,根据userid 前两位, 自动将表名更改

扩展:

如果需要别的分表策略,只需要在实现ShardEntity时,将返回分表名后缀的方法换一种实现,比如根据创建时间,或者根据区域等

拦截器中返回结果处,可以扩展为, 如果不存在shardName 则获取所有叫 user_* 的表,查询所有表结果然后 union 拼接,只不过这样会使效率降低

mysql mybatis分表查询_mybatis 自动分表相关推荐

  1. mysql左连接去重查询_mysql之单表查询、多表查询

    mysql查询 单表查询 """ 增: insert [into] [数据库名.]表名[(字段1[,...,字段n])] values (数据1[,...,数据n])[, ...

  2. 【MySQL】多表查询策略(多表联查子查询)

    目录 一.MySQL多表查询 二.准备工作 1.运行环境 2.创建公司表 3.创建员工表 三.多表查询 (一)多表联查-同时查询多张表 1.联结 2.左连接 3.右连接 4.右连接 (二)子查询 1. ...

  3. MySQL.MyBatis怎么将查询的两个字段作为Map的key和value

    MySQL.MyBatis怎么将查询的两个字段作为Map的key和value 问题的由来 前端使用Echarts图标显示汇总数据.需要形式如下的数据: {"C20": 42.01, ...

  4. 面向考试数据库—单表查询(包含建表数据)

    面向考试数据库-单表查询(包含建表数据) 引言 ● 建立练习数据库(之后习题亦是基于该库) 建表源码 单表查询知识点汇总 单表查询练习题32道 (1)选取表中的若干列 (2)选择表中若干元祖 (3)o ...

  5. 预付费智能电表,做到一户一表、远程自动抄表、电费预充值、电表实时计量扣费、欠费自动跳闸。-安科瑞黄安南

    前言 国家从2018年开始对转供电加价开展规范清理以来,已经出来了一系列政策,不仅包括专门针对转供电问题的政策,18-20年间还在每次降电价政策中突出强调了转供电主体不得截留降价红利的要求.从具体内容 ...

  6. php 多表查询输出,ThinkPHP多表查询

    ThinkPHP多表查询处理 ThinkPHP多表连接查询处理 ThinkPHP关联查询(多表查询) 网上找到三种方法:table().join().原生SQL语句查询.(以下三种方法输出结果一致,并 ...

  7. SQL语句多表查询:【多表连查】和【子查询】

    SQL语句多表查询:[多表连查]和[子查询] 说明:insert.update.delete只针对[一张表]执行操作. 说明:select可以查询一张表.也可以查询多张表. 说明:多表查询分为:[多表 ...

  8. VLOOKUP函数制作多表查询(学生信息表/员工工资表)

    VLOOKUP函数制作多表查询(学生信息表/员工工资表) 一.vlookup函数定义 VLOOKUP函数是Excel中的一个纵向查找函数,在工作中都有广泛应用,例如可以用来核对数据,多个表格之间快速导 ...

  9. 将联表查询简化为单表查询案例

    目录 1.将联表查询简化为单表查询案例 1.1 问题描述 1.2 优化过程 1.2.1 将联表查询修改为单表查询 1.2.2 利用覆盖索引避免回表 1.将联表查询简化为单表查询案例 1.1 问题描述 ...

最新文章

  1. 二十四、oracle pl/sql 变量
  2. Nginx+tomcat集群的session共享问题
  3. JavaScript入门(part1)--初识JavaScript
  4. Java 8 Friday:可选将保留为Java中的一个选项
  5. 通过反射实现圆角ImageView
  6. java random array_java復習之Math、Random、Arrays工具類
  7. 解决chrome崩溃的方法
  8. Vs2015常见错误码:error LNK2019: 无法解析的外部符号;error C2011:类型重定义;
  9. vue3.0 案例小demo
  10. Python函数的返回值
  11. 热分析(一):什么是热仿真/热分析?
  12. qomo linux最新版本,Qomo Linux下一个版本将推驱动中心
  13. 永磁同步电机矢量控制(二)—— 控制原理与坐标变换推导
  14. 【反欺诈场景剖析】虚假账号的产生和流转
  15. 拳王虚拟项目公社:虚拟资源项目超详解,人人皆可熟练操作
  16. [java/初学者] 猜测随机数字的大小
  17. C盘扩大 解决办法
  18. one choise or a complain
  19. 光学透明胶片行业现状调研及趋势分析报告
  20. 洋葱架构简介——分离是为了更好的结合

热门文章

  1. 远程报:这可能是由于credssp加密oracle修正
  2. 结构体类型的变量的初始化
  3. 云医在线服务器不可用,云医在线app
  4. 华为云mysql端口号_远程连接华为云数据库(端口3306)
  5. Liunx磁盘管理——LVM
  6. C#不四舍五入保留两位小数
  7. 一定要学会的vsCode格式化整理代码的快捷键,再也不用手动调格式了
  8. 英语学习资料下载大全
  9. 基于javaweb(springboot)汽车配件管理系统设计和实现以及文档报告
  10. 【Ajax】form表单