怎樣做才是最優雅方式切換 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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...