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