5-3 代碼層讀寫分離的實現A

  • dataSource 不能滿足主從分離的需求,因爲只能從單一的數據源裏獲取數據,也就是隻能讀取一個jdbc.url.

  • 我們需要實現,寫的時候用主庫數據源,讀的時候用從庫數據源

1.

package com.o2o.dao.split;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
//AbstractRoutingDataSource是個抽象類,具有路由功能能路由到不同數據源,類內還有determineTargetDataSource()方法來決定用哪個數據源,類內還有determineCurrentLookupKey方法來決定數據源的名字
public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDbType();
        //返回對應key值
    }

}

2.

package com.o2o.dao.split;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



public class DynamicDataSourceHolder {
    private static Logger logger =  LoggerFactory.getLogger(DynamicDataSourceHolder.class);
    //能保證線程安全,可以理解爲是一個十分安全的臨時數據存儲容器
    private static  ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static final String DB_MASTER = "master";
    public static final String DB_SLAVE  = "slave" ;
    /**
     * 獲取線程的dbType
     * @return
     */
    public static String getDbType() {
        String db = contextHolder.get();
        if(db==null) {
            db=DB_MASTER;
        }
        return db;
    }
    /**
     * 設置線程的dbType
     * @param str
     */
    public static void setDbType(String str) {
        logger.debug("所使用的數據源爲:" + str);
        contextHolder.set(str);
    }
    /**
     * 清理連接類型
     */
    public static void clearDbType() {
        contextHolder.remove();

    }

}

3.

package com.o2o.dao.split;

/**
 * 用來攔截mybatis的傳遞進來的SQL信息,如果是insert,update就用寫數據源,如果是select就用讀數據源
 * 
 * @author Hasee
 *
 */
@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
    @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
            RowBounds.class, ResultHandler.class }) })
public class DynamicDataSourceInterceptor implements Interceptor {
    /**
     * 主要攔截方法,就是要進行攔截的時候要執行的方法
     */
    private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);
    //定義一個正則表達式來進行匹配SQL語句
    private static final String REGEX = ".insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";

    public Object intercept(Invocation invocations) throws Throwable {
        //要是用@tansaction處理的話synchronizationActive就爲ture
        boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
        //獲得方法的參數,ex:update中的MappedStatement
        Object[] objects = invocations.getArgs();
        MappedStatement ms = (MappedStatement) objects[0];
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;
        if (synchronizationActive != true) {
        //selectKey 爲自增id查詢主鍵(SELECT LAST_INSTER_ID())方法,使用主庫
            if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                    lookupKey = DynamicDataSourceHolder.DB_MASTER;
                } else {
                    BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
                    String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", "");
                    if (sql.matches(REGEX)) {
                        lookupKey = DynamicDataSourceHolder.DB_MASTER;
                    } else {
                        lookupKey = DynamicDataSourceHolder.DB_SLAVE;
                    }
                }
            }
        } else {
            lookupKey = DynamicDataSourceHolder.DB_MASTER;
        }
        logger.debug("設置方法[{}] use[{}] Strategy,SqlCommandType [{}]..", ms.getId(), lookupKey,
                ms.getSqlCommandType().name());
        DynamicDataSourceHolder.setDbType(lookupKey);
        return invocations.proceed();
    }

    // 決定返回的是本體,還是編織好的代理,Executor在mybatis裏是用來支持一系列增刪改查的操作的
    // 就是說如果檢測的對象裏有增刪改查的操作我們就把他攔截下來
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
        // 判定傳入的對象是不是mybatis裏的Executor對象的實例其實不用,因爲在註解裏標註了
            return Plugin.wrap(target, this);
            // 將intercept包裝到target裏面(也就是編制好的代理)
        } else {
            return target;// 返回本體
        }

    }

    @Override
    //用於在Mybatis配置文件中指定一些屬性的。在Configuration初始化當前的Interceptor時就會執行
    public void setProperties(Properties properties) {
        // TODO Auto-generated method stub

    }

}

  • 解釋: mybatis-plugin(攔截器)

    MyBatis的Configuration配置中有一個Plugin配置,根據其名可以解釋爲“插件”,這個插件實質可以理解爲“攔截器”。可以想想攔截器是怎麼實現的。Plugin用到了Java中很重要的一個特性——動態代理。所以這個Plugin可以理解爲,在調用一個方法時,我“攔截”其方法做一些我想讓它做的事。它可以攔截以下方法:

