RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Mybatis 攔截器實(shí)現(xiàn)單數(shù)據(jù)源內(nèi)多數(shù)據(jù)庫(kù)切換

京東云 ? 來(lái)源:京東保險(xiǎn) 王奕龍 ? 作者:京東保險(xiǎn) 王奕龍 ? 2024-12-12 10:23 ? 次閱讀

作者:京東保險(xiǎn) 王奕龍

物流的分揀業(yè)務(wù)在某些分揀場(chǎng)地只有一個(gè)數(shù)據(jù)源,因?yàn)閿?shù)據(jù)量比較大,將所有數(shù)據(jù)存在一張表內(nèi)查詢速度慢,也為了做不同設(shè)備數(shù)據(jù)的分庫(kù)管理,便在這個(gè)數(shù)據(jù)源內(nèi)創(chuàng)建了多個(gè)不同庫(kù)名但表完全相同的數(shù)據(jù)庫(kù)

現(xiàn)在需要上線報(bào)表服務(wù)來(lái)查詢所有數(shù)據(jù)庫(kù)中的數(shù)據(jù)進(jìn)行統(tǒng)計(jì),那么現(xiàn)在的問(wèn)題來(lái)了,該如何 滿足在配置一個(gè)數(shù)據(jù)源的情況下來(lái)查詢?cè)摂?shù)據(jù)源下不同數(shù)據(jù)庫(kù)的數(shù)據(jù) 呢,借助搜索引擎查到的分庫(kù)實(shí)現(xiàn)大多是借助 Sharding-JDBC 框架,配置多個(gè)數(shù)據(jù)源根據(jù)分庫(kù)算法實(shí)現(xiàn)數(shù)據(jù)源的切換,但是對(duì)于只有一個(gè)數(shù)據(jù)源的系統(tǒng)來(lái)說(shuō),我覺(jué)得引入框架再將單個(gè)數(shù)據(jù)源根據(jù)不同的庫(kù)名配置成多個(gè)不同的數(shù)據(jù)源來(lái)實(shí)現(xiàn)分庫(kù)查詢的邏輯我覺(jué)得并不好。

如果我們能在 SQL 執(zhí)行前將 SQL 中所有的表名前拼接上對(duì)應(yīng)的庫(kù)名的話,那么就能夠?qū)崿F(xiàn)數(shù)據(jù)源的切換了,下面我們講一下使用 JSqlParser 和 Mybatis攔截器 實(shí)現(xiàn)該邏輯,借助 JSqlParser 主要是為了解析SQL,找到其中所有的表名進(jìn)行拼接,如果大家有更好的實(shí)現(xiàn)方式,該組件并不是必須的。

實(shí)現(xiàn)邏輯

SqlSource 是讀取 XML 中 SQL 內(nèi)容并將其發(fā)送給數(shù)據(jù)庫(kù)執(zhí)行的對(duì)象,如果我們?cè)趫?zhí)行前能攔截到該對(duì)象,并將其中的 SQL 替換掉便達(dá)成了我們的目的。 SqlSource 有多種實(shí)現(xiàn),包括常見(jiàn)的DynamicSqlSource。其中包含著必要的執(zhí)行邏輯,我們需要做的工作便是在這些邏輯執(zhí)行完之后,對(duì) SQL 進(jìn)行改造,所以這次實(shí)現(xiàn)我們使用了 裝飾器模式,在原來(lái)的 SqlSource 上套一層,執(zhí)行完 SqlSource 本身的方法之后對(duì)其進(jìn)行增強(qiáng),代碼如下:

public abstract class AbstractDBNameInterceptor {

    /**
     * SqlSource 的裝飾器,作用是增強(qiáng)了 getBoundSql 方法,在基礎(chǔ)上增加了動(dòng)態(tài)分庫(kù)的邏輯
     */
    static class SqlSourceDecorator implements SqlSource {

        /**
         * SQL 字段名稱
         */
        private static final String SQL_FIELD_NAME = "sql";

        /**
         * 原本的 sql source
         */
        private final SqlSource sqlSource;

        /**
         * 裝飾器進(jìn)行封裝
         */
        public SqlSourceDecorator(SqlSource sqlSource) {
            this.sqlSource = sqlSource;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            try {
                // 先生成出未修改前的 SQL
                BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
                // 獲取數(shù)據(jù)庫(kù)名
                String dbName = getSpecificDBName(parameterObject);
                // 有效才修改
                if (isValid(dbName)) {
                    // 生成需要修改完庫(kù)名的 SQL
                    String targetSQL = getRequiredSqlWithSpecificDBName(boundSql, dbName);
                    // 更新 SQL
                    updateSql(boundSql, targetSQL);
                }

                return boundSql;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * 校驗(yàn)是否為有效庫(kù)名
         */
        private boolean isValid(String dbName) {
            return StringUtils.isNotEmpty(dbName) && !"null".equals(dbName);
        }

        /**
         * 獲取到我們想要的庫(kù)名的 SQL
         */
        private String getRequiredSqlWithSpecificDBName(BoundSql boundSql, String dbName) throws JSQLParserException {
            String originSql = boundSql.getSql();
            // 獲取所有的表名
            Set tables = TablesNamesFinder.findTables(originSql);
            for (String table : tables) {
                originSql = originSql.replaceAll(table, dbName + "." + table);
            }
            return originSql;
        }

        /**
         * 修改 SQL
         */
        private void updateSql(BoundSql boundSql, String sql) throws NoSuchFieldException, IllegalAccessException {
            // 通過(guò)反射修改sql語(yǔ)句
            Field field = boundSql.getClass().getDeclaredField(SQL_FIELD_NAME);
            field.setAccessible(true);
            field.set(boundSql, sql);
        }
    }
    
    // ... 
}

定義了 AbstractDBNameInterceptor 抽象類是為了實(shí)現(xiàn)復(fù)用,并將 SqlSourceDecorator 裝飾器定義為靜態(tài)內(nèi)部類,這樣的話,將所有邏輯都封裝在抽象類內(nèi)部,之后這部分實(shí)現(xiàn)好后研發(fā)直接實(shí)現(xiàn)抽象類的通用方法即可,不必關(guān)注它的內(nèi)部實(shí)現(xiàn)。

結(jié)合注釋我們解釋一下 SqlSourceDecorator 的邏輯,其中用到了 Java 反射相關(guān)的操作。首先通過(guò)反射獲取到 SQL,getSpecificDBName 方法是需要自定義實(shí)現(xiàn)的,其中 parameterObject 對(duì)象是傳到 DAO 層執(zhí)行查詢時(shí)的參數(shù),在我們的業(yè)務(wù)中是能夠根據(jù)其中的設(shè)備相關(guān)參數(shù)拿到對(duì)應(yīng)的所在庫(kù)名的,而設(shè)備和具體庫(kù)名的映射關(guān)系需要提前初始化好。在獲取到具體的庫(kù)名后執(zhí)行 getRequiredSqlWithSpecificDBName 方法來(lái)將其拼接到表名前,在這里我們使用到了 JSqlParser 的工具類,解析出來(lái)所有的表名,執(zhí)行字符串的替換,最后一步同樣是使用反射操作將該參數(shù)值再寫(xiě)回去,這樣便完成了指定庫(kù)名的任務(wù)。

接下來(lái)我們需要看下抽象攔截器中供攔截器復(fù)用的方法,如下:

public abstract class AbstractDBNameInterceptor {

    /**
     * SqlSource 字段名稱
     */
    private static final String SQL_SOURCE_FIELD_NAME = "sqlSource";

    /**
     * 執(zhí)行修改數(shù)據(jù)庫(kù)名的邏輯
     */
    protected Object updateDBName(Invocation invocation) throws Throwable {
        // 裝飾器裝飾 SqlSource
        decorateSqlSource((MappedStatement) invocation.getArgs()[0]);
        return invocation.proceed();
    }

    /**
     * 裝飾 SqlSource
     */
    private void decorateSqlSource(MappedStatement statement) throws NoSuchFieldException, IllegalAccessException {
        if (!(statement.getSqlSource() instanceof SqlSourceDecorator)) {
            Field sqlSource = statement.getClass().getDeclaredField(SQL_SOURCE_FIELD_NAME);
            sqlSource.setAccessible(true);
            sqlSource.set(statement, new SqlSourceDecorator(statement.getSqlSource()));
        }
    }
}

這個(gè)還是比較簡(jiǎn)單的,只是借助反射機(jī)制做了一層“裝飾”,查詢攔截器實(shí)現(xiàn)如下:

@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class SelectDBNameInterceptor extends AbstractDBNameInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return updateDBName(invocation);
    }
}

將其配置到 Mybatis 攔截器中,便能實(shí)現(xiàn)數(shù)據(jù)庫(kù)動(dòng)態(tài)切換了。

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)庫(kù)
    +關(guān)注

    關(guān)注

    7

    文章

    3793

    瀏覽量

    64340
  • mybatis
    +關(guān)注

    關(guān)注

    0

    文章

    60

    瀏覽量

    6709
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    lanbview怎么與數(shù)據(jù)庫(kù)連接?

    擇SQL Server驅(qū)動(dòng)程序,新建一個(gè)SQL Server的驅(qū)動(dòng)程序②在“創(chuàng)建SQL Server創(chuàng)建數(shù)據(jù)源”窗口中輸入“Mydb”作為連接的DSN的名稱,同時(shí)根據(jù)遠(yuǎn)程數(shù)據(jù)庫(kù)服務(wù)的配置,在“服務(wù)
    發(fā)表于 03-22 11:32

    labvies訪問(wèn)數(shù)據(jù)庫(kù)

    )是微軟最新的數(shù)據(jù)庫(kù)訪問(wèn)技術(shù),可以用于編寫(xiě)通過(guò)OLE DB 提供者對(duì)在數(shù)據(jù)庫(kù)服務(wù)中的數(shù)據(jù)進(jìn)行訪問(wèn)和操作的應(yīng)用程序。OLE DB 是一個(gè)底層的數(shù)據(jù)
    發(fā)表于 08-26 10:19

    LabView動(dòng)態(tài)創(chuàng)建數(shù)據(jù)源的方法

    DSN(Data Source Name,數(shù)據(jù)源名)。LabSQL與數(shù)據(jù)庫(kù)之間的連接就是建立在DSN 基礎(chǔ)之上的。但是這種過(guò)于麻煩,在生成操作程序時(shí)不便于安裝,于是需要一種可以在LabView中直接創(chuàng)建數(shù)據(jù)源的方法。通過(guò)資料查證
    發(fā)表于 09-23 01:53

    LabVIEW連接Access數(shù)據(jù)庫(kù)的問(wèn)題

    想在LabVIEW中用Access數(shù)據(jù)庫(kù)實(shí)現(xiàn)數(shù)據(jù)操作,但是電腦沒(méi)法連上數(shù)據(jù)源,照著網(wǎng)站上的做法沒(méi)有找到數(shù)據(jù)源,具體情況如圖,最后報(bào)錯(cuò)。
    發(fā)表于 11-05 21:45

    QuickBI助你成為分析師——搞定數(shù)據(jù)源

    分析師”。產(chǎn)品的核心流程如下圖所示,QuickBI實(shí)現(xiàn)無(wú)縫集成云上數(shù)據(jù)庫(kù):支持阿里云多種數(shù)據(jù)源,包括但不限于 MaxCompute、RDS(MySQL、PostgreSQL、SQL Server
    發(fā)表于 03-28 12:43

    springboo修改數(shù)據(jù)源為Druid

    springboo修改數(shù)據(jù)源Druid整合mybatis 使用Mybatis-Generator插件生成代碼和分頁(yè)插件
    發(fā)表于 05-05 14:45

    mybatis支持數(shù)據(jù)庫(kù)輕兼容的輕量方案

    一個(gè)輕量的方案, 令mybatis支持數(shù)據(jù)庫(kù)輕兼容
    發(fā)表于 04-09 17:44

    SpringBoot項(xiàng)目多數(shù)據(jù)源配置數(shù)據(jù)庫(kù)

    SpringBoot項(xiàng)目多數(shù)據(jù)源配置
    發(fā)表于 06-05 09:51

    數(shù)據(jù)源配置工具

    odbc_for_access.exe1、可直接配置數(shù)據(jù)源。2、可直接創(chuàng)建Access數(shù)據(jù)庫(kù)文件。3、為數(shù)據(jù)庫(kù)配置密碼。
    發(fā)表于 07-01 16:22 ?4次下載

    springmvc 自定義攔截器實(shí)現(xiàn)未登錄用戶的攔截

    springmvc自定義攔截器實(shí)現(xiàn)未登錄用戶的攔截
    發(fā)表于 11-25 14:44 ?2519次閱讀
    springmvc 自定義<b class='flag-5'>攔截器</b><b class='flag-5'>實(shí)現(xiàn)</b>未登錄用戶的<b class='flag-5'>攔截</b>

    數(shù)據(jù)倉(cāng)庫(kù)入門之創(chuàng)建數(shù)據(jù)源

    首先需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)源,SSAS(分析服務(wù))將利用數(shù)據(jù)源來(lái)連接數(shù)據(jù)庫(kù)。一、準(zhǔn)備環(huán)境二、啟動(dòng)SSDT,新建項(xiàng)目三、創(chuàng)建數(shù)據(jù)源
    發(fā)表于 02-24 14:48 ?2529次閱讀
    <b class='flag-5'>數(shù)據(jù)</b>倉(cāng)庫(kù)入門之創(chuàng)建<b class='flag-5'>數(shù)據(jù)源</b>

    基于Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)范圍權(quán)限

    前端的菜單和按鈕權(quán)限都可以通過(guò)配置來(lái)實(shí)現(xiàn),但很多時(shí)候,后臺(tái)查詢數(shù)據(jù)庫(kù)數(shù)據(jù)的權(quán)限需要通過(guò)手動(dòng)添加SQL來(lái)實(shí)現(xiàn)。
    的頭像 發(fā)表于 06-20 09:57 ?1324次閱讀
    基于<b class='flag-5'>Mybatis</b><b class='flag-5'>攔截器</b><b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>數(shù)據(jù)</b>范圍權(quán)限

    如何實(shí)現(xiàn)基于Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)范圍權(quán)限呢?

    前端的菜單和按鈕權(quán)限都可以通過(guò)配置來(lái)實(shí)現(xiàn),但很多時(shí)候,后臺(tái)查詢數(shù)據(jù)庫(kù)數(shù)據(jù)的權(quán)限需要通過(guò)手動(dòng)添加SQL來(lái)實(shí)現(xiàn)。
    的頭像 發(fā)表于 06-20 09:59 ?1229次閱讀
    如何<b class='flag-5'>實(shí)現(xiàn)</b>基于<b class='flag-5'>Mybatis</b><b class='flag-5'>攔截器</b><b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>數(shù)據(jù)</b>范圍權(quán)限呢?

    多數(shù)據(jù)源數(shù)據(jù)轉(zhuǎn)換和同步的ETL工具推薦

    多種數(shù)據(jù)源的連接,包括文件系統(tǒng)、數(shù)據(jù)庫(kù)、消息隊(duì)列、網(wǎng)絡(luò)接口等。它提供了可視化的界面和強(qiáng)大的數(shù)據(jù)處理功能,可以輕松地創(chuàng)建數(shù)據(jù)流,進(jìn)行數(shù)據(jù)轉(zhuǎn)換和
    的頭像 發(fā)表于 07-28 16:32 ?1087次閱讀

    SpringBoot實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源

    最近在做業(yè)務(wù)需求時(shí),需要從不同的數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)然后寫(xiě)入到當(dāng)前數(shù)據(jù)庫(kù)中,因此涉及到切換數(shù)據(jù)源問(wèn)題。本來(lái)想著使用
    的頭像 發(fā)表于 12-08 10:53 ?1012次閱讀
    SpringBoot<b class='flag-5'>實(shí)現(xiàn)</b>動(dòng)態(tài)<b class='flag-5'>切換</b><b class='flag-5'>數(shù)據(jù)源</b>
    RM新时代网站-首页