分库分表简单?那我想问如何实现“分库分表插件”?( 二 )


public%20class%20HashAlgorithm%20implements%20Algorithm%20{%20%20%20%20@Override%20%20%20%20public%20String%20doSharding(String%20tableName,%20Object%20value,int%20length)%20{%20%20%20%20%20%20%20%20if%20(this.isEmpty(value)){%20%20%20%20%20%20%20%20%20%20%20%20return%20tableName;%20%20%20%20%20%20%20%20}else{%20%20%20%20%20%20%20%20%20%20%20%20int%20h;%20%20%20%20%20%20%20%20%20%20%20%20int%20hash%20=%20(h%20=%20value.hashCode())%20^%20(h%20>>>%2016);%20%20%20%20%20%20%20%20%20%20%20%20int%20index;%20%20%20%20%20%20%20%20%20%20%20%20if%20(is2Power(length)){%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%20=%20(length%20-%201)%20&%20hash;%20%20%20%20%20%20%20%20%20%20%20%20}else%20{%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%20=%20Math.floorMod(hash,%20length);%20%20%20%20%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20%20%20%20%20return%20tableName+"_"+index;%20%20%20%20%20%20%20%20}%20%20%20%20}}四、拦截器配置和分片算法都有了,接下来就是重头戏了 。在这里,我们使用Mybatis拦截器将它们派上用场 。
常年CRUD的我们,都知道一条业务SQL肯定逃不出它们的范围 。其中,在业务上我们的删除功能一般都是逻辑删除,所以,基本上不会有DELETE操作 。
相较而言,新增和修改SQL都比较简单且格式固定,查询SQL往往比较灵活且复杂 。所以,在这里笔者定义了两个拦截器 。
不过,在介绍拦截器之前,我们有理由要了解另外两个东西:SQL语法解析器和分片算法处理器 。
1、JSqlParserJSqlParser负责解析SQL语句,并转化为JAVA类的层次结构 。我们可以先看个简单的例子来认识它 。
public%20static%20void%20main(String[]%20args)%20throws%20JSQLParserException%20{ String%20insertSql%20=%20"insert%20into%20user%20(id,name,age)%20value(1001,'范闲',20)"; Statement%20parse%20=%20CCJSqlParserUtil.parse(insertSql); Insert%20insert%20=%20(Insert)%20parse; String%20tableName%20=%20insert.getTable().getName(); List<Column>%20columns%20=%20insert.getColumns(); ItemsList%20itemsList%20=%20insert.getItemsList(); System.out.println("表名:"+tableName+"%20列名:"+columns+"%20属性:"+itemsList);}输出:%20表名:user%20列名:[id,%20name,%20age]%20属性:(1001,%20'范闲',%2020)我们可以看到,JSqlParser可以解析出SQL的语法信息 。相应的,我们也可以更改对象内容,从而达到修改SQL语句的目的 。
2、算法处理器我们的分片算法有多个,具体应该调用哪一个是在程序运行期来决定的 。所以,我们使用一个Map先将算法注册起来,然后根据分片模式来调用它 。这也是策略模式的体现 。
@Componentpublic%20class%20AlgorithmHandler%20{%20%20%20%20private%20Map<String,%20Algorithm>%20algorithm%20=%20new%20HashMap<>();%20%20%20%20@PostConstruct%20%20%20%20public%20void%20init(){%20%20%20%20%20%20%20%20algorithm.put("range",new%20RangeAlgorithm());%20%20%20%20%20%20%20%20algorithm.put("hash",new%20HashAlgorithm());%20%20%20%20}%20%20%20%20public%20String%20handler(String%20mode,String%20name,Object%20value,int%20length){%20%20%20%20%20%20%20%20return%20algorithm.get(mode).doSharding(name,%20value,length);%20%20%20%20}}3、拦截器我们知道,MyBatis允许你在已映射语句执行过程中的某一点进行拦截调用 。
如果你对它的原理还不熟悉,那么可以先看看笔者的文章:Mybatis拦截器的原理 。
整体来看,它的流程如下:

  • 通过Mybatis拦截待执行的SQL;
  • 通过JSqlParser解析SQL,获取逻辑表名等;
  • 调用分片算法获取真实表名;
  • 修改SQL,并修改BoundSql;
  • Mybatis执行修改后的SQL,达成目的 。
比如,对于insert语句和update语句,它的核心代码如下:
分库分表简单?那我想问如何实现“分库分表插件”?

文章插图
 
五、查询及分页事实上,新增和修改都比较简单,较为复杂的是查询语句 。
但是,我们的插件并不在于要满足所有的查询语句,而是可以根据真实的业务场景来扩展修改 。
【分库分表简单?那我想问如何实现“分库分表插件”?】不过分页功能基本上是逃不开的 。拿PageHelper为例,它的原理也是通过Mybatis拦截器来实现的 。如果它和我们的分表插件在一起,可能会产生冲突 。
所以在分表插件中,笔者也集成了分页功能,基本上和PageHelper一样,但并未直接使用它 。另外,对于查询来说,在查询条件中是否带有分片键,也是很关键的地方 。
1、查询在范围算法中,在业务上我们要求只查询特定某一个月或者近几个月的数据即可;在Hash算法中,我们则要求每次都带有主键 。


推荐阅读