9月份参加软件架构师大会,京东老师提到了他们解决数据库水平切分用的mybatis拦截器来实现,目前所做的项目用的是mybatis,而恰好也需要这个功能,研究了下基本实现了拦截器根据配置自动切分数据表来进行访问。新老代码的改造很简单,加几个配置即可。
一、具体使用配置
1.1、拦截器配置
在mybatis-config.xml里面配置拦截器插件:
- <plugins>
- <plugin interceptor="com.wagcy.plugin.mybatis.TableSegInterceptor"></plugin>
- </plugins>
如果是与spring集成,则配置如下
- <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="dataSource" ref="dataSource" />
- <property name="plugins">
- <array>
- <bean id="tableSegInterceptor" class="com.vrv.im.plugin.mybatis.TableSegInterceptor"/>
- </array>
- </property>
- </bean>
1.2、切分配置
这里主要使用注解的方式,在mapper上去配置,如:
- @TableSeg(tableName="Blog",shardType="%5",shardBy="id")
- public interface BlogMapper {
- Blog selectBlog(int id);
- Blog selectBlogByObj(Blog blog);
- Blog selectBlogByMap(Map map);
- }
tableName分表表名 ;shardType切分类型,如%5:取模,表示取5余数;shardType:切分字段。这里只是做了项目中用到最多的切分方式-取模,可以根据需要扩展。
通过这两项配置,就可以实现数据库表自动切分。原来的开发模式不变,只需要传入切分字段即可,开发人员不需要关心怎么去切分,怎么写切分后的SQL.
二、实现代码
2.1、拦截器实现
拦截器主要的作用是读取配置,根据配置的切分策略和字段,来切分表,然后替换原执行的SQL,从而实现自动切分,上代码:
- @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
- public class TableSegInterceptor implements Interceptor {
- private static final String tag = TableSegInterceptor.class.getName();
- private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
- private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
-
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- StatementHandler statementHandler = (StatementHandler) invocation
- .getTarget();
- MetaObject metaStatementHandler = MetaObject.forObject(
- statementHandler, DEFAULT_OBJECT_FACTORY,
- DEFAULT_OBJECT_WRAPPER_FACTORY);
- String originalSql = (String) metaStatementHandler
- .getValue("delegate.boundSql.sql");
- BoundSql boundSql = (BoundSql) metaStatementHandler
- .getValue("delegate.boundSql");
-
-
- Object parameterObject = metaStatementHandler
- .getValue("delegate.boundSql.parameterObject");
- if (originalSql!=null&&!originalSql.equals("")) {
- MappedStatement mappedStatement = (MappedStatement) metaStatementHandler
- .getValue("delegate.mappedStatement");
- String id = mappedStatement.getId();
- String className = id.substring(0, id.lastIndexOf("."));
- Class<?> classObj = Class.forName(className);
-
- TableSeg tableSeg = classObj.getAnnotation(TableSeg.class);
- if(tableSeg!=null){
- AnalyzeActualSql as = new AnalyzeActualSqlImpl(mappedStatement, parameterObject, boundSql);
- String newSql=as.getActualSql(originalSql, tableSeg);
- if(newSql!=null){
- LogUtil.d(tag,"分表后SQL =====>"+ newSql);
- metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
- }
- }
- }
-
- return invocation.proceed();
- }
-
- @Override
- public Object plugin(Object target) {
-
-
- if (target instanceof StatementHandler) {
- return Plugin.wrap(target, this);
- } else {
- return target;
- }
- }
-
- @Override
- public void setProperties(Properties properties) {
-
-
- }
2.2、切分策略解析
读取切分配置,可以根据自己的需要,扩展实现不同的切分策略。主要逻辑就是读取切分字段值,然后根据切分策略,得出切分后表的扩展名。
2.3、切分字段值获取
在实现解析字段值得时候,使用了mybatis原有的解析参数方式进行解析,避免了二次开发。
-
-
-
-
-
-
-
- public Object getFieldValue(String propertyName,boolean isMutiPara) {
- MetaObject metaObject = parameterObject == null ? null : configuration
- .newMetaObject(parameterObject);
- Object value;
- if (boundSql.hasAdditionalParameter(propertyName)) {
- value = boundSql.getAdditionalParameter(propertyName);
- } else if (parameterObject == null) {
- value = null;
- } else if (typeHandlerRegistry.hasTypeHandler(parameterObject
- .getClass())) {
- if(isMutiPara)
- return null;
- value = parameterObject;
- } else {
- value = metaObject == null ? null : metaObject
- .getValue(propertyName);
- }
- return value;
- }
2.4、切分配置
这里主要使用注解的方式,在mapper上去配置,注解代码:
- @Target({ ElementType.TYPE })
- @Retention(RetentionPolicy.RUNTIME)
- @Inherited
- @Documented
- public @interface TableSeg {
-
-
-
-
- public String tableName();
-
-
-
-
-
- public String shardType();
-
-
-
-
-
- public String shardBy();
-
- }
总体来说基本满足了项目的需求,实现了简单的取模分表,后续如果有其他的切分需求,可以根据需求扩展,基本思路大致一致。