大型系統常將數據按業務維度分片存儲於多個資料庫實例,提升水平擴展性。此時應用需根據請求上下文(如用戶 ID、租戶 ID)動態決定數據源,避免將選擇邏輯滲透到 DAO 或 Service 層。

引入一個虛擬 DataSource 代理,內部維護多個真實 DataSource,根據客戶端提供的上下文動態路由。Spring 2.x 提供 AbstractRoutingDataSource 作為基礎框架。

public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger log = Logger.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
// 從 ThreadLocal 獲取當前用戶標識
String userId = (String) DbContextHolder.getContext();
// 業務邏輯:根據 userId 計算目標數據源 ID
Integer dataSourceId = getDataSourceIdByUserId(userId);
return dataSourceId; // 返回作為 Map key 的查找鍵
}
private Integer getDataSourceIdByUserId(String userId) {
// 實際可為取模、範圍映射、配置表查詢等策略
return Math.abs(userId.hashCode()) % 3; // 示例:3 個庫取模
}
}
DbContextHolder 為簡單的 ThreadLocal 持有器,在 Filter 或 Interceptor 中根據請求設置/清除。
<bean id="dataSource" class="com.example.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.Integer">
<entry key="0" value-ref="dataSource0"/>
<entry key="1" value-ref="dataSource1"/>
<entry key="2" value-ref="dataSource2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource0"/>
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="classpath:sqlmap/sql-map-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="UserInfoDAO" class="com.example.dao.impl.UserInfoDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
dataSource Bean,無感知路由邏輯determineCurrentLookupKey 實現DbContextHolder 在請求開始時設置、結束時清除,防止線程池複用導致數據源錯亂