怎樣做才是最優雅方式切換 web 項目數據源 ?

来源:http://www.cnblogs.com/java-class/archive/2017/08/16/7374623.html
-Advertisement-
Play Games

隨著業務變遷/需求變更,JavaEE 應用中會被迫連接多個數據源進行業務處理。 怎樣在不影響原有項目結構的情況下,已最優雅/最簡潔的方式動態切換數據源呢? 本文已一次添加數據源後動態切換實踐為例,描述整個思考和實踐過程,文中如有紕漏,還望指正。 1. 依賴 Spring 動態數據源實現 Spring ...


   隨著業務變遷/需求變更,JavaEE 應用中會被迫連接多個數據源進行業務處理。

   怎樣在不影響原有項目結構的情況下,已最優雅/最簡潔的方式動態切換數據源呢?

   本文已一次添加數據源後動態切換實踐為例,描述整個思考和實踐過程,文中如有紕漏,還望指正。

1. 依賴 Spring  動態數據源實現

   

   Spring 中提供了一個叫做 AbstractRoutingDataSource 抽象路由數據源對象繼承自 AbstractDataSource 並實現了 JDK 中 DataSource 介面。

   也就意味著繼承 AbstractRoutingDataSource  並重寫它 determineCurrentLookupKey 方法的類可以作為數據源,並個性化多數據源動態路由切換。

   (如果你平時夠仔細的話,現開源的資料庫連接池都實現 DataSource 介面併進行了自己的個性化封裝。)

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

   對於一次 web 請求來說可以理解為單獨的線程,將當前數據源暫存線上程當中是比較合理的做法。

public class DbContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal<>();

    /**
     * 設置數據源
     *
     * @param dbSourceEnum 要設置的資料庫枚舉名稱
     */
    public static void setDbType(DBSourceEnum dbSourceEnum) {
        contextHolder.set(dbSourceEnum.getValue());
    }

    /**
     * 取得當前數據源
     */
    public static String getDbType() {
        return String.valueOf(contextHolder.get());
    }

    /**
     * 清除上下文數據
     */
    public static void clearDbType() {
        contextHolder.remove();
    }
}

   當然為了後期的擴展和維護,以及使用的便捷性,這裡數據源對象我們引入枚舉類型。

   這樣後續其他同事編程使用枚舉,改動起來也相當方便,還能進行二次數據源的一些自定義標示。

public enum DBSourceEnum {
    one("dataSource1"),
    two("dataSource2");

    private String value;

    DBSourceEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

   上述的 dataSource1/dataSource2 即為 spring-context  中已載入的數據源對象 Id。

    <bean name="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        ......
    </bean>
    <bean name="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        ......
    </bean>

   接下來在 context 中配置繼承自 AbstractRoutingDataSource 的 DynamicDataSource。

   <bean id="dataSource" class="com.rambo.spm.core.multidb.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="dataSource1" value-ref="dataSource1"/>
                <entry key="dataSource2" value-ref="dataSource2"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="dataSource1"/>
    </bean>

   Ok,這樣在配置後續 dao 層時使用該 DynamicDataSource 即可。

2. 最優雅的切換數據源方式

   完成上述工作之後,其實動態切換數據源已經實現,在業務層如下麵這樣編程。

        DbContextHolder.setDbType(DBSourceEnum.one);
        List<Menu> menuList = menuService.selectList(null);

        DbContextHolder.setDbType(DBSourceEnum.two);
        List<User> userList = userService.selectList(null);

   缺點很明顯,連接數據源2時要進行切換/不利於擴展/切換不當時給後人埋雷的幾率很大。

   和團隊進行交流時,討論出用強大 aop 來攔截 dao 層對象,動態切換數據源的方案。

   對於 dao 層對象來說訪問資料庫的哪張表是確定的,編寫自定義註解與 dao 層對象進行綁定。

   自定義數據源註解如下:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DataSource {

    DBSourceEnum value() default DBSourceEnum.one;
}

   編寫切麵處理對象,在 dao 層對象使用前進行攔截,順手切換數據源,如果沒有數據源註解,設置為預設。

   所以對於項目中原配數據源 dao 層對象,不需要進行任何修改,切麵處理如下。

    @Before("cut()")
    public void doBefore(JoinPoint joinPoint) {
        DataSource dataSource = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
        DbContextHolder.setDbType(dataSource != null ? dataSource.value() : DBSourceEnum.one);
        log.info("當前數據源為:" + DbContextHolder.getDbType());
    }

   多數項目中 dao 層錯綜複雜的抽象和繼承關係會給你 aop 切麵攔截造成一定的困難,多思考、多實踐總會有辦法的。

   好了,就這樣吧,是不是感覺比 aop 攔截方式比在程式中硬編碼更容易擴展、更容易編程、更容易理解,當然也更優雅。

   代碼已托管在:https://git.oschina.net/LanboEx/spmvc-mybatis.git 有興趣的朋友,可以檢到本地 run 一下。

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 虛擬機類載入機制 一、類載入的階段和時機 1.階段 整個生命周期包括:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個階段。 其中驗證、準 ...
  • 通過set()獲取兩個數組的交/並/差集: ...
  • 線程實現和線程安全性 參考博客:(http://www.importnew.com/20672.html),參考書籍:《Java併發編程實戰》 一、線程Java代碼實現 1.繼承Thread + 聲明Thread的子類 + 運行thread子類的方法 2.創建Thread的匿名子類 3.實現Runn ...
  • IP的獲取與轉換 1、前言 IP轉換成整型存儲是資料庫優化一大趨勢,字元串索引比整型索引消耗資源很多,特別是表中數據量大的時候,以及求查詢某一個ip段的數據。本文所指的IP是ip4,ip6暫不再討論範圍 2、ip4轉化為整形 這裡將介紹: php自帶函數 ip2long php原生模擬ip2long ...
  • 在php中有一個 serialize() 函數 可以把數組序列化成字元串進行存儲和傳輸 如果想反序列化這種字元串,在php中只需要一個簡單的unserialize() 函數就可以完成了.但是在golang中可就沒有這麼容易了,非得費個九牛二虎之力,寫上不少代碼才行。 這時候只想感嘆一下,php真的是 ...
  • 使用過SpringMVC的都知道DispatcherServlet,下麵介紹下該Servlet的啟動與初始化。作為Servlet,DispatcherServlet的啟動與Serlvet的啟動過程是相聯繫的。在Serlvet的初始化過程程中,Serlvet的init方法會被調用,以進行初始化。Dis ...
  • 首先,整個頁麵包括:列表數據、數據總數、當前頁數、每頁顯示數據個數、列表總頁數、當前索引數 新建Page類,包含以上6個屬性。其中 總頁數 和 當前索引值 可以通過計算得出,所以可以註釋掉 計算總頁數: 計算當前索引值: 在後臺代碼中設置當前頁面pageNo、每頁多少數據pageSize 在serv ...
  • Java類中對象的序列化工作是通過ObjectOutputStream和ObjectInputStream來完成的。 寫入: 讀取: 註意: 對於任何需要被序列化的對象,都必須要實現介面Serializable,它只是一個標識介面,本身沒有任何成員,只是用來標識說明當前的實現類的對象可以被序列化。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...