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

Spring 整合 MyBatis 實現動態多數據源切換

     編輯:Java 相關知識

架構設計原理

動態數據源切換核心在於基於 ThreadLocal 的上下文持有者結合 Spring 的 AbstractRoutingDataSource 抽象路由機制。運行時根據業務邏輯動態決定當前線程使用哪個數據源,實現讀寫分離、多租戶隔離等場景。

核心組件實現

1. 數據源上下文持有者 (DbContextHolder)

public class DbContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    
    public static void setDbType(String dbType) { contextHolder.set(dbType); }
    public static String getDbType() { return contextHolder.get(); }
    public static void clearDbType() { contextHolder.remove(); }
}

設計要點:使用 ThreadLocal 保證線程隔離,每個線程獨立維護數據源標識,避免併發衝突。

2. 動態路由數據源 (DynamicDataSource)

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDbType();
    }
}

繼承 Spring 抽象類,只需實現 determineCurrentLookupKey() 返回當前數據源 Key,框架自動完成路由分發。

3. Spring XML 配置 (spring.xml)

<!-- 兩個業務數據源 -->
<bean id="jksh" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${driver.jksh}" />
    <property name="url" value="${url.jksh}" />
    <property name="username" value="${username.jksh}" />
    <property name="password" value="${password.jksh}" />
    <!-- 連接池參數 -->
</bean>

<bean id="jclt" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- 同理配置第二個數據源 -->
</bean>

<!-- 動態路由數據源 -->
<bean id="dataSource" class="com.jclt.service.commons.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="jksh" value-ref="jksh" />
            <entry key="jclt" value-ref="jclt" />
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="jksh" /> <!-- 默認數據源 -->
</bean>

4. 業務代碼切換示例

public class DataSourceSwitchDemo {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("client-beans.xml");
        
        // 切換至 jclt 數據源
        DbContextHolder.setDbType("jclt");
        queryUser("jclt");
        
        // 切換至 jksh 數據源
        DbContextHolder.setDbType("jksh");
        queryUser("jksh");
    }
    
    private static void queryUser(String dbName) {
        DataSource ds = (DataSource) ctx.getBean("dataSource");
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(ds);
        bean.setConfigLocation(new FileSystemResource("src/main/resources/ibatis-config.xml"));
        
        try (SqlSession session = bean.getObject().openSession()) {
            User user = session.selectOne("com.jclt.service.Dao.readJKSH.findOne");
            System.out.println("[" + dbName + "] " + user.getName());
        } catch (Exception e) { e.printStackTrace(); }
    }
}

進階應用模式

AOP 切面自動切換(推薦生產環境)

@Aspect
@Component
public class DataSourceAspect {
    @Before("@annotation(targetDataSource)")
    public void switchDataSource(TargetDataSource targetDataSource) {
        DbContextHolder.setDbType(targetDataSource.value());
    }
    
    @After("@annotation(targetDataSource)")
    public void clearDataSource(TargetDataSource targetDataSource) {
        DbContextHolder.clearDbType(); // 清理防止污染下一請求
    }
}

// 使用註解標記
@TargetDataSource("jclt")
public void queryJcltData() { ... }

事務管理注意事項

  • 同一事務內禁止切換數據源,否則導致事務失效
  • 跨數據源事務需引入 JTA (如 Atomikos) 或採用最終一致性方案
  • @Transactional 與數據源切換切面順序:先切換數據源,再開啟事務

常見問題與優化

問題解決方案
線程池複用導致數據源洩漏任務執行完畢必須調用 clearDbType(),或使用 DelegatingRunnable 包裝任務自動清理
只讀從庫路由擴展註解支援 @ReadOnly 自動路由至從庫
啟動時默認數據源未就緒配置 lazy-init="true" 或實現 InitializingBean 檢查連接

總結

基於 AbstractRoutingDataSource + ThreadLocal 的動態數據源方案輕量級、無侵入性,適合多數中小型多數據源場景。生產環境建議結合 AOP 註解驅動,並嚴格管理線程上下文生命周期,避免數據源洩漏導致的數據混亂。

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