Spring Boot多數據源切換:三種實現方式詳解與實戰

在復雜業務系統中,多數據源切換已成為必備技能。本文將深入剖析三種主流實現方案,帶你從入門到精通!

一、多數據源應用場景

  1. 讀寫分離:主庫負責寫操作,從庫處理讀請求

  2. 多租戶系統:不同租戶使用獨立數據庫

  3. 分庫分表:業務數據按規則分散存儲

  4. 多數據庫類型:同時使用MySQL、Oracle等異構數據庫

二、3種實現方案對比

方案實現復雜度侵入性維護成本適用場景
AbstractRoutingDataSource中等簡單讀寫分離
多SqlSessionFactory異構數據庫
dynamic-datasource復雜多數據源

三、方案一:AbstractRoutingDataSource

實現原理

通過繼承Spring的AbstractRoutingDataSource類,動態路由數據源

實現步驟

1. 添加依賴
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency>
</dependencies>

?

2. 配置多數據源

# application.yml
spring:datasource:master:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/master_dbusername: rootpassword: root123slave:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/slave_dbusername: rootpassword: root123
3. 動態數據源配置類
@Configuration
public class DataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}@Beanpublic DataSource dynamicDataSource() {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("master", masterDataSource());targetDataSources.put("slave", slaveDataSource());DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(masterDataSource());return dynamicDataSource;}
}
4. 自定義動態數據源
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSourceKey();}public static class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setDataSourceKey(String key) {contextHolder.set(key);}public static String getDataSourceKey() {return contextHolder.get();}public static void clearDataSourceKey() {contextHolder.remove();}}
}
5. 自定義注解切換數據源
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String value() default "master";
}
6. AOP切面實現動態切換
@Aspect
@Component
public class DataSourceAspect {@Before("@annotation(dataSource)")public void beforeSwitchDataSource(JoinPoint point, DataSource dataSource) {String dataSourceKey = dataSource.value();DynamicDataSource.DataSourceContextHolder.setDataSourceKey(dataSourceKey);}@After("@annotation(dataSource)")public void afterSwitchDataSource(JoinPoint point, DataSource dataSource) {DynamicDataSource.DataSourceContextHolder.clearDataSourceKey();}
}
7. 業務層使用示例
@Service
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;// 使用主庫@DataSource("master")public void createUser(User user) {String sql = "INSERT INTO users(name, email) VALUES(?, ?)";jdbcTemplate.update(sql, user.getName(), user.getEmail());}// 使用從庫@DataSource("slave")public List<User> getAllUsers() {String sql = "SELECT * FROM users";return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));}
}

方案優缺點

優點

  • 純Spring實現,無第三方依賴

  • 靈活控制數據源切換

缺點

  • 事務管理復雜

  • 需手動處理連接池

  • 切換邏輯侵入業務代碼

四、方案二:多SqlSessionFactory

實現原理

為每個數據源創建獨立的MyBatis SqlSessionFactory

實現步驟

1. 主數據源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.master", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Beanpublic SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/master/*.xml"));return bean.getObject();}@Beanpublic DataSourceTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
2. 從數據源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.slave", sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}@Beanpublic SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/slave/*.xml"));return bean.getObject();}@Beanpublic DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
3. 業務層使用
@Service
public class OrderService {// 注入主庫Mapper@Autowired@Qualifier("masterOrderMapper")private OrderMapper masterOrderMapper;// 注入從庫Mapper@Autowired@Qualifier("slaveOrderMapper")private OrderMapper slaveOrderMapper;@Transactional(transactionManager = "masterTransactionManager")public void createOrder(Order order) {masterOrderMapper.insert(order);}@Transactional(transactionManager = "slaveTransactionManager")public Order getOrder(Long id) {return slaveOrderMapper.selectById(id);}
}

方案優缺點

優點

  • 各數據源完全隔離

  • 事務管理清晰

  • 支持異構數據庫

缺點

  • 配置復雜,冗余代碼多

  • Mapper需按數據源分包

  • 動態切換不靈活

五、方案三:dynamic-datasource框架

框架優勢

  • 零侵入:通過注解實現數據源切換

  • 功能豐富:支持讀寫分離、分庫分表等

  • 簡單易用:簡化多數據源配置

實現步驟

1. 添加依賴
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.0</version>
</dependency>
2. 配置數據源
spring:datasource:dynamic:primary: master # 默認數據源strict: false   # 是否嚴格匹配數據源datasource:master:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/master_dbusername: rootpassword: root123slave1:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/slave_db1username: rootpassword: root123slave2:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/slave_db2username: rootpassword: root123oracle_db:driver-class-name: oracle.jdbc.OracleDriverurl: jdbc:oracle:thin:@localhost:1521:orclusername: systempassword: oracle123
3. 使用@DS注解切換數據源
@Service
public class ProductService {// 默認使用主庫@Autowiredprivate JdbcTemplate jdbcTemplate;// 使用主庫@DS("master")public void createProduct(Product product) {jdbcTemplate.update("INSERT INTO product(...) VALUES(...)");}// 隨機使用從庫@DS("slave")public Product getProduct(Long id) {return jdbcTemplate.queryForObject("SELECT * FROM product WHERE id = ?", new BeanPropertyRowMapper<>(Product.class), id);}// 指定特定從庫@DS("slave1")public List<Product> getHotProducts() {return jdbcTemplate.query("SELECT * FROM product WHERE hot = 1", new BeanPropertyRowMapper<>(Product.class));}// 使用Oracle數據庫@DS("oracle_db")public List<Category> getOracleCategories() {return jdbcTemplate.query("SELECT * FROM categories", new BeanPropertyRowMapper<>(Category.class));}
}
4. 高級功能:讀寫分離
spring:datasource:dynamic:primary: masterdatasource:master:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://master-host:3306/dbusername: rootpassword: root123slave_1:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://slave1-host:3306/dbusername: rootpassword: root123slave_2:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://slave2-host:3306/dbusername: rootpassword: root123strategy: # 讀寫分離配置load-balance: # 負載均衡策略slave: round_robin # 從庫輪詢策略
5. 事務管理
@DS("master")
@Transactional
public void placeOrder(Order order) {// 1. 扣減庫存productService.reduceStock(order.getProductId(), order.getQuantity());// 2. 創建訂單orderMapper.insert(order);// 3. 記錄日志logService.logOrder(order);// 所有操作都在主庫事務中執行
}

最佳實踐技巧

  1. 數據源分組管理

    spring:datasource:dynamic:datasource:master_1: # 配置...master_2:# 配置...slave_1:# 配置...slave_2:# 配置...group:masters: master_1, master_2slaves: slave_1, slave_2
  2. 多租戶數據源動態注冊

    @Autowired
    private DynamicRoutingDataSource routingDataSource;public void addTenantDataSource(String tenantId, DataSourceProperty property) {DataSource dataSource = dataSourceCreator.createDataSource(property);routingDataSource.addDataSource(tenantId, dataSource);
    }
  3. 自定義負載均衡策略

    public class RandomStrategy implements LoadBalanceStrategy {@Overridepublic String determineDataSource(List<String> dataSourceNames) {Random random = new Random();return dataSourceNames.get(random.nextInt(dataSourceNames.size()));}
    }

六、性能優化建議

  1. 連接池配置優化

    spring:datasource:dynamic:datasource:master:# ...hikari:maximum-pool-size: 20minimum-idle: 5connection-timeout: 30000idle-timeout: 600000max-lifetime: 1800000
  2. 避免頻繁切換數據源

    • 將同一數據源操作集中處理

    • 使用@DSTransactional管理跨庫事務

  3. 監控數據源狀態

    @RestController
    public class DataSourceMonitor {@Autowiredprivate DynamicRoutingDataSource routingDataSource;@GetMapping("/datasources")public Map<String, DataSource> listDataSources() {return routingDataSource.getDataSources();}@GetMapping("/datasources/stats")public Map<String, Object> getDataSourceStats() {Map<String, Object> stats = new HashMap<>();routingDataSource.getDataSources().forEach((key, ds) -> {if(ds instanceof HikariDataSource) {HikariDataSource hikari = (HikariDataSource) ds;HikariPoolMXBean pool = hikari.getHikariPoolMXBean();stats.put(key, Map.of("active", pool.getActiveConnections(),"idle", pool.getIdleConnections(),"total", pool.getTotalConnections()));}});return stats;}
    }
     

七、方案選型建議

  1. 中小型項目:優先選用dynamic-datasource,開發效率高

  2. 異構數據庫系統:選擇多SqlSessionFactory方案,隔離性好

  3. 需要高度定制:AbstractRoutingDataSource提供最大靈活性

  4. 云原生環境:dynamic-datasource + Seata分布式事務

八、常見問題解決方案

  1. 數據源切換失效

    • 檢查方法是否被AOP代理

    • 確保@DS注解在public方法上

    • 避免類內部方法調用

  2. 跨庫事務問題

    // 使用分布式事務
    @DS("order")
    @GlobalTransactional
    public void createOrder(Order order) {// 操作訂單庫orderMapper.insert(order);// 操作庫存庫stockService.reduce(order.getProductId(), order.getQuantity());
    }
  3. 連接泄露檢測

    @Bean
    public DataSource dataSource(DataSourceProperties properties) {HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();dataSource.setLeakDetectionThreshold(5000); // 5秒泄露檢測return dataSource;
    }

九、結語

