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

Spring 雙數據源配置實戰:從 XML 到動態路由

     編輯:PHP 相關知識

場景

單一應用需連接多個業務隔離的數據庫(如:主業務庫 + 日誌分析庫、或 多租戶隔離庫)。Spring 通過多 DataSource + 多 SessionFactory + AOP 動態路由 實現透明切換。

1. 屬性配置分離

<context:property-placeholder location="classpath:config.properties" />

# config.properties
A.driver_class=com.mysql.jdbc.Driver
A.url=jdbc:mysql://db1:3306/biz_a?useUnicode=true&characterEncoding=utf8
A.username=user_a
A.password=pass_a

B.driver_class=com.mysql.jdbc.Driver
B.url=jdbc:mysql://db2:3306/biz_b?useUnicode=true&characterEncoding=utf8
B.username=user_b
B.password=pass_b

2. 定義多個 DataSource

<!-- 數據源 A -->
<bean id="dataSource_A" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${A.driver_class}" />
    <property name="url" value="${A.url}" />
    <property name="username" value="${A.username}" />
    <property name="password" value="${A.password}" />
</bean>

<!-- 數據源 B -->
<bean id="dataSource_B" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${B.driver_class}" />
    <property name="url" value="${B.url}" />
    <property name="username" value="${B.username}" />
    <property name="password" value="${B.password}" />
</bean>

3. 定義對應的 SessionFactory

<bean id="sessionFactory_A" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource_A" />
    <property name="packagesToScan" value="com.example.modulea.entity" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
            <prop key="hibernate.show_sql">false</prop>
        </props>
    </property>
</bean>

<bean id="sessionFactory_B" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource_B" />
    <property name="packagesToScan" value="com.example.moduleb.entity" />
    <!-- hibernateProperties 同上 -->
</bean>

4. DAO 層注入指定 SessionFactory

<!-- 使用 A 庫的 DAO -->
<bean id="userDao" class="com.example.modulea.dao.UserDaoImpl">
    <property name="sessionFactory" ref="sessionFactory_A" />
</bean>

<!-- 使用 B 庫的 DAO -->
<bean id="logDao" class="com.example.moduleb.dao.LogDaoImpl">
    <property name="sessionFactory" ref="sessionFactory_B" />
</bean>

5. 進階:基於註解的動態數據源路由(推薦)

避免 DAO 爆炸式增長,使用 @TargetDataSource 註解 + AOP 切面實現方法級自動切換。

步驟 1:定義註解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    String value() default "dataSource_A"; // 默認數據源
}

步驟 2:動態數據源持有器(ThreadLocal)

public class DynamicDataSourceHolder {
    private static final ThreadLocal holder = new ThreadLocal<>();

    public static void put(String dsKey) { holder.set(dsKey); }
    public static String get() { return holder.get(); }
    public static void clear() { holder.remove(); }
}

步驟 3:繼承 AbstractRoutingDataSource

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.get();
    }
}

步驟 4:配置路由數據源為默認

<bean id="dataSource" class="com.example.config.DynamicRoutingDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="dataSource_A" value-ref="dataSource_A" />
            <entry key="dataSource_B" value-ref="dataSource_B" />
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="dataSource_A" />
</bean>

<bean id="sessionFactory" class="...AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource" /> <!-- 注入路由數據源 -->
</bean>

步驟 5:AOP 切面自動切換

@Aspect
@Order(1) // 確保在事務開啟前切換
public class DataSourceSwitchAspect {
    @Before("@annotation(targetDataSource)")
    public void switchDataSource(TargetDataSource targetDataSource) {
        DynamicDataSourceHolder.put(targetDataSource.value());
    }

    @After("@annotation(targetDataSource)")
    public void clearDataSource() {
        DynamicDataSourceHolder.clear(); // 必須清理,防止線程池污染
    }
}

步驟 6:業務代碼優雅標註

@Service
public class UserService {
    @Autowired UserDao userDao; // 僅注入一個 DAO

    @TargetDataSource("dataSource_A")
    public User getUser(Long id) { return userDao.findById(id); }

    @TargetDataSource("dataSource_B")
    public List getLogs(Long userId) { return logDao.findByUserId(userId); }
}

事務管理陷阱與對策

陷阱後果對策
跨數據源單事務無法保證 ACID,JTA 複雜且效能差業務層面拆分,最終一致性(補償/重試/消息隊列)
線程池複用導致數據源洩漏後續請求使用錯誤數據源@After 必須 clear(),配合 HikariCP 連接驗證
只讀副本路由衝突寫操作誤路由到只讀庫結合 @Transactional(readOnly=true) 判斷路由

結語

Spring 多數據源配置的核心是「隔離與路由」。XML 顯式注入適合數據源固定、模塊清晰的場景;動態路由 + 註解驅動適合業務快速迭代、數據源頻繁變更的場景。選對模式,配合完善的測試與監控,才能駕馭多庫架構的複雜度。

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