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

Spring AbstractRoutingDataSource 實現動態多資料源切換:AOP 注解驅動完整方案

     編輯:Java 相關知識

在單一應用中連接多個資料庫(如主庫寫、從庫讀、跨專案同步),需動態切換資料源。本文基於 Spring AbstractRoutingDataSource 實現註解驅動的動態切換方案,含核心原理、AOP 織入、事務協同與分散式事務探討。

核心原理:AbstractRoutingDataSource

AbstractRoutingDataSource 繼承 AbstractDataSource,核心邏輯在 determineTargetDataSource():


protected DataSource determineTargetDataSource() {
    Object lookupKey = determineCurrentLookupKey(); // 抽象方法,由子類實現
    DataSource ds = this.resolvedDataSources.get(lookupKey);
    return ds != null ? ds : this.resolvedDefaultDataSource;
}

子類只需實現 determineCurrentLookupKey() 回傳當前資料源 Key。

實作步驟

1. 線程上下文持有者


public class DataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();

    public static void set(String key) { CONTEXT.set(key); }
    public static String get() { return CONTEXT.get(); }
    public static void clear() { CONTEXT.remove(); }
}

2. 動態資料源路由類


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

3. 切換註解


@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "master";
}

4. AOP 切面:方法執行前切換


@Aspect
@Order(1) // 必須在事務切面之前執行
public class DataSourceSwitchAspect {
    @Before("@annotation(ds)")
    public void switchDataSource(DataSource ds) {
        DataSourceContextHolder.set(ds.value());
    }

    @After("@annotation(ds)")
    public void clearDataSource(DataSource ds) {
        DataSourceContextHolder.clear();
    }
}

5. Spring 配置


<bean id="masterDs" class="com.zaxxer.hikari.HikariDataSource">
    <property name="jdbcUrl" value="${master.url}"/>
    <property name="username" value="${master.user}"/>
    <property name="password" value="${master.pwd}"/>
</bean>

<bean id="slaveDs" class="com.zaxxer.hikari.HikariDataSource">
    <property name="jdbcUrl" value="${slave.url}"/>
    <property name="username" value="${slave.user}"/>
    <property name="password" value="${slave.pwd}"/>
</bean>

<bean id="routingDs" class="com.example.datasource.DynamicRoutingDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="master" value-ref="masterDs"/>
            <entry key="slave" value-ref="slaveDs"/>
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="masterDs"/>
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="routingDs"/>
</bean>

<aop:aspectj-autoproxy/>

6. 業務層使用


@Service
public class OrderService {
    @DataSource("master")
    public void createOrder(Order order) { ... } // 寫主庫

    @DataSource("slave")
    public List<Order> queryOrders() { ... } // 讀從庫
}

關鍵細節:AOP 執行順序

資料源切換必須在 事務開啟之前 完成,否則連接已從預設資料源獲取。配置 @Order(1) 確保切換切面優先於事務切面(@Order(2))。

分散式事務難題

若單一業務需跨多資料源且要求 ACID(如:主庫寫訂單、從庫扣庫存),本地事務無法保證一致性。解決方案:

  • Atomikos / Bitronix:JTA 實現,支援 XA 兩階段提交。
  • Seata (AT 模式):無侵入式分散式事務,基於全域鎖與反向 SQL。
  • 事件溯源 / SAGA:業務層面補償機制,適合微服務。

建議:儘量避免跨資料源事務,透過資料同步、異步事件最終一致性替代。

總結

  1. AbstractRoutingDataSource + ThreadLocal 實現線程級資料源隔離。
  2. 註解 + AOP 實現聲明式切換,業務碼零侵入。
  3. 切面順序 先於事務,確保連接獲取正確。
  4. 跨庫事務謹慎使用,優先考慮最終一致性方案。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved