在 SaaS 或多租戶系統中,同一套代碼需根據登錄用戶動態路由到不同數據庫(結構相同,數據隔離)。Spring 2.0.1+ 提供的 AbstractRoutingDataSource 讓這一需求實現得優雅且透明。本文完整還原配置流程,並針對「切換時機」「性能影響」兩大核心疑慮給出解答。
核心組件:
DataSourceMap:常量類,定義數據源鍵值(如 Admin、Yxh)。CustomerContextHolder:基於 ThreadLocal 存儲當前線程綁定的數據源標識,實現線程隔離。DynamicDataSource:繼承 AbstractRoutingDataSource,重寫 determineCurrentLookupKey() 從 ContextHolder 獲取鍵值。<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 繼承複用公共屬性,若連接異構數據庫則各自獨立配置。
<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 兜底防止鍵值缺失。
<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)自動設置,避免污染業務邏輯。
A:不會。 SessionFactory 僅在啟動時構建一次,內部持有的是 DynamicDataSource 代理。運行時 getConnection() 由代理委派給當前線程綁定的真實 DataSource,**無需重建 SessionFactory**。
A:Service 層(事務邊界)。 在事務開始前確定數據源,確保整個事務使用同一連接。若在 DAO 切換,可能導致同一事務跨數據源,破壞 ACID。
使用 ThreadLocal + AOP:登錄時將用戶關聯的數據源鍵存入 Session;請求進入時,攔截器從 Session 讀取並 CustomerContextHolder.set();請求結束時 clear()。業務代碼零感知。