mybatis攔截器實現數據庫表水平切分

          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");
		//Configuration configuration = (Configuration) metaStatementHandler
				//.getValue("delegate.configuration");
		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);
			//根據配置自動生成分表SQL
			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) {
		// 當目標類是StatementHandler類型時,才包裝目標類,否者直接返回目標本身,減少目標被代理的
		// 次數
		if (target instanceof StatementHandler) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub

	}
         

      2.2、切分策略解析

        讀取切分配置,可以根據自己的需要,擴展實現不同的切分策略。主要邏輯就是讀取切分字段值,然後根據切分策略,得出切分後表的擴展名。

      2.3、切分字段值獲取

       在實現解析字段值得時候,使用了mybatis原有的解析參數方式進行解析,避免了二次開發。
/**
	 * 獲取字段值
	 * 
	 * @param propertyName
	 * @param isMutiPara
	 * @return
	 */
	public Object getFieldValue(String propertyName,boolean isMutiPara) {
		MetaObject metaObject = parameterObject == null ? null : configuration
				.newMetaObject(parameterObject);
		Object value;
		if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
			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 {
	/**
	 * 表名
	 * @return
	 */
	public String tableName();
	/**
	 * 分表方式,取模,如%5:表示取5餘數,
	 * 如果不設置,直接根據shardBy值分表
	 * @return
	 */
	public String shardType();
	/**
	 * 根據什麼字段分表
	 * 多個字段用數學表達表示,如a+b   a-b
	 * @return
	 */
	public String shardBy();

}

      總體來說基本滿足了項目的需求,實現了簡單的取模分表,後續如果有其他的切分需求,可以根據需求擴展,基本思路大致一致。

發佈了29 篇原創文章 · 獲贊 3 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章