1. 程式人生 > >Shardbatis開源框架原始碼的修改實踐經驗分享

Shardbatis開源框架原始碼的修改實踐經驗分享

Shardbatis開源框架原始碼按自身業務的改進

 

摘要

在研發過程中,我們遇到了單表資料量瓶頸問題,同時又不能增加資料庫的費用,最後選擇了分表技術來解決效能問題。在分表技術的呼叫過程中,我們有2種技術實現方案。第一種基於mybatis的plugin 外掛自研發,一種是採用開源的shardbatis框架。在對比研究分析過程中,發現shardbatis的框架設計理念擴充套件性良好,對於團隊開發有很好規範作用,同時採用配置設計理念,修改配置檔案就能滿足業務應用場景的需求,最後選擇了shardbatis框架,在實際應用過程中發現shardbatis採用sqlparse框架解析SQL,對編寫的MySQL的查詢語句進行了修正,查詢SQL分頁需要特殊處理,同時parselist不能到類級別攔截,最後我們下載原始碼進行了改進,替換了sqlparse解析框架,覆蓋了isParse方法,達到滿足自己的一套業務需求的框架。

 

Shardbatis的設計原理

 

1.      容器啟動載入mybatis-config.xml配置檔案

2.      解析mybatis配置檔案的同時載入mybatis攔截器介面的實現類,通過攔截器介面的setProperties 方法載入shard_config.xml引數,註冊分表策略

3.      Intercept方法攔截,輪詢所有的mapperid與分表策略中配置的mapperid比較,是否相等,相等表示需要進行分表,如果需要分表,就走分表程式碼執行邏輯,不分表直接呼叫invocation.proceed()

返回

4.      分表邏輯,讀取StatementHandler sql 進行表替換,表的替換按照分表對應的具體策略進行替換,替換完成,重新繫結SQL,呼叫invocation.proceed() 返回

5.      業務分表策略必須實現此介面,才能被攔截器解析.

 

mybatis-config.xml

 

<?xml version="1.0"encoding="UTF-8" ?>

<!DOCTYPE configuration

        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <settings>

        <setting name="lazyLoadingEnabled"value="false" />

        <!-- 全域性延遲載入 ,在啟用延遲載入的同時,需要禁用"aggressiveLazyLoading" -->

        <setting name="aggressiveLazyLoading"value="false" />

        <!-- 這個設定項在使用者手冊當中的定義當啟用時,有延遲載入屬性的物件在被呼叫時將會完全載入任意屬性。否則,每種屬性將會按需要載入。 -->

        <setting name="lazyLoadTriggerMethods"value="clone" />

        <!-- <setting name="defaultExecutorType"value="BATCH" /> -->

    </settings>

 

    <plugins>

        <plugin interceptor="com.google.code.shardbatis.plugin.ShardPlugin">

            <propertyname="shardingConfig" value="shard_config.xml"/>

        </plugin>

    </plugins>

<plugins>

</plugins>

 

shard_config.xml

 

<?xml version="1.0"encoding="UTF-8"?> 

<!DOCTYPE shardingConfigPUBLIC "-//shardbatis.googlecode.com//DTDShardbatis 2.0//EN" 

  "http://shardbatis.googlecode.com/dtd/shardbatis-config.dtd">

<shardingConfig>

    <!--parseList可選配置如果配置了parseList,只有在parseList範圍內的sql才會被解析和修改,配置到dao -->

    <parseList>

        <value>com.xxx.member.dao.xxxEdt.insert</value>

    </parseList>

    <!-- 配置分表策略 -->

    <strategy tableName="t_crm_member"strategyClass="com.xxx.member.dao.UserIdStrategy" />

</shardingConfig>

 

程式碼分析如下:

com.google.code.shardbatis.plugin.ShardPlugin核心類

com.google.code.shardbatis.strategy.ShardStrategy  核心介面

 

@Intercepts( { @Signature(type = StatementHandler.class, method = "prepare",args = { Connection.class })})

publicclass ShardPlugin implements Interceptor {

     public Object intercept(Invocation invocation) throws Throwable{

              StatementHandler statementHandler =(StatementHandler) invocation.getTarget();

         MappedStatement mappedStatement = null;

         if (statementHandlerinstanceofRoutingStatementHandler) {

              StatementHandler delegate = (StatementHandler)ReflectionUtils

                       .getFieldValue(statementHandler, "delegate");

              mappedStatement = (MappedStatement)ReflectionUtils.getFieldValue(delegate, "mappedStatement");

         } else {

              mappedStatement = (MappedStatement)ReflectionUtils.getFieldValue(statementHandler, "mappedStatement");

         }

         String mapperId = mappedStatement.getId();        

         if (isShouldParse(mapperId)) {

              String sql = statementHandler.getBoundSql().getSql();

              if (log.isDebugEnabled()) {

                   log.debug("Original Sql ["+ mapperId+ "]:"+ sql);

              }

              Object params = statementHandler.getBoundSql().getParameterObject();

             

              SqlConverterFactory cf = SqlConverterFactory.getInstance();

              sql= cf.convert(sql,params, mapperId);

              if (log.isDebugEnabled()) {

                   log.debug("Converted Sql ["+ mapperId+ "]:"+ sql);

              }

              ReflectionUtils.setFieldValue(statementHandler

                       .getBoundSql(), "sql", sql);

         }

         returninvocation.proceed();

}

     publicObject plugin(Object target){

 

}

public void setProperties(Properties properties){

 

}

}

publicinterface ShardStrategy{

     /**

      * 得到實際表名

      * @param baseTableName 邏輯表名,一般是沒有字首或者是字尾的表名

      * @param params mybatis執行某個statement時使用的引數

      * @param mapperId mybatis配置的statement id

      * @return

      */

     String getTargetTableName(String baseTableName,Object params,String mapperId);

}

Shardbatis優化改進

1. SqlConverterFactory.convert 方法

原始碼:

SqlConverterFactory cf =SqlConverterFactory.getInstance();

sql = cf.convert(sql,params, mapperId);

 

重寫convert方法:

sql = convert(sql, params, mapperId);

 

protected String convert(String sql,Object params, String mapperId) {

        ShardConfigHolder configFactory =ShardConfigHolder.getInstance();

        Map<String, ShardStrategy> strategyRegister=configFactory.getStrategyRegister();

        Iterator<String> iterators=strategyRegister.keySet().iterator();

        while(iterators.hasNext()){

            String tableName = iterators.next().toUpperCase();

            //獲取分表策略並轉換sql

            ShardStrategy strategy =configFactory.getStrategy(tableName);

            String shardTable =strategy.getTargetTableName(tableName, params, mapperId);

            sql =sql.replaceAll(jointRegex(tableName), jointShard(shardTable));

        }

        returnsql ;

    }

    /**

     * 生成匹配正則

     *

     * @param tabelName 表名

     * @return

     */

    private String jointRegex(String tabelName) {

        returnnew StringBuffer(REGEX_PREFIX).append(tabelName).append(REGEX_SUFIX).toString();

    }

    /**

     * 拼接分表名

     *

     * @param shardTable 分表名

     * @return

     */

    private String jointShard(String shardTable) {

        returnnew StringBuffer(SPACE).append(shardTable).append(SPACE).toString();

}

 

2.       ShardConfigHolder. isParseId方法

shardbatis框架配置必須要到方法,不能到類級別,而我們業務中都是類級別的,對原始碼進行了修改,滿足自身的要求

 

原始碼:

<?xml version="1.0"encoding="UTF-8"?> 

<!DOCTYPE shardingConfigPUBLIC "-//shardbatis.googlecode.com//DTDShardbatis 2.0//EN" 

  "http://shardbatis.googlecode.com/dtd/shardbatis-config.dtd">

<shardingConfig>

    <!--parseList可選配置如果配置了parseList,只有在parseList範圍內的sql才會被解析和修改,配置到dao -->

    <parseList>

        <value>com.xxx.member.dao.xxxEdt.insert</value>

    </parseList>

    <!-- 配置分表策略 -->

    <strategy tableName="t_crm_member"strategyClass="com.xxx.member.dao.UserIdStrategy" />

</shardingConfig>

修改:

<?xml version="1.0"encoding="UTF-8"?> 

<!DOCTYPE shardingConfigPUBLIC "-//shardbatis.googlecode.com//DTDShardbatis 2.0//EN" 

  "http://shardbatis.googlecode.com/dtd/shardbatis-config.dtd">

<shardingConfig>

    <!--parseList可選配置如果配置了parseList,只有在parseList範圍內的sql才會被解析和修改,配置到dao -->

    <parseList>

        <value> com.xxx.member.dao.xxxEdt </value>

    </parseList>

    <!-- 配置分表策略 -->

    <strategy tableName="t_crm_member"strategyClass="com.xxx.member.dao.UserIdStrategy" />

</shardingConfig>

 

publicclass ShardConfigHolder {

publicboolean isParseId(String id) {

        id=id.substring(0,id.lastIndexOf("."));

        returnparseSet != null && parseSet.contains(id);

   }

}

 

最後,由於筆者水平有限,文章中如有不足之處,敬請讀者斧正,文明交流,溝通分享。