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

Spring MyBatis 多資料源配置兩種方案深度解析

     編輯:Java 相關知識

多資料源場景分類

類型特徵典型場景
獨立資料源業務無關、Schema 獨立平台庫 + 遊戲庫、多租戶隔離
主從複制Master 寫、Slave 讀、數據最終一致讀寫分離、報表查詢分流

方案一:獨立資料源(XML 直接配置)

適合業務完全隔離的場景,各資料源擁有獨立的 SqlSessionFactory、TransactionManager、Mapper 掃描包。

核心配置

<!-- 資料源 1:平台庫 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath*:mapper/platform/**/*.xml"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.app.mapper.platform"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

<!-- 資料源 2:遊戲庫 -->
<bean id="dataSourceGame" class="com.alibaba.druid.pool.DruidDataSource"> ... </bean>
<bean id="sqlSessionFactoryGame" class="org.mybatis.spring.SqlSessionFactoryBean"> ... </bean>
<bean id="transactionManagerGame" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> ... </bean>
<tx:annotation-driven transaction-manager="transactionManagerGame"/>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.app.mapper.game"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryGame"/>
</bean>

關鍵點

  • MapperScannerConfigurer 的 sqlSessionFactoryBeanName 指定對應工廠
  • 各資料源事務管理器獨立,不支援跨庫分布式事務
  • Service 層需明確使用對應 Repository,避免誤用

方案二:主從路由(AbstractRoutingDataSource + AOP)

適合讀寫分離場景,單一 SqlSessionFactory,動態路由決定走 Master 或 Slave。

1. 定義資料源枚舉

public enum DataSourceType { MASTER, SLAVE }

2. ThreadLocal 上下文持有者

public class DataSourceContextHolder {
    private static final ThreadLocal<DataSourceType> context = 
        ThreadLocal.withInitial(() -> DataSourceType.MASTER);
    
    public static void set(DataSourceType type) { context.set(type); }
    public static DataSourceType get() { return context.get(); }
    public static void clear() { context.remove(); }
}

3. 路由 DataSource 實現

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

4. Spring 配置裝配

<bean id="dataSourceMaster" class="com.alibaba.druid.pool.DruidDataSource"> ... </bean>
<bean id="dataSourceSlave" class="com.alibaba.druid.pool.DruidDataSource"> ... </bean>

<bean id="routingDataSource" class="com.app.config.RoutingDataSource">
    <property name="defaultTargetDataSource" ref="dataSourceMaster"/>
    <property name="targetDataSources">
        <map key-type="com.app.enums.DataSourceType">
            <entry key="MASTER" value-ref="dataSourceMaster"/>
            <entry key="SLAVE" value-ref="dataSourceSlave"/>
        </map>
    </property>
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="routingDataSource"/>
    <property name="mapperLocations" value="classpath*:mapper/**/*.xml"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="routingDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>

5. AOP 切面動態切換

@Aspect
@Component
public class DataSourceRoutingAspect {
    
    // 所有以 select/find/get/query 開頭的 Service 方法走 Slave
    @Pointcut("execution(* com.app.service..*.select*(..)) || " +
              "execution(* com.app.service..*.find*(..)) || " +
              "execution(* com.app.service..*.get*(..)) || " +
              "execution(* com.app.service..*.query*(..))")
    public void readMethods() {}
    
    @Before("readMethods()")
    public void routeToSlave() {
        DataSourceContextHolder.set(DataSourceType.SLAVE);
    }
    
    // 寫操作、事務方法強制走 Master
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalMethods() {}
    
    @Before("transactionalMethods()")
    public void routeToMaster() {
        DataSourceContextHolder.set(DataSourceType.MASTER);
    }
    
    // 方法結束清理
    @After("readMethods() || transactionalMethods()")
    public void clear() {
        DataSourceContextHolder.clear();
    }
}

6. 原理深度解析

  1. AbstractRoutingDataSource 實現 InitializingBean,afterPropertiesSet() 將配置的 targetDataSources 轉為 resolvedDataSources Map
  2. getConnection() → determineTargetDataSource() → determineCurrentLookupKey() 獲取 ThreadLocal 值 → Map 查找對應 DataSource
  3. AOP 在方法進入前設定 ThreadLocal,方法結束後清理,確保執行緒隔離

方案對比與選型建議

維度方案一:獨立配置方案二:路由+AOP
適用場景業務隔離、多租戶主從複制、讀寫分離
事務支援單庫事務單庫事務(同庫主從)
跨庫事務不支援不支援(需 Seata/Atomikos)
配置複雜度低(重複配置)中(需 AOP 規則設計)
靈活度低高(方法級路由控制)
延遲感知無需業務判斷:強一致查詢走 Master

進階:強一致讀標註

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MasterRead {}

@Aspect
@Component
public class MasterReadAspect {
    @Before("@annotation(MasterRead)")
    public void forceMaster() {
        DataSourceContextHolder.set(DataSourceType.MASTER);
    }
}

// 使用
@Service
public class OrderService {
    @MasterRead  // 購買後立即查詢,必須見到剛寫入的數據
    public Order getOrderAfterPay(Long orderId) { ... }
}

總結

多資料源配置核心在於業務場景判斷:業務隔離選方案一,讀寫分離選方案二。方案二結合 AOP 實現「零侵入」路由,但需嚴謹設計切點表達式,並配合 @MasterRead 等註解處理強一致讀場景。生產環境建議整合 Dynamic-Datasource-Spring-Boot-Starter 等成熟框架,避免重複造輪子。

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