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

Spring AOP 實現基於註解的動態資料源切換:讀寫分離完整方案

     編輯:Java 綜合教學

業務場景

主從資料庫架構下,寫操作走主庫,讀操作走從庫,需透過 AOP 在方法執行前自動切換資料源。

核心元件設計

1. 動態資料源路由

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.getDataSource(); // 從 ThreadLocal 取得當前資料源 Key
    }
}

2. 資料源上下文持有者 (ThreadLocal 隔離線程)

public abstract class DBContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal<>();

    public enum DbType {
        DB_TYPE_RW("dataSourceKeyRW"), // 讀寫/主庫
        DB_TYPE_R("dataSourceKeyR");   // 唯讀/從庫

        private final String dataSourceKey;
        DbType(String key) { this.dataSourceKey = key; }
        public String getDataSourceKey() { return dataSourceKey; }
    }

    public static String getDataSource() {
        String ds = contextHolder.get();
        return (ds == null) ? DbType.DB_TYPE_RW.getDataSourceKey() : ds; // 預設主庫
    }

    public static void setDataSource(String key) { contextHolder.set(key); }
    public static void clear() { contextHolder.remove(); }
}

3. AOP 切面:切換邏輯與優先級

public class DataSourceSwitchingAop {
    public void switchDataSource(JoinPoint joinPoint) {
        Class targetClass = joinPoint.getTarget().getClass();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        // 優先級 1:方法級 @DataSourceKey
        DataSourceKey dsKey = getAnnotation(targetClass, methodName, DataSourceKey.class, args);
        if (dsKey != null) { setDataSource(joinPoint, dsKey.dataSourceKey()); return; }

        // 優先級 2:方法級 @ReadOnlyKey
        if (getAnnotation(targetClass, methodName, ReadOnlyKey.class, args) != null) {
            setDataSource(joinPoint, DbType.DB_TYPE_R.getDataSourceKey()); return;
        }

        // 優先級 3:類級 @DataSourceKey
        DataSourceKey classKey = targetClass.getAnnotation(DataSourceKey.class);
        if (classKey != null) { setDataSource(joinPoint, classKey.dataSourceKey()); return; }

        // 預設:讀寫主庫
        setDataSource(joinPoint, DbType.DB_TYPE_RW.getDataSourceKey());
    }

    private void setDataSource(JoinPoint jp, String key) {
        DBContextHolder.setDataSource(key);
        log.debug("{} 配置資料源:{}", jp.getSignature(), key);
    }

    // 通用獲取方法級註解(支援參數解析)邏輯省略...
}

4. 自訂註解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ReadOnlyKey {} // 僅標記唯讀

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface DataSourceKey {
    String dataSourceKey() default "dataSource"; // 可指定具體資料源 Key
}

Spring 配置整合

<!-- 事務管理器綁定動態資料源 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dynamicDataSource"/>
</bean>

<!-- AOP 切面:order=0 確保在事務開啟前切換資料源 -->
<bean id="dataSourceSwitchingAop" class="com.xxx.common.framework2x.dao.DataSourceSwitchingAop"/>

<aop:config>
    <aop:aspect id="dataSourceSwitching" ref="dataSourceSwitchingAop" order="0">
        <aop:pointcut id="dataSourceSwitchingService" 
            expression="execution(* com.xxx.manager..*.*(..))"/>
        <aop:before method="switchDataSource" pointcut-ref="dataSourceSwitchingService"/>
    </aop:aspect>
</aop:config>

關鍵注意點

  1. 切面執行順序:order="0" 確保資料源切換在 @Transactional 開啟事務之前執行,否則事務綁定錯誤連線
  2. 註解優先級:方法 @DataSourceKey > 方法 @ReadOnlyKey > 類 @DataSourceKey > 預設主庫
  3. @ReadOnlyKey 僅能用於方法;@DataSourceKey 可用於方法或類
  4. 多資料源擴充:直接在方法加 @DataSourceKey("dataSourceKeyX") 即可

使用範例

@Service
public class OrderService {
    @ReadOnlyKey // 走從庫
    public Order queryOrder(Long id) { ... }

    @DataSourceKey("dataSourceKeyRW") // 顯式指定主庫
    public void createOrder(Order order) { ... }

    @DataSourceKey("dataSourceKeyReport") // 走報表庫
    public Report generateReport() { ... }
}

總結

方案核心:AbstractRoutingDataSource + ThreadLocal 上下文 + AOP 前置切換 + 註解聲明式配置。優點:非侵入式、優先級清晰、支援多資料源、與 Spring 事務無縫整合。

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