首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Spring 動態數據源實戰:基於 AbstractRoutingDataSource 實現多租戶數據庫無縫切換

     編輯:Java 相關知識

在 SaaS 或多租戶系統中,同一套代碼需根據登錄用戶動態路由到不同數據庫(結構相同,數據隔離)。Spring 2.0.1+ 提供的 AbstractRoutingDataSource 讓這一需求實現得優雅且透明。本文完整還原配置流程,並針對「切換時機」「性能影響」兩大核心疑慮給出解答。

架構設計

核心組件:

  1. DataSourceMap:常量類,定義數據源鍵值(如 Admin、Yxh)。
  2. CustomerContextHolder:基於 ThreadLocal 存儲當前線程綁定的數據源標識,實現線程隔離。
  3. DynamicDataSource:繼承 AbstractRoutingDataSource,重寫 determineCurrentLookupKey() 從 ContextHolder 獲取鍵值。

Spring XML 配置詳解

1. 定義底層 DataSource

<bean id="parentDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="COM.ibm.db2.jdbc.net.DB2Driver"/>
    <property name="url" value="jdbc:db2:127.0.0.1:TEST"/>
</bean>

<bean id="adminDataSource" parent="parentDataSource">
    <property name="username" value="admin"/>
    <property name="password" value="master997mb"/>
</bean>
<bean id="yxhDataSource" parent="parentDataSource">
    <property name="username" value="yxh"/>
    <property name="password" value="yxh"/>
</bean>

使用 parent 繼承複用公共屬性,若連接異構數據庫則各自獨立配置。

2. 配置動態路由 DataSource

<bean id="dataSource" class="cn.com.xinli.ccp.dynamicds.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="Yxh" value-ref="yxhDataSource"/>
            <entry key="Admin" value-ref="adminDataSource"/>
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="adminDataSource"/>
</bean>

key-type 必須與 DataSourceMap 常量類型一致;defaultTargetDataSource 兜底防止鍵值缺失。

3. SessionFactory 引用動態 DataSource

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">...</property>
    <property name="hibernateProperties">...</property>
</bean>

業務代碼中如何切換?

// Service 或 Controller 層
CustomerContextHolder.setCustomerType(DataSourceMap.Yxh);
myDao.save(entity); // 自動路由到 yxhDataSource

CustomerContextHolder.setCustomerType(DataSourceMap.Admin);
myDao.save(entity); // 同一 Dao,寫入 adminDataSource

關鍵點: 切換必須在 同一線程、同一事務邊界內 完成。建議在 Service 方法入口處通過 AOP 或攔截器根據登錄用戶信息(Session/Token)自動設置,避免污染業務邏輯。

核心疑慮解答

Q1:切換頻繁會重複初始化 SessionFactory 導致性能下降嗎?

A:不會。 SessionFactory 僅在啟動時構建一次,內部持有的是 DynamicDataSource 代理。運行時 getConnection() 由代理委派給當前線程綁定的真實 DataSource,**無需重建 SessionFactory**。

Q2:什麼時候切換最合適?Service 層還是 DAO 層?

A:Service 層(事務邊界)。 在事務開始前確定數據源,確保整個事務使用同一連接。若在 DAO 切換,可能導致同一事務跨數據源,破壞 ACID。

Q3:如何傳遞用戶標識而不污染方法簽名?

使用 ThreadLocal + AOP:登錄時將用戶關聯的數據源鍵存入 Session;請求進入時,攔截器從 Session 讀取並 CustomerContextHolder.set();請求結束時 clear()。業務代碼零感知。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved