首页 > 代码库 > (一)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中放入自己的散表标示,比如@@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);
到此,这个方案就解说完毕了,当然,这个方案有个致命的问题,那就是我们得修改mybatis本身,因为他没有提供插件的机制,让我们可以做这个处理,我们只能复写他的SqlSourceBuilder类,一种情况,直接将编译后的类覆盖他的jar,一种情况将我们的类放到class目录下,这样会比lib目录下先加载。
当然优点也是有的,效率比较高,而且足够的简单。就看大家自己的选择了。
下一遍,我继续再说一下,怎么实现对散库的支持。
(一)mybatis的散表散库支持