多數據源管理是現代應用開發的必備技能。通過本文介紹的三種方案:

  1. AbstractRoutingDataSource:Spring原生方案,適合定制化場景

  2. 多SqlSessionFactory:適合異構數據庫系統

  3. dynamic-datasource:生產環境首選,功能強大易用

最佳實踐提示:對于大多數Java項目,推薦使用dynamic-datasource框架,它提供了一站式的解決方案,大大降低了多數據源管理的復雜度。同時結合Spring Cloud Alibaba Seata,可輕松實現分布式事務管理。

擴展閱讀

  • Spring官方文檔:數據訪問

  • dynamic-datasource高級用法

  • MyBatis多數據源最佳實踐

掌握多數據源切換技術,讓你的應用從容應對復雜數據場景!

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/909957.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/909957.shtml
英文地址,請注明出處:http://en.pswp.cn/news/909957.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Kafka性能壓測報告撰寫

在大數據生態體系中&#xff0c;Kafka以其卓越的高吞吐、低延遲特性&#xff0c;成為消息隊列領域的中流砥柱。然而&#xff0c;隨著業務規模不斷擴張&#xff0c;數據流量日益激增&#xff0c;Kafka的性能表現直接關乎業務系統的穩定運行與效率提升。通過科學嚴謹的性能壓測&a…

使用DevEco Testing快速創建HarmonyOS5單元測試

1.測試環境準備 確保已安裝DevEco Studio 5.0在module的build.gradle添加依賴&#xff1a; dependencies {testImplementation org.junit.jupiter:junit-jupiter:5.8.2ohosTestImplementation com.huawei.ohos.testkit:runner:1.0.0.200 }2.創建測試類&#xff08;示例測試計…

開源物聯網(IoT)平臺對比

一些 開源物聯網&#xff08;IoT&#xff09;平臺&#xff0c;它們廣泛應用于設備管理、數據采集、遠程監控和邊緣計算等場景&#xff1a; &#x1f31f; 主流開源物聯網平臺 平臺描述技術棧許可證ThingsBoard功能豐富&#xff0c;支持設備管理、遙測數據收集、規則引擎、告警…

插值與模板字符串

背景。表單渲染需要獲取對象中屬性進行賦值操作。 插值錯誤使用。以下方舉例。其中的placeholder不能被正確渲染。因為Vue 不會解析 {{ }} 在屬性中的內容&#xff1b;如果這樣寫編譯會出問題&#xff0c;而且比較難找出是哪的問題 模板字符串。正確做法時使用。模板字符串用…

Luckfox Pico Pi RV1106學習<4>:RV1106的幀率問題

Luckfox Pico Pi RV1106學習&#xff1c;4&#xff1e;&#xff1a;RV1106的幀率問題 1. 背景2. 問題 1. 背景 接上篇。我在應用中創建3個線程&#xff1a; CAM線程&#xff0c;使用V4L2驅動&#xff0c;從 /dev/video11 獲取圖像。ENC線程&#xff0c;使用硬件編碼器&#x…

內測分發平臺應用的異地容災和負載均衡處理和實現思路?

在軟件開發過程中&#xff0c;內測分發平臺扮演著至關重要的角色。它不僅幫助開發者將應用程序傳播給內部測試人員&#xff0c;還負責收集反饋、跟蹤錯誤并改進產品。然而&#xff0c;為了確保一個平穩、連貫的內測過程&#xff0c;對內測分發平臺實施異地容災和負載均衡機制是…

國內用戶如何高效升級npm:使用阿里云鏡像加速指南

文章目錄 引言為什么需要升級npm?環境檢查使用阿里云鏡像安裝nvm配置阿里云鏡像加速npm使用nvm安裝最新Node.js驗證安裝結果升級npm到最新版本解決常見問題1. 權限問題2. 鏡像源驗證3. 項目創建失敗創建測試項目總結引言 作為前端開發者,npm(Node Package Manager)是我們日…

LeetCode--34.在排序數組中查找元素的第一個和最后一個位置

解題思路&#xff1a; 1.獲取信息&#xff1a; 給定一個非遞減順序的整數數組&#xff0c;要求找出給定元素在該數組中從左往右第一次出現的位置和最后一個出現的位置&#xff0c;即&#xff1a;最右邊的位置和最左邊的位置 如果不存在該元素&#xff0c;則返回{ -1 , -1 } 限制…

低秩分解的本質是通過基矩陣和系數矩陣的線性組合,以最小的存儲和計算代價近似表示復雜矩陣

低秩分解的本質是通過基矩陣和系數矩陣的線性組合&#xff0c;以最小的存儲和計算代價近似表示復雜矩陣 flyfish 一、最基礎起點&#xff1a;數字與數組 數字與標量&#xff08;Scalar&#xff09; 單獨的數&#xff0c;如 1 , 2.5 , ? 3 1, 2.5, -3 1,2.5,?3&#xff0c;…

SVN本地使用--管理個人倉庫

1.SVN官網下載鏈接 Download – TortoiseGit – Windows Shell Interface to Git 一路安裝即可&#xff0c;安裝后在桌面空白處右鍵菜單可以看到選項即安裝成功。 2.建立個人SVN數據庫 選擇一個磁盤新建一個文件夾&#xff0c;在文件夾中右鍵創建數據庫。 3.上傳文件到SVN…

Cloud Automation-Resource optimization, cleanup and dashboard

如何使用Automation Account Run Book實現自動化 1. 什么是 Runbook&#xff1f; Azure Automation Account 中的 Runbook 是一套自動化腳本&#xff0c;用于在云中或混合環境中執行常規任務。Runbook 支持多種腳本語言&#xff0c;包括 PowerShell、Python、Graphical、Powe…

leetcode_3583 統計特殊三元組

1. 題意 求給定數組中下標 ( i , j , k ) (i,j,k) (i,j,k)的對數&#xff0c; 且滿足 i < j < k , 2 a [ j ] a [ i ] a [ k ] i < j <k,2 a[j]a[i]a[k] i<j<k,2a[j]a[i]a[k] 2. 題解 2.1 枚舉中間 三個數枚舉中間那個數&#xff0c;再存前綴和后綴個數…

Sentinel(一):Sentinel 介紹和安裝

一、Sentinel 介紹 1、什么是 Sentinel&#xff1f; 一句話來說&#xff0c;Sentinel就是&#xff1a;分布式系統的流量衛兵&#xff08;官網&#xff09;。 隨著微服務的普及&#xff0c;服務調用的穩定性變得越來越重要。Sentinel以“流量”為切入點&#xff0c;在流量 控制…

pyspark 初試

1、安裝jdk sudo apt-get install openjdk-17-jdk 2、安裝spark curl -o spark.tgz https://mirrors.tuna.tsinghua.edu.cn/apache/spark/spark-4.0.0/spark-4.0.0-bin-hadoop3.tgz tar -xvf spark.tgz mv spark-4.0.0-bin-hadoop3 /opt/spark修改 /etc/profile 添加 exp…

深入解析select模型:FD_SET機制與1024限制的終極指南

在Linux網絡編程中&#xff0c;select函數是最經典的I/O多路復用技術之一&#xff0c;但其核心機制FD_SET的1024限制常成為高并發系統的瓶頸。本文將深入剖析FD_SET實現原理&#xff0c;并提供突破限制的實戰方案。 一、FD_SET底層結構解析 FD_SET本質是固定長度的位圖數組&am…

C函數基礎.go

前言&#xff1a; 在Go語言中&#xff0c;函數是構成程序的基本模塊&#xff0c;它封裝了一段具有特定功能的代碼&#xff0c;使得代碼更易讀&#xff0c;更易維護和重用。熟練掌握函數的定義、調用以及相關特性是成為Go語言開發者的必經之路。 目錄 函數定義&#xff1a;給代…

什么是池化

池化是深度學習中用于降低數據維度、提取核心特征的一種操作&#xff0c;主要應用于卷積神經網絡&#xff08;CNN&#xff09;。其核心思想是通過對局部區域進行聚合統計&#xff08;如取最大值、平均值&#xff09;&#xff0c;保留關鍵信息的同時減少計算量。 池化的作用 降維…

C++ 性能分析工具:Valgrind 與 perf

在 C 開發中&#xff0c;性能優化是提升軟件質量的關鍵環節。內存泄漏和 CPU 資源消耗是最常見的性能瓶頸&#xff0c;而 Valgrind 和 perf 作為專業的性能分析工具&#xff0c;能幫助開發者精準定位這些問題。下面將從工具原理、使用方法、實戰案例等方面進行詳細介紹。 一、…

ABP VNext + MongoDB 數據存儲:多模型支持與 NoSQL 擴展

&#x1f680; ABP VNext MongoDB 數據存儲&#xff1a;多模型支持與 NoSQL 擴展&#xff08;生產級實踐&#xff09; 目錄 &#x1f680; ABP VNext MongoDB 數據存儲&#xff1a;多模型支持與 NoSQL 擴展&#xff08;生產級實踐&#xff09;&#x1f3af; 引言&#x1f9f0…

Cursor Rules 的核心定位與作用 DevOps是

Cursor Rules 是 AI 編程工具 Cursor IDE 中的核心功能&#xff0c;用于約束 AI 生成代碼的行為&#xff0c;確保其符合項目規范、編碼風格或特定技術需求。它本質上是一套持久化、可復用的指令集&#xff0c;會動態插入到 AI 模型的上下文提示中&#xff0c;指導其生成代碼的邏…