溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

springboot+dynamicDataSource怎么實現動態添加切換數據源

發布時間:2022-01-07 15:37:43 來源:億速云 閱讀:1324 作者:iii 欄目:開發技術
# SpringBoot + DynamicDataSource 實現動態添加切換數據源

## 一、前言

在現代企業級應用開發中,多數據源的需求變得越來越普遍。無論是出于分庫分表的考慮,還是需要連接不同業務系統的數據庫,動態數據源切換都成為了必備技能。本文將詳細介紹如何在SpringBoot項目中通過dynamic-datasource框架實現動態添加和切換數據源。

## 二、動態數據源核心概念

### 2.1 什么是動態數據源

動態數據源(Dynamic DataSource)是指應用程序在運行時能夠根據需要切換不同的數據庫連接。與傳統的單一數據源不同,動態數據源具有以下特點:

1. 運行時動態添加新數據源
2. 支持多數據源之間的自由切換
3. 可根據業務邏輯自動選擇數據源

### 2.2 常見應用場景

- 多租戶SaaS系統
- 讀寫分離架構
- 分庫分表實現
- 異構數據庫集成

## 三、技術選型

### 3.1 主流實現方案對比

| 方案                | 優點                          | 缺點                          |
|---------------------|-----------------------------|-----------------------------|
| AbstractRoutingDataSource | Spring原生支持,無需額外依賴 | 功能較為基礎,缺少高級特性      |
| dynamic-datasource  | 功能豐富,文檔完善            | 需要引入第三方依賴            |
| MyBatis多數據源      | 與MyBatis深度集成            | 對其他ORM框架支持不足         |

### 3.2 為什么選擇dynamic-datasource

1. 支持數據源分組(主從架構)
2. 提供豐富的SPI擴展點
3. 內置敏感信息加密
4. 支持Seata分布式事務
5. 活躍的社區維護

## 四、環境準備

### 4.1 項目依賴

```xml
<dependencies>
    <!-- SpringBoot基礎依賴 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- 數據庫相關 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    
    <!-- 其他工具 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

4.2 基礎配置

spring:
  datasource:
    dynamic:
      primary: master # 設置默認數據源
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/master_db
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_1:
          url: jdbc:mysql://localhost:3306/slave_db_1
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver

五、核心實現

5.1 動態數據源注冊

創建數據源注冊工具類:

@Slf4j
@Component
public class DataSourceRegisterUtil {
    
    @Autowired
    private DataSourcePropertiesCreator propertiesCreator;
    
    @Autowired
    private DynamicRoutingDataSource routingDataSource;
    
    /**
     * 注冊新數據源
     * @param poolName 數據源名稱
     * @param driverClassName 驅動類
     * @param url 數據庫URL
     * @param username 用戶名
     * @param password 密碼
     */
    public synchronized void register(String poolName, 
                                    String driverClassName,
                                    String url,
                                    String username,
                                    String password) {
        // 檢查是否已存在
        if (routingDataSource.getDataSources().containsKey(poolName)) {
            log.warn("數據源[{}]已存在,將被覆蓋", poolName);
        }
        
        // 創建數據源配置
        DataSourceProperty property = new DataSourceProperty();
        property.setPoolName(poolName);
        property.setDriverClassName(driverClassName);
        property.setUrl(url);
        property.setUsername(username);
        property.setPassword(password);
        
        // 創建數據源
        DataSource dataSource = propertiesCreator.createDataSource(property);
        
        // 注冊到動態數據源
        routingDataSource.addDataSource(poolName, dataSource);
        
        log.info("數據源[{}]注冊成功", poolName);
    }
}

5.2 數據源切換實現

5.2.1 注解方式切換

創建數據源注解:

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

創建切面處理:

@Aspect
@Component
@Order(-1) // 確保在事務注解前執行
@Slf4j
public class DynamicDataSourceAspect {
    
    @Around("@annotation(ds)")
    public Object around(ProceedingJoinPoint point, DS ds) throws Throwable {
        String dsKey = ds.value();
        if (!DynamicDataSourceContextHolder.containsDataSource(dsKey)) {
            log.error("數據源[{}]不存在,使用默認數據源", dsKey);
        } else {
            DynamicDataSourceContextHolder.push(dsKey);
            log.debug("切換數據源到: {}", dsKey);
        }
        
        try {
            return point.proceed();
        } finally {
            DynamicDataSourceContextHolder.poll();
            log.debug("恢復數據源到: {}", 
                DynamicDataSourceContextHolder.peek());
        }
    }
}

5.2.2 編程式切換

public class DataSourceContextHolder {
    
    public static void setDataSource(String dsName) {
        if (!DynamicDataSourceContextHolder.containsDataSource(dsName)) {
            throw new IllegalArgumentException("數據源"+dsName+"不存在");
        }
        DynamicDataSourceContextHolder.push(dsName);
    }
    
    public static void clear() {
        DynamicDataSourceContextHolder.poll();
    }
    
    public static String getCurrentDataSource() {
        return DynamicDataSourceContextHolder.peek();
    }
}

5.3 動態添加數據源API

創建REST接口:

@RestController
@RequestMapping("/api/datasource")
public class DataSourceController {
    
    @Autowired
    private DataSourceRegisterUtil registerUtil;
    
    @PostMapping("/add")
    public Result addDataSource(@RequestBody DataSourceDTO dto) {
        try {
            registerUtil.register(
                dto.getPoolName(),
                dto.getDriverClassName(),
                dto.getUrl(),
                dto.getUsername(),
                dto.getPassword()
            );
            return Result.success();
        } catch (Exception e) {
            return Result.fail(e.getMessage());
        }
    }
}

@Data
class DataSourceDTO {
    private String poolName;
    private String driverClassName;
    private String url;
    private String username;
    private String password;
}

六、高級功能實現

6.1 數據源健康檢查

@Component
public class DataSourceHealthChecker {
    
    @Autowired
    private DynamicRoutingDataSource routingDataSource;
    
    private final Map<String, Boolean> healthStatus = new ConcurrentHashMap<>();
    
    @Scheduled(fixedDelay = 30000)
    public void checkAllDataSources() {
        Map<String, DataSource> dataSources = routingDataSource.getDataSources();
        dataSources.forEach((name, ds) -> {
            boolean healthy = testConnection(ds);
            healthStatus.put(name, healthy);
            if (!healthy) {
                log.error("數據源[{}]連接異常", name);
            }
        });
    }
    
    private boolean testConnection(DataSource dataSource) {
        try (Connection conn = dataSource.getConnection()) {
            return conn.isValid(3);
        } catch (SQLException e) {
            return false;
        }
    }
    
    public boolean isHealthy(String dsName) {
        return healthStatus.getOrDefault(dsName, false);
    }
}

6.2 數據源負載均衡

實現讀數據源的輪詢負載均衡:

@Component
public class ReadDataSourceLoadBalancer {
    
    @Autowired
    private DynamicRoutingDataSource routingDataSource;
    
    private final AtomicInteger counter = new AtomicInteger(0);
    
    public String selectReadDataSource() {
        List<String> readDataSources = routingDataSource.getDataSources()
            .keySet().stream()
            .filter(name -> name.startsWith("slave_"))
            .collect(Collectors.toList());
        
        if (readDataSources.isEmpty()) {
            return null;
        }
        
        int index = counter.getAndIncrement() % readDataSources.size();
        if (counter.get() > 10000) {
            counter.set(0);
        }
        
        return readDataSources.get(index);
    }
}

七、最佳實踐

7.1 事務處理注意事項

在多數據源環境下,事務處理需要特別注意:

  1. 跨數據源事務:需要使用分布式事務解決方案如Seata
  2. 注解順序@DS注解必須放在@Transactional之前
  3. 傳播行為:避免在切換數據源的方法中使用REQUIRES_NEW

7.2 性能優化建議

  1. 連接池配置:為不同數據源配置合適的連接池參數
    
    spring:
     datasource:
       dynamic:
         datasource:
           master:
             hikari:
               maximum-pool-size: 20
               minimum-idle: 5
    
  2. 數據源預熱:應用啟動時初始化常用數據源連接
  3. 監控集成:集成Micrometer監控數據源指標

八、常見問題解決方案

8.1 數據源無法切換

現象:注解切換不生效,始終使用默認數據源

排查步驟: 1. 檢查@DS注解是否被正確掃描 2. 確認切面執行順序高于事務切面 3. 檢查數據源名稱是否拼寫正確

8.2 動態添加的數據源不生效

可能原因: 1. 新數據源配置有誤 2. 未正確刷新數據源集合

解決方案

// 添加數據源后手動刷新
routingDataSource.getDataSources().put(poolName, dataSource);
routingDataSource.afterPropertiesSet();

九、總結

本文詳細介紹了在SpringBoot項目中實現動態數據源的全過程,包括:

  1. 動態數據源的基本原理和核心概念
  2. dynamic-datasource框架的集成方法
  3. 數據源動態注冊和切換的實現細節
  4. 生產環境中的高級功能和最佳實踐

通過本文的指導,開發者可以快速在項目中實現靈活的多數據源管理,滿足復雜業務場景下的數據訪問需求。


附錄:完整配置示例

spring:
  datasource:
    dynamic:
      primary: master
      strict: true # 嚴格模式匹配數據源
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/master_db?useSSL=false
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
          hikari:
            connection-timeout: 30000
            maximum-pool-size: 20
        slave_1:
          url: jdbc:mysql://localhost:3306/slave_1?useSSL=false
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
      druid: # 公共druid配置
        initial-size: 5
        max-active: 20
        min-idle: 5

”`

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女