一个完整的分表插件流程

分表查询的思路很简单,就是在sql的运行过程中的某一阶段,拦截下sql,将它“自动”路由到分表中的任意一个

一、Mybatis Interceptor接口使用

  按照思路所说,自然要想办法把运行到某一阶段的sql拦截下来并做更改,那么就需要Interceptor。

  Interceptor可以拦截的方法,官网描述如下:

  MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

    •   Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    •   ParameterHandler (getParameterObject, setParameters)
    •   ResultSetHandler (handleResultSets, handleOutputParameters)
    •   StatementHandler (prepare, parameterize, batch, update, query)

  这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。

  sql语句是被封装在BoundSql里的,而BoundSql由StatementHandler获取,所以我们拦截StatementHandler的prepare方法(StatementHandler和BoundSql部分源码如下)。

public interface StatementHandler {

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}

public class BoundSql {

  private String sql;

  public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
    this.sql = sql;
    ......
  }

  public String getSql() {
    return sql;
  }

}

  在写插件的时候,我们只需要在插件类上添加注解:@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class })}) 

  注解中属性的意义应该一看便知吧,只要准备写一个Mybatis插件类,就必须添加@Intercepts注解。

  Interceptor接口中方法介绍

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}

  ① intercept方法:执行拦截内容。下面的plugin方法触发该方法。

  ② plugin方法:用于给target创建一个jdk的动态代理对象,用于触发intercept方法。这个方法的实现中一般只写一句话Plugin.wrap(target,this),可以看一下这个wrap方法:

public class Plugin implements InvocationHandler {

  private Object target;
  private Interceptor interceptor;
  private Map<Class<?>, Set<Method>> signatureMap;

  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {     // 当生产的动态代理类运行到super.h.invoke时,调用了intercept方法。
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

}

