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


   隨着業務變遷/需求變更,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 一下。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM