# 第09天:动态数据源原理 ## 📚 今日目标 1. 理解动态数据源的原理 2. 理解AbstractRoutingDataSource 3. 实现DynamicDataSource类 4. 理解数据源切换机制 --- ## 🎯 知识点1:什么是动态数据源? ### 问题场景 **多数据源场景**: - 数据库1:用户数据 - 数据库2:订单数据 - 数据库3:商品数据 **动态切换**: - 根据业务逻辑选择不同的数据源 - 同一个方法可能使用不同的数据源 ### Spring的解决方案 **AbstractRoutingDataSource**: - Spring提供的抽象类 - 根据key动态选择数据源 - 支持多个目标数据源 --- ## 🎯 知识点2:AbstractRoutingDataSource ### 核心方法 ```java public abstract class AbstractRoutingDataSource extends AbstractDataSource { // 决定使用哪个数据源的key protected abstract Object determineCurrentLookupKey(); // 目标数据源Map private Map targetDataSources; // 默认数据源 private Object defaultTargetDataSource; } ``` ### 工作原理 ``` 1. 调用 determineCurrentLookupKey() 获取key ↓ 2. 从 targetDataSources 中根据key获取数据源 ↓ 3. 如果找不到,使用 defaultTargetDataSource ↓ 4. 返回数据源 ``` --- ## 🛠️ 实践任务:实现DynamicDataSource ### 步骤1:创建DynamicDataSource类 在 `src/main/java/cn/bugstack/middleware/db/router/dynamic/` 目录下创建 `DynamicDataSource.java`: ```java package cn.bugstack.middleware.db.router.dynamic; import cn.bugstack.middleware.db.router.DBContextHolder; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 动态数据源 * * 根据ThreadLocal中的key动态选择数据源 * * @author 小傅哥 */ public class DynamicDataSource extends AbstractRoutingDataSource { private String defaultDataSource; @Override protected Object determineCurrentLookupKey() { // 从ThreadLocal获取数据源key String dbKey = DBContextHolder.getDBKey(); if (null == dbKey || dbKey.isEmpty()) { // 如果没有设置,返回默认数据源 return defaultDataSource; } return dbKey; } public void setDefaultDataSource(String defaultDataSource) { this.defaultDataSource = defaultDataSource; } } ``` ### 代码解释 1. **继承AbstractRoutingDataSource**: - 实现`determineCurrentLookupKey()`方法 - 返回数据源的key 2. **从ThreadLocal获取key**: - 调用`DBContextHolder.getDBKey()` - 获取当前线程的数据源key 3. **默认数据源**: - 如果没有设置key,使用默认数据源 - 保证系统正常运行 --- ## 🎓 知识点拓展 ### 拓展1:数据源配置 **配置多个数据源**: ```java @Configuration public class DataSourceConfig { @Bean public DataSource dynamicDataSource() { // 创建多个数据源 DataSource db01 = createDataSource("jdbc:mysql://localhost:3306/db01"); DataSource db02 = createDataSource("jdbc:mysql://localhost:3306/db02"); // 创建数据源Map Map targetDataSources = new HashMap<>(); targetDataSources.put("db01", db01); targetDataSources.put("db02", db02); // 创建动态数据源 DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSources); dataSource.setDefaultTargetDataSource(db01); return dataSource; } } ``` ### 拓展2:数据源切换的时机 **切换时机**: 1. **AOP切面**:在方法执行前设置key 2. **数据源选择**:在获取连接时根据key选择 3. **清理**:在方法执行后清理key **流程**: ``` 请求进入 ↓ AOP切面:设置DBContextHolder.setDBKey("db01") ↓ 执行方法:需要数据库连接 ↓ DynamicDataSource.determineCurrentLookupKey() → 返回"db01" ↓ 从targetDataSources获取db01数据源 ↓ 获取连接,执行SQL ↓ AOP切面:清理DBContextHolder.clear() ``` ### 拓展3:事务和数据源 **问题**:事务和数据源的关系 **原理**: - 事务管理器绑定数据源 - 同一个事务内使用同一个数据源 - 事务开始时就确定了数据源 **我们的项目**: - 在AOP切面中设置数据源 - 在事务开始前就确定了数据源 - 保证事务内数据源一致 --- ## ✅ 今日检查清单 - [ ] 理解了动态数据源的原理 - [ ] 理解了AbstractRoutingDataSource - [ ] 实现了DynamicDataSource类 - [ ] 理解了数据源切换机制 - [ ] 理解了事务和数据源的关系 - [ ] 完成了拓展阅读 --- ## 🎯 明日预告 明天我们将学习: - 策略模式设计 - 路由策略接口设计 - 为后续实现做准备 --- ## 💡 思考题 1. 为什么用AbstractRoutingDataSource而不是直接切换数据源? 2. 数据源切换的时机是什么? 3. 如何保证事务内数据源一致? --- ## 📚 参考资源 - [AbstractRoutingDataSource文档](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/datasource/lookup/AbstractRoutingDataSource.html) - [多数据源配置](https://www.baeldung.com/spring-abstract-routing-data-source) - [动态数据源实现](https://www.baeldung.com/spring-boot-configure-multiple-datasources)