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



1.        <plugins>  
2. <plugin interceptor="com.wagcy.plugin.mybatis.TableSegInterceptor"></plugin>  
3. </plugins>


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上去配置,如:





1. @TableSeg(tableName="Blog",shardType="%5",shardBy="id")  
2. public interface BlogMapper {  
3. int id);  
4.     Blog selectBlogByObj(Blog blog);  
5.     Blog selectBlogByMap(Map map);  
6. }




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




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. "delegate.boundSql.sql");  
16.         BoundSql boundSql = (BoundSql) metaStatementHandler  
17. "delegate.boundSql");  
18. //Configuration configuration = (Configuration) metaStatementHandler  
19. //.getValue("delegate.configuration");  
20.         Object parameterObject = metaStatementHandler  
21. "delegate.boundSql.parameterObject");  
22. if (originalSql!=null&&!originalSql.equals("")) {  
23.             MappedStatement mappedStatement = (MappedStatement) metaStatementHandler  
24. "delegate.mappedStatement");  
25.             String id = mappedStatement.getId();  
26. 0, id.lastIndexOf("."));  
27.             Class<?>  classObj = Class.forName(className);  
28. //根据配置自动生成分表SQL  
29. class);  
30. if(tableSeg!=null){  
31. new AnalyzeActualSqlImpl(mappedStatement, parameterObject, boundSql);  
32.                 String newSql=as.getActualSql(originalSql, tableSeg);  
33. if(newSql!=null){  
34. "分表后SQL =====>"+ newSql);  
35. "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.     }


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




       在实现解析字段值得时候,使用了mybatis原有的解析参数方式进行解析,避免了二次开发。




1. /**
2.      * 获取字段值
3.      * 
4.      * @param propertyName
5.      * @param isMutiPara
6.      * @return
7.      */  
8. public Object getFieldValue(String propertyName,boolean isMutiPara) {  
9. 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. null;  
16. else if (typeHandlerRegistry.hasTypeHandler(parameterObject  
17.                 .getClass())) {  
18. if(isMutiPara)//多个参数,这情况就不应该匹配了  
19. return null;  
20.             value = parameterObject;  
21. else {  
22. null ? null : metaObject  
23.                     .getValue(propertyName);  
24.         }  
25. return value;  
26.     }




               这里主要使用注解的方式,在mapper上去配置,注解代码:




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. }



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