image

定義自己的Interceptor最重要的是要實現plugin方法和intercept方法,在plugin方法中我們可以決定是否要進行攔截進而決定要返回一個什麼樣的目標對象。而intercept方法就是要進行攔截的時候要執行的方法。

對於plugin方法而言,其實Mybatis已經爲我們提供了一個實現。Mybatis中有一個叫做Plugin的類,裏面有一個靜態方法wrap(Object target,Interceptor interceptor),通過該方法可以決定要返回的對象是目標對象還是對應的代理。

對於實現自己的Interceptor而言有兩個很重要的註解,一個是@Intercepts,其值是一個@Signature數組。@Intercepts用於表明當前的對象是一個Interceptor,而@Signature則表明要攔截的接口、方法以及對應的參數類型,每個Signature都是一個攔截點
  • 對註解@Intercepts的理解可以依靠下面的一段代碼
@Intercepts( {
       @Signature(method = "query", type = Executor.class, args = {
              MappedStatement.class, Object.class, RowBounds.class,
              ResultHandler.class }),
       @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class MyInterceptor implements Interceptor {

    public Object intercept(Invocation invocation) throws Throwable {
       Object result = invocation.proceed();
       System.out.println("Invocation.proceed()");
       return result;
    }

    public Object plugin(Object target) {
       return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
       String prop1 = properties.getProperty("prop1");
       String prop2 = properties.getProperty("prop2");
       System.out.println(prop1 + "------" + prop2);
    }

}
  1. 首先看setProperties方法,這個方法在Configuration初始化當前的Interceptor時就會執行,這裏只是簡單的取兩個屬性進行打印。

  2. 其次看plugin方法中我們是用的Plugin的邏輯來實現Mybatis的邏輯的。

  3. 接着看MyInterceptor類上我們用@Intercepts標記了這是一個Interceptor,然後在@Intercepts義了兩個@Signature,即兩個攔截點。第一個@Signature我們定義了該Interceptor將攔截Executor接口中參數類型爲MappedStatement、Object、RowBounds和ResultHandler的query方法;第二個@Signature我們定義了該Interceptor將攔截StatementHandler中參數類型爲Connection的prepare方法。

  4. 最後再來看一下intercept方法,這裏我們只是簡單的打印了一句話,然後調用invocation的proceed方法,使當前方法正常的調用。

  5. 對於這個攔截器,Mybatis在註冊該攔截器的時候就會利用定義好的n個property作爲參數調用該攔截器的setProperties方法。之後在新建可攔截對象的時候會調用該攔截器的plugin方法來決定是返回目標對象本身還是代理對象。對於這個攔截器而言,當Mybatis是要Executor或StatementHandler對象的時候就會返回一個代理對象,其他都是原目標對象本身。然後當Executor代理對象在執行參數類型爲MappedStatement、Object、RowBounds和ResultHandler的query方法或StatementHandler代理對象在執行參數類型爲Connection的prepare方法時就會觸發當前的攔截器的intercept方法進行攔截,而執行這兩個接口對象的其他方法時都只是做一個簡單的代理。


  • 解釋: MappedStatement,SqlSource,BoundSql

    1. MappedStatement類在Mybatis框架中用於表示XML文件中一個sql語句節點,即一個、或者標籤。Mybatis框架在初始化階段會對XML配置文件進行讀取,將其中的sql語句節點對象化爲一個個MappedStatement對象。
    2. SqlSource是一個接口類,在MappedStatement對象中是作爲一個屬性出現的.SqlSource接口只有一個getBoundSql(Object parameterObject)方法,返回一個BoundSql對象。一個BoundSql對象,代表了一次sql語句的實際執行,而SqlSource對象的責任,就是根據傳入的參數對象,動態計算出這個BoundSql,也就是說Mapper文件中的節點的計算,是由SqlSource對象完成的。SqlSource最常用的實現類是DynamicSqlSource.
  • 解釋: 正則表達式

    1. u0020 - 表示空格
    2. .* - 匹配所有字符
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章