mybatis拦截器实现数据库表水平切分

 9月份参加软件架构师大会,京东老师提到了他们解决数据库水平切分用的mybatis拦截器来实现,目前所做的项目用的是mybatis,而恰好也需要这个功能,研究了下基本实现了拦截器根据配置自动切分数据表来进行访问。新老代码的改造很简单,加几个配置即可。

      一、具体使用配置

          1.1、拦截器配置

            在mybatis-config.xml里面配置拦截器插件:
[html]  view plain  copy
  1.        <plugins>  
  2.     <plugin interceptor="com.wagcy.plugin.mybatis.TableSegInterceptor">plugin>  
  3. plugins>  
            如果是与spring集成,则配置如下
[html]  view plain  copy
  1. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  2.     <property name="dataSource" ref="dataSource" />  
  3.     <property name="plugins">  
  4.      <array>  
  5.          <bean id="tableSegInterceptor" class="com.vrv.im.plugin.mybatis.TableSegInterceptor"/>  
  6.      array>  
  7.     property>          
  8. bean>  

         1.2、切分配置

         这里主要使用注解的方式,在mapper上去配置,如:
[java]  view plain  copy
  1. @TableSeg(tableName="Blog",shardType="%5",shardBy="id")  
  2. public interface BlogMapper {  
  3.     Blog selectBlog(int id);  
  4.     Blog selectBlogByObj(Blog blog);  
  5.     Blog selectBlogByMap(Map map);  
  6. }  
       tableName分表表名  ;shardType切分类型,如%5:取模,表示取5余数;shardType:切分字段。这里只是做了项目中用到最多的切分方式-取模,可以根据需要扩展。

       通过这两项配置,就可以实现数据库表自动切分。原来的开发模式不变,只需要传入切分字段即可,开发人员不需要关心怎么去切分,怎么写切分后的SQL.
          

     二、实现代码

       2.1、拦截器实现

           拦截器主要的作用是读取配置,根据配置的切分策略和字段,来切分表,然后替换原执行的SQL,从而实现自动切分,上代码:
[java]  view plain  copy
  1. @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })  
  2. public class TableSegInterceptor implements Interceptor {  
  3.     private static final String tag = TableSegInterceptor.class.getName();  
  4.     private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();  
  5.     private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();  
  6.   
  7.     @Override  
  8.     public Object intercept(Invocation invocation) throws Throwable {  
  9.         StatementHandler statementHandler = (StatementHandler) invocation  
  10.                 .getTarget();  
  11.         MetaObject metaStatementHandler = MetaObject.forObject(  
  12.                 statementHandler, DEFAULT_OBJECT_FACTORY,  
  13.                 DEFAULT_OBJECT_WRAPPER_FACTORY);  
  14.         String originalSql = (String) metaStatementHandler  
  15.                 .getValue("delegate.boundSql.sql");  
  16.         BoundSql boundSql = (BoundSql) metaStatementHandler  
  17.                 .getValue("delegate.boundSql");  
  18.         //Configuration configuration = (Configuration) metaStatementHandler  
  19.                 //.getValue("delegate.configuration");  
  20.         Object parameterObject = metaStatementHandler  
  21.                 .getValue("delegate.boundSql.parameterObject");  
  22.         if (originalSql!=null&&!originalSql.equals("")) {  
  23.             MappedStatement mappedStatement = (MappedStatement) metaStatementHandler  
  24.                     .getValue("delegate.mappedStatement");  
  25.             String id = mappedStatement.getId();  
  26.             String className = id.substring(0, id.lastIndexOf("."));  
  27.             Class  classObj = Class.forName(className);  
  28.             //根据配置自动生成分表SQL  
  29.             TableSeg tableSeg = classObj.getAnnotation(TableSeg.class);  
  30.             if(tableSeg!=null){  
  31.                 AnalyzeActualSql  as = new AnalyzeActualSqlImpl(mappedStatement, parameterObject, boundSql);  
  32.                 String newSql=as.getActualSql(originalSql, tableSeg);  
  33.                 if(newSql!=null){  
  34.                     LogUtil.d(tag,"分表后SQL =====>"+ newSql);  
  35.                     metaStatementHandler.setValue("delegate.boundSql.sql", newSql);  
  36.                 }  
  37.             }  
  38.         }  
  39.         // 传递给下一个拦截器处理  
  40.         return invocation.proceed();  
  41.     }  
  42.   
  43.     @Override  
  44.     public Object plugin(Object target) {  
  45.         // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的  
  46.         // 次数  
  47.         if (target instanceof StatementHandler) {  
  48.             return Plugin.wrap(target, this);  
  49.         } else {  
  50.             return target;  
  51.         }  
  52.     }  
  53.   
  54.     @Override  
  55.     public void setProperties(Properties properties) {  
  56.         // TODO Auto-generated method stub  
  57.   
  58.     }  
         

      2.2、切分策略解析

        读取切分配置,可以根据自己的需要,扩展实现不同的切分策略。主要逻辑就是读取切分字段值,然后根据切分策略,得出切分后表的扩展名。

      2.3、切分字段值获取

       在实现解析字段值得时候,使用了mybatis原有的解析参数方式进行解析,避免了二次开发。
[java]  view plain  copy
  1. /** 
  2.      * 获取字段值 
  3.      *  
  4.      * @param propertyName 
  5.      * @param isMutiPara 
  6.      * @return 
  7.      */  
  8.     public Object getFieldValue(String propertyName,boolean isMutiPara) {  
  9.         MetaObject metaObject = parameterObject == null ? null : configuration  
  10.                 .newMetaObject(parameterObject);  
  11.         Object value;  
  12.         if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params  
  13.             value = boundSql.getAdditionalParameter(propertyName);  
  14.         } else if (parameterObject == null) {  
  15.             value = null;  
  16.         } else if (typeHandlerRegistry.hasTypeHandler(parameterObject  
  17.                 .getClass())) {  
  18.             if(isMutiPara)//多个参数,这情况就不应该匹配了  
  19.                 return null;  
  20.             value = parameterObject;  
  21.         } else {  
  22.             value = metaObject == null ? null : metaObject  
  23.                     .getValue(propertyName);  
  24.         }  
  25.         return value;  
  26.     }  

  2.4、切分配置

               这里主要使用注解的方式,在mapper上去配置,注解代码:
               
[java]  view plain  copy
  1. @Target({ ElementType.TYPE })  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. @Inherited  
  4. @Documented  
  5. public @interface TableSeg {  
  6.     /** 
  7.      * 表名 
  8.      * @return 
  9.      */  
  10.     public String tableName();  
  11.     /** 
  12.      * 分表方式,取模,如%5:表示取5余数, 
  13.      * 如果不设置,直接根据shardBy值分表 
  14.      * @return 
  15.      */  
  16.     public String shardType();  
  17.     /** 
  18.      * 根据什么字段分表 
  19.      * 多个字段用数学表达表示,如a+b   a-b 
  20.      * @return 
  21.      */  
  22.     public String shardBy();  
  23.   
  24. }  

      总体来说基本满足了项目的需求,实现了简单的取模分表,后续如果有其他的切分需求,可以根据需求扩展,基本思路大致一致。

你可能感兴趣的:(mybatis拦截器实现数据库表水平切分)