首页 > 代码库 > (一)mybatis的散表散库支持

(一)mybatis的散表散库支持

当业务数据量逐渐增大的时候,我们避免不了要做数据的拆分,做散表散库处理,这就必然要对现有代码做一定的修改,如果我们用的是现成的框架,比如mybatis,要对他的修改也会很麻烦。


搜索了一下网上的实现,大部分都是讲google code上的一个插件(org.shardbatis)实现,他只实现了散表的支持,感兴趣的同学可以去百度一下。

他的主要原理是mybatis提供了plugin的支持,plugin允许你对StatementHandler等执行层面的代码进行AOP的处理,但仅仅支持这几个执行层面的类,也就是说他支持sql执行层面的扩展,这时候的sql已经被mybatis解析处理过了,是实际要执行的sql,如果大家想对执行性能做一些特殊处理,到是可以写自己的plugin。

回到我们要实现的散表散库的功能,plugin中实现,就意味着我们得自己解析一遍sql,来得到tableName,上面讲的shardbatis就是这样处理的,他使用的是net.sf.jsqlparser解析sql,来解析出table name。这样势必要造成一定的性能损失。而且也不一定支持所有的sql。


当然,即使我们不考虑性能的损失,google code访问也比较困难,哈哈。


好吧,下面进入正题,实际上mybatis在进入connection实际执行sql之前,会要先对sql进行解析处理,比如最简单的要把${XX},解析成?,然后得到preparestatement,我们何不在他解析的时候做点东西呢,查看代码,我就会知道,他的解析入口在org.apache.ibatis.builder.SqlSourceBuilder类中。


我们来看一下这个类到底做了什么:

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }


看这个方法我们就知道了,他实际上去解析mapper.xml中定义的sql语句的文本解析,得到最终的sql。


那么,我们可不可以在mapper.xml中放入自己的散表标示,比如@@table,然后在这个类里去转换呢。实际上是可以的。


public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        if(originalSql.indexOf("@@table_end") != -1){
            String end = TableShardUtils.getTableEnd();
            if(StringUtils.isBlank(end)){
                end = "";
            }
            originalSql = originalSql.replace("@@table_end",end);
            if(!StringUtils.isBlank(end)){
                TableShardUtils.setTableEnd("");
            }
        }

        if(originalSql.toLowerCase().startsWith("select")){
            if(!DataBaseShardUtils.isForceMaster()) {
                DataBaseShardUtils.setSlave(true);
            }
        }

        SqlSourceBuilder.ParameterMappingTokenHandler handler = new SqlSourceBuilder.ParameterMappingTokenHandler(this.configuration, parameterType, additionalParameters);
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql = parser.parse(originalSql);
        return new StaticSqlSource(this.configuration, sql, handler.getParameterMappings());
    }

大家看一下我的实现,这里我们将这个@@table_end特殊字符转换为我们要添加的表后缀,来看一下我们的mapper.xml文件:

  <select id="countByExample" parameterType="org.weixin.test.model.AdminCriteria" resultType="java.lang.Integer">
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
    -->
    select count(*) from weixin_admin@@table_end
    <if test="_parameter != null">
      <include refid="Example_Where_Clause" />
    </if>
  </select>


mapper.xml文件中我们如果想对某个表或者某个查询做散表处理,我就加上@@table_end,那什么时候来决定到底用哪个表呢?我们用ThreadLocal类来解决,由代码来决定:

public class TableShardUtils {
    private static ThreadLocal<String> tableEnd = new ThreadLocal<String>();

    public static void setTableEnd(String end){
        tableEnd.set(end);
    }

    public static String getTableEnd(){
        return tableEnd.get();
    }

    public static void setTableEnd(int id,int count){
        int end = id %count;
        String endStr = "_" + end;
        TableShardUtils.setTableEnd(endStr);
    }
}

我们再来看一下,实际的一个执行:

            TableShardUtils.setTableEnd(jiaoHuanInfo.getId(),4);
            jiaoHuanInfo.setId(oldJiaoHuanInfo.getId());
            jiaoHuanInfo.setUpdateTime(new Date());
            jiaoHuanInfoMapper.updateByPrimaryKey(jiaoHuanInfo);


这里,我们在实际执行前,把jiaoHuanInfo这个类对应的表,根据id散为4份。


到此,这个方案就解说完毕了,当然,这个方案有个致命的问题,那就是我们得修改mybatis本身,因为他没有提供插件的机制,让我们可以做这个处理,我们只能复写他的SqlSourceBuilder类,一种情况,直接将编译后的类覆盖他的jar,一种情况将我们的类放到class目录下,这样会比lib目录下先加载。

当然优点也是有的,效率比较高,而且足够的简单。就看大家自己的选择了。


下一遍,我继续再说一下,怎么实现对散库的支持。







(一)mybatis的散表散库支持