当业务数据量逐渐增大的时候,我们避免不了要做数据的拆分,做散表散库处理,这就必然要对现有代码做一定的修改,如果我们用的是现成的框架,比如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中放入自己的散表标示,比如@@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());
} <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>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);
到此,这个方案就解说完毕了,当然,这个方案有个致命的问题,那就是我们得修改mybatis本身,因为他没有提供插件的机制,让我们可以做这个处理,我们只能复写他的SqlSourceBuilder类,一种情况,直接将编译后的类覆盖他的jar,一种情况将我们的类放到class目录下,这样会比lib目录下先加载。
当然优点也是有的,效率比较高,而且足够的简单。就看大家自己的选择了。
下一遍,我继续再说一下,怎么实现对散库的支持。
原文地址:http://blog.csdn.net/shunlongjin/article/details/41825789