  看到了实现了InvocationHandler接口,有invoke方法,就知道这和动态代理(关于动态代理可以看我的另一篇:https://www.cnblogs.com/NoYone/p/8733868.html)有关,然后我们看wrap方法,实际上就是返回了target的动态代理之后的对象。

  ③ setProperties()方法:给自定义的拦截器传递xml配置的属性参数。

二、intercept实现方法

  在intercept方法中最重要的是拿到sql语句,而sql语句是被封装到顶层被代理类里的,所以需要从StatementHandler往“上”遍历获得顶层被代理类。

        // 取出被拦截的对象
        StatementHandler stmtHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStmtHandler = SystemMetaObject.forObject(stmtHandler);
        // 分离代理对象,从而形成多次代理,通过两次循环最原始的被代理类,Mybatis使用的是JDK代理
        while (metaStmtHandler.hasGetter("h")) {
            Object object = metaStmtHandler.getValue("h");
            metaStmtHandler = SystemMetaObject.forObject(object);
        }

        // 分离最后一个代理目标类
        while (metaStmtHandler.hasGetter("target")) {
            Object object = metaStmtHandler.getValue("target");
            metaStmtHandler = SystemMetaObject.forObject(object);
        }

        String sql = (String) metaStmtHandler.getValue("delegate.boundSql.sql");
        Object param = metaStmtHandler.getValue("delegate.boundSql.parameterObject");       

  至此,我们已经拿到了sql语句和sql的参数,接下来我们要替换sql中的分表标志,使sql语句路由到对应的表去。

三、分表路由规则的制定

  比方说我们有20张表,什么时候去请求哪一张表,这肯定是需要一定的规则的,根据业务需求自行设计即可。一般情况下就用取模就可以了。

  我们这里设置三个字段:

  1. symbol 分表标识符,即判断此条sql是否需要分表,若带这个这个标识符,则进入分表逻辑
  2. filedName 分表列,即根据哪一个字段去做分表
  3. splitConut 分表的总个数,有这个数呢,就方便取模,路由找表

  这种字段可以设置在mybatis的配置文件中,由上面介绍过的setProperties方法set进来。示例如下

    <plugins>
        <plugin interceptor="com.jd.fspinvoice.plugin.SplitTablePluginXXX">
            <!--取膜20标号范围0,19 -->
            <property value="20" name="splitCount"/>
            <property value="rid" name="filedName"/>
            <property value="@2" name="symbol"/>
        </plugin>

    </plugins>

            public void setProperties(Properties properties) {
        try {
            splitCount = Integer.valueOf((String) properties.get("splitCount"));
            filedName = (String) properties.get("filedName");
            symbol = (String) properties.get("symbol");
        } catch (Exception e) {
            logger.error("未设置分表数量", e);
            throw new RuntimeException("未设置分表数量");
        }
        this.props = properties;
    }

  也可以在传入sql语句的参数中,比方说Mapper接口设置接口的是一个Map,那么map里就要set上这个这个filedName即可。

四、完整的Intercept方法

@Override
    public Object intercept(Invocation invocation) throws Throwable {

            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            MetaObject metaStmtHandler = SystemMetaObject.forObject(statementHandler);
            while (metaStmtHandler.hasGetter("h")) {
                Object object = metaStmtHandler.getValue("h");
                metaStmtHandler = SystemMetaObject.forObject(object);
                while (metaStmtHandler.hasGetter("target")) {
                    object = metaStmtHandler.getValue("target");
                    metaStmtHandler = SystemMetaObject.forObject(object);
                }
            }

            String sql = (String) metaStmtHandler.getValue("delegate.boundSql.sql");
            Object param = metaStmtHandler.getValue("delegate.boundSql.parameterObject");
             sql = sql.trim();

            String lowSql = sql.toLowerCase();
            if (lowSql.startsWith("insert") || lowSql.startsWith("update") || lowSql.startsWith("delete")|| lowSql.startsWith("select")) {
                if (lowSql.indexOf(symbol) != -1) {
                    Long filedValue = getBusinessValue(param, filedName,Long.class);
                    if(filedValue == null){
                        throw new RuntimeException("需要路由字段:"+filedName );
                    }
                    long hash = getHashLong(String.valueOf(filedValue));
                    logger.info("此SQL需要进行路由操作。 表坐标:" + hash % splitCount + ", 路由字段:" + filedName+",值:"+filedValue);
                    // 取模操作
                    sql = generateSql(sql, new Long(hash % splitCount).toString(),symbol);
                    metaStmtHandler.setValue("delegate.boundSql.sql", sql);
                } else {
                    // 无@标识不需要分表无需处理
                }
            } else {
                // 不走路由
            }
            return invocation.proceed();
    }

原文地址:https://www.cnblogs.com/NoYone/p/9479455.html

时间: 2024-08-01 08:24:22

一个完整的分表插件流程的相关文章

分享一个MySQL分库分表备份脚本(原)

分享一个MySQL分库备份脚本(原) 开发思路: 1.路径:规定备份到什么位置,把路径(先判断是否存在,不存在创建一个目录)先定义好,我的路径:/mysql/backup,每个备份用压缩提升效率,带上时间方便整理 2.取数据库:抓取数据库名称,我用的awk和grep配合取数据库的名称(如果想按照表备份可以再细化一下)注意要用mysql -e选项 这样才能做成脚本 3.系统环境变量:因为用到了函数,所以非系统内置的命令 最好在脚本里面用 . /etc/profile  把系统当前的环境变量传过来

分享一个 MySQL 分库分表类(php)

当一个表数据记录过大时就会出现性能瓶颈,而一般对应的解决办法是要么做分区表,要么分表,分区表就不说了,分表又分为垂直分割和水平分割,具体区别请自行搜索.一般而言,分库分表属于水平分割,按照一定的规则将数据插入到不同的表中去.而分库则可以很方便的转移数据库的压力,比如将一个很大库的分别放在不同的服务器上. 下面是我写的一个分库分表的实现: <?php /** * User: guoyu * Date: 14-8-12 * Time: 下午3:16 */ namespace App\Model\Da

Mysql分表的一个考虑

今天看到一篇博客,讲述的是Mysql的分表方案,内容比较简单,不过有个思路倒是挺好的,记录下,后续分表可以参考 作者主要是说到两种分表,一个是取模,另一个是范围分表 取模:比如用户ID%10,分10张表 范围分表:比如约定,用户ID0~1000w的数据存在表1,1000w~2000w的用户表2,以此类推 两者各有优缺点,主要体现在扩展性,冷热数据均匀分布的问题. 取模的话,冷热数据比较均衡,但是扩展性比较差,加入后期数据量翻一倍,10个表存不下了,再加10个表咋办? 范围分表的话不存在扩展性问题

mysql大数据解决方案--分表分库(0)

引言 对于一个大型的互联网应用,海量数据的存储和访问成为了系统设计的瓶颈问题,对于系统的稳定性和扩展性造成了极大的问题.通过数据切分来提高网站性能,横向扩展数据层已经成为架构研发人员首选的方式. •水平切分数据库:可以降低单台机器的负载,同时最大限度的降低了宕机造成的损失: •负载均衡策略:可以降低单台机器的访问负载,降低宕机的可能性: •集群方案:解决了数据库宕机带来的单点数据库不能访问的问题: •读写分离策略:最大限度了提高了应用中读取数据的速度和并发量: 问题描述 1.单个表数据量越大,读

数据库水平切分的实现原理解析——分库,分表,主从,集群,负载均衡器(转)

第1章 引言 随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计的瓶颈问题.对于 一个大型的互联网应用,每天几十亿的PV无疑对数据库造成了相当高的负载.对于系统的稳定性和扩展性造成了极大的问题.通过数据切分来提高网站性能,横向 扩展数据层已经成为架构研发人员首选的方式. 水平切分数据库:可以降低单台机器的负载,同时最大限度的降低了宕机造成的损失: 负载均衡策略:可以降低单台机器的访问负载,降低宕机的可能性: 集群方案:解决了数据库宕机带来的单点数据库不能访问的问题: 读写分离策略:最大

数据库分库分表系统学习

一  为什么要进行数据切分 为什么需要数据切分呢?比如像Oracle这样成熟稳定的数据库,足以支撑海量数据的存储与查询了?为什么还需要数据切片呢?的确,Oracle的DB确实很成熟很稳定,但是高昂的使用费用和高端的硬件支撑不是每一个公司能支付的起的.试想一下一年几千万的使用费用和动辄上千万元的小型机作为硬件支撑,这是一般公司能支付的起的吗?即使就是能支付的起,假如有更好的方案,有更廉价且水平扩展性能更好的方案,我们肯定会进行选择的. 平常我们会自觉的按照范式来设计我们的数据库,负载高点可能考虑使

数据库水平切分的实现原理解析---分库,分表,主从,集群,负载均衡器

原文来自:http://zhengdl126.iteye.com/blog/419850 第1章  引言 随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计的瓶颈问题.对于一个大型的 互联网应用,每天几十亿的PV无疑对数据库造成了相当高的负载.对于系统的稳定性和扩展性造成了极大的问题.通过数据切分来提高网站性能,横向扩展数据层 已经成为架构研发人员首选的方式.水平切分数据库,可以降低单台机器的负载,同时最大限度的降低了了宕机造成的损失.通过负载均衡策略,有效的降低了单台 机器的访问负载

数据库水平切分的原理探讨、设计思路--数据库分库,分表,集群,负载均衡器

本文转载:http://www.cnblogs.com/olartan/archive/2009/12/02/1615131.html 第1章  引言 数据量巨大时,首先把多表分算到不同的DB中,然后把数据根据关键列,分布到不同的数据库中.库分布以后,系统的查询,io等操作都可以有多个机器组成的群组共同完成了.本文主要就是针对,海量数据库,进行分库.分表.负载均衡原理,进行探讨,并提出解决方案. 随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计的瓶颈问题.对于一个大型的互联网应用,每

数据库水平切分的实现原理解析&mdash;&mdash;分库,分表,主从,集群,负载均衡器(转)

申明:此文为转载(非原创),文章分析十分透彻,已添加原文链接,如有任何侵权问题,请告知,我会立即删除. 第1章 引言 随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计的瓶颈问题.对于一个大型的互联网应用,每天几十亿的PV无疑对数据库造成了相当高的负载.对于系统的稳定性和扩展性造成了极大的问题.通过数据切分来提高网站性能,横向扩展数据层已经成为架构研发人员首选的方式. 水平切分数据库:可以降低单台机器的负载,同时最大限度的降低了宕机造成的损失: 负载均衡策略:可以降低单台机器的访问负载