讀寫分離實現自動切換數據庫(基於mybatis攔截器)

仍是動態數據源的那一套,先要繼承java

AbstractRoutingDataSource

1spring

package com.statistics.util;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 用戶 :LX
 * 建立時間: 2018/7/4. 23:49
 * 地點:廣州
 * 目的: 動態數據源,若是是作讀寫分離,能夠用這個類來作讀寫的自動切換數據庫,可是這裏只是測試
 * 結果:
 * 1 AbstractRoutingDataSource用來作動態數據源切換的類,要繼承他才行
 * 2 建立 DynamicDataSourceHolder 類,用來作操做數據源
 * 3 編寫 DynamicDataSourceInterceptor 類來自動切換數據源
 * 4 在mybatis的配置文件中去設置 DynamicDataSourceInterceptor 攔截器
 * 5 spring中對數據源進行配置
 * 6 寫好註解,哪些要攔截
 */

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDbType();
    }
}

而後建立數據庫操做類sql

package com.statistics.util;

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


/**
 * 用戶 :LX
 * 建立時間: 2018/7/4. 23:52
 * 地點:廣州
 * 目的: 動態數據源的操做
 * 結果:
 */
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";

    /**
     * 獲取數據源
     * @return
     */
    public static String getDbType(){
        String db = contextHolder.get();
        logger.debug("getDbType方法中從線程安全的裏面獲取到:" + db);
        if (db == null){
            db = DB_MASTER;
        }
        return db;
    }

    /**
     * 注入線程的數據源
     * @param str
     */
    public static void setDbType(String str){
        logger.debug("所注入使用的數據源:" + str);
        contextHolder.set(str);
    }

    /**
     * 清理鏈接
     */
    public static void clearDBType(){
        contextHolder.remove();
    }
}

最關鍵的地方就是下面的這個類數據庫

package com.statistics.util;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.Locale;
import java.util.Properties;

/**
 * 用戶 :LX
 * 建立時間: 2018/7/5. 0:02
 * 地點:廣州
 * 目的: 數據源的攔截器,靠這個來進行切換讀寫時候的數據源
 * 結果:
 *  Interceptor 就是mybatis的 Interceptor,mybatis的攔截器
 */

//@Intercepts標記了這是一個Interceptor,而後在@Intercepts中定義了兩個@Signature,即兩個攔截點。第一個@Signature咱們定義了該Interceptor將攔截Executor接口中參數類型
// 爲MappedStatement、Object、RowBounds和ResultHandler的query方法;第二個@Signature咱們定義了該Interceptor將攔截StatementHandler中參數類型爲Connection的prepare方法。
//   只要攔截了update和query便可,全部的增刪改查都會封裝在update和query中
@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);

    /**
     * 判斷是插入仍是增長仍是刪除之類的正則, u0020是空格
     */
    private static final String regex = ".*insert\\u0020.*|.*delete\\u0020.*|.update\\u0020.*";

    /**
     * 咱們要操做的主要攔截方法,什麼狀況下去攔截,就看這個了
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //判斷當前是否有實際事務處於活動狀態 true 是
        boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
        //獲取sql的資源變量參數(增刪改查的一些參數)
        Object[] objects = invocation.getArgs();
        //MappedStatement 能夠獲取到究竟是增長仍是刪除 仍是修改的操做
        MappedStatement mappedStatement = (MappedStatement) objects[0];
        //用來決定datasource的
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;

        if (synchronizationActive != true){
            //當前的是有事務的====================Object[0]=org.apache.ibatis.mapping.MappedStatement@c028cc
            logger.debug("當前的是有事務的====================Object[0]=" + objects[0]);
            //讀方法,說明是 select 查詢操做
            if (mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)){
                //若是selectKey 爲自增id查詢主鍵(select last_insert_id()方法),使用主庫,這個查詢是自增主鍵的一個查詢
                if (mappedStatement.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)){
                    //使用主庫
                    lookupKey = DynamicDataSourceHolder.DB_MASTER;
                } else {
                    //獲取到綁定的sql
                    BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql(objects[1]);
                    String sqlstr = boundSql.getSql();
                    //toLowerCase方法用於把字符串轉換爲小寫,replaceAll正則將全部的製表符轉換爲空格
                    String sql = sqlstr.toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                    //sql是這個===================:select top 1 * from cipentinfo where regno=?
                    logger.debug("sql是這個===================:" + sql);

                    //使用sql去匹配正則,看他是不是增長、刪除、修改的sql,若是是則使用主庫
                    if (sql.matches(regex)){
                        lookupKey = DynamicDataSourceHolder.DB_MASTER;
                    } else {
                        //從讀庫(從庫),注意,讀寫分離後必定不能將數據寫到讀庫中,會形成很是麻煩的問題
                        lookupKey = DynamicDataSourceHolder.DB_SLAVE;
                    }
                }
            }
        } else {
            //非事務管理的用主庫
            lookupKey = DynamicDataSourceHolder.DB_MASTER;
        }

        //設置方法[slave] ues [SELECT] Strategy,SqlCommanType[SELECT],sqlconmantype[{}]......................... cipentinfoNamespace.selectOneByRegNo
        logger.debug("設置方法[{}] ues [{}] Strategy,SqlCommanType[{}],sqlconmantype[{}]......................... " + mappedStatement.getId(), lookupKey, mappedStatement.getSqlCommandType().name(), mappedStatement.getSqlCommandType());
        //最終決定使用的數據源
        DynamicDataSourceHolder.setDbType(lookupKey);
        return invocation.proceed();
    }

    /**
     * 返回封裝好的對象,決定返回的是本體仍是編織好的代理
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        //Executor是mybatis的,全部的增刪改查都會通過這個類
        if (target instanceof Executor){
            //若是是Executor 那就進行攔截
            return Plugin.wrap(target, this);
        } else {
            //不然返回本體
            return target;
        }
    }

    /**
     * 類初始化的時候作一些操做
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {

    }
}

上面已經會根據語句自動進行選擇數據庫的 工做apache

而後在mybatis的配置文件中將攔截器配置進去安全

<?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>

    <!--mybatis的配置文件-->


    <plugins>
        <!--配置自定義的攔截器,這是mybatis自帶的 -->
        <plugin interceptor="com.statistics.util.DynamicDataSourceInterceptor" />
    </plugins>

</configuration>

最後將spring配置多數據源那裏註冊bean(session

DynamicDataSource

)類mybatis

將全部的數據源管理起來,注意一點,咱們本身定義的數據源的名字必定要和spring配置這裏的數據源的對應,不然找不到數據源就尷尬了。這一部分就省略了,不懂的百度app

相關文章
相關標籤/搜索