# 第15天:完善自动配置类(完整代码) ## 📚 今日目标 1. 完善DataSourceAutoConfig类 2. 实现数据源创建逻辑 3. 实现配置读取(Environment) 4. 实现getGlobalProps和injectGlobal方法 --- ## 🛠️ 完整实现代码 ### DataSourceAutoConfig完整实现 在 `src/main/java/cn/bugstack/middleware/db/router/config/` 目录下完善 `DataSourceAutoConfig.java`: ```java package cn.bugstack.middleware.db.router.config; import cn.bugstack.middleware.db.router.DBRouterConfig; import cn.bugstack.middleware.db.router.DBRouterJoinPoint; import cn.bugstack.middleware.db.router.dynamic.DynamicDataSource; import cn.bugstack.middleware.db.router.dynamic.DynamicMybatisPlugin; import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy; import cn.bugstack.middleware.db.router.strategy.impl.DBRouterStrategyHashCode; import cn.bugstack.middleware.db.router.util.PropertyUtil; import org.apache.ibatis.plugin.Interceptor; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.support.TransactionTemplate; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * 数据源自动配置类 * * @author 小傅哥 */ @Configuration @ConditionalOnClass({DataSource.class, org.apache.ibatis.session.SqlSessionFactory.class}) @EnableConfigurationProperties(DBRouterConfig.class) public class DataSourceAutoConfig implements EnvironmentAware { private static final String TAG_GLOBAL = "global"; private static final String TAG_POOL = "pool"; private Map> dataSourceMap = new HashMap<>(); private Map defaultDataSourceConfig; private int dbCount; private int tbCount; private String routerKey; /** * 创建路由配置 */ @Bean public DBRouterConfig dbRouterConfig() { DBRouterConfig config = new DBRouterConfig(); config.setDbCount(dbCount); config.setTbCount(tbCount); config.setRouterKey(routerKey); return config; } /** * 创建路由策略 */ @Bean @ConditionalOnMissingBean public IDBRouterStrategy dbRouterStrategy(DBRouterConfig dbRouterConfig) { return new DBRouterStrategyHashCode(dbRouterConfig); } /** * 创建AOP切面 */ @Bean @ConditionalOnMissingBean public DBRouterJoinPoint point(DBRouterConfig dbRouterConfig, IDBRouterStrategy dbRouterStrategy) { return new DBRouterJoinPoint(dbRouterConfig, dbRouterStrategy); } /** * 创建MyBatis插件 */ @Bean @ConditionalOnMissingBean public Interceptor plugin() { return new DynamicMybatisPlugin(); } /** * 创建动态数据源 */ @Bean @ConditionalOnMissingBean public DataSource createDataSource() { // 创建多个数据源 Map targetDataSources = new HashMap<>(); for (int i = 1; i <= dbCount; i++) { String dbKey = String.format("db%02d", i); DataSource dataSource = createDataSource(dataSourceMap.get(dbKey)); targetDataSources.put(dbKey, dataSource); } // 创建动态数据源 DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(createDataSource(defaultDataSourceConfig)); return dynamicDataSource; } /** * 创建单个数据源 */ private DataSource createDataSource(Map config) { if (null == config || config.isEmpty()) { return null; } try { // 使用HikariCP创建数据源 com.zaxxer.hikari.HikariConfig hikariConfig = new com.zaxxer.hikari.HikariConfig(); // 设置基本属性 hikariConfig.setJdbcUrl((String) config.get("url")); hikariConfig.setUsername((String) config.get("username")); hikariConfig.setPassword((String) config.get("password")); hikariConfig.setDriverClassName((String) config.get("driver-class-name")); // 设置连接池属性 if (config.containsKey(TAG_POOL)) { Map poolConfig = (Map) config.get(TAG_POOL); if (poolConfig.containsKey("minSize")) { hikariConfig.setMinimumIdle((Integer) poolConfig.get("minSize")); } if (poolConfig.containsKey("maxSize")) { hikariConfig.setMaximumPoolSize((Integer) poolConfig.get("maxSize")); } } return new com.zaxxer.hikari.HikariDataSource(hikariConfig); } catch (Exception e) { throw new RuntimeException("创建数据源失败", e); } } /** * 创建事务模板 */ @Bean public TransactionTemplate transactionTemplate(DataSource dataSource) { return new TransactionTemplate(new DataSourceTransactionManager(dataSource)); } @Override public void setEnvironment(Environment environment) { String prefix = "router.jdbc.datasource."; // 读取数据库数量 dbCount = Integer.parseInt(environment.getProperty(prefix + "dbCount", "2")); tbCount = Integer.parseInt(environment.getProperty(prefix + "tbCount", "4")); routerKey = environment.getProperty(prefix + "routerKey", "userId"); // 读取全局配置 Map globalProps = getGlobalProps(environment, prefix); // 读取每个数据源的配置 for (int i = 1; i <= dbCount; i++) { String dbKey = String.format("db%02d", i); Map dbProps = PropertyUtil.handle(environment, prefix + dbKey, Map.class); if (null != dbProps && !dbProps.isEmpty()) { // 注入全局配置 injectGlobal(globalProps, dbProps); dataSourceMap.put(dbKey, dbProps); } } // 读取默认数据源配置 defaultDataSourceConfig = PropertyUtil.handle(environment, prefix + "default", Map.class); if (null != defaultDataSourceConfig && !defaultDataSourceConfig.isEmpty()) { injectGlobal(globalProps, defaultDataSourceConfig); } } /** * 获取全局配置 */ private Map getGlobalProps(Environment environment, String prefix) { try { Map globalProps = PropertyUtil.handle(environment, prefix + TAG_GLOBAL, Map.class); return null == globalProps ? new HashMap<>() : globalProps; } catch (Exception e) { return new HashMap<>(); } } /** * 注入全局配置到数据源配置 */ private void injectGlobal(Map globalProps, Map dbProps) { if (null == globalProps || globalProps.isEmpty()) { return; } for (Map.Entry entry : globalProps.entrySet()) { if (!dbProps.containsKey(entry.getKey())) { dbProps.put(entry.getKey(), entry.getValue()); } } } } ``` --- ## 📝 配置示例 ### application.yml配置 ```yaml router: jdbc: datasource: dbCount: 2 tbCount: 4 routerKey: userId global: driver-class-name: com.mysql.jdbc.Driver pool: minSize: 5 maxSize: 20 db01: url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 db02: url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 default: url: jdbc:mysql://localhost:3306/default?useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 ``` --- ## 🎓 知识点拓展 ### 拓展1:数据源连接池选择 **HikariCP**: - ✅ 性能最好 - ✅ Spring Boot 2.x默认 - ✅ 轻量级 **Druid**: - ✅ 功能强大(监控、SQL解析) - ✅ 适合生产环境 - ❌ 相对重一些 **选择建议**: - 性能优先:HikariCP - 功能优先:Druid ### 拓展2:配置读取的优先级 **优先级**(从高到低): 1. 数据源特定配置(db01.url) 2. 全局配置(global.url) 3. 默认值 **实现**:使用injectGlobal方法,数据源配置优先 --- ## ✅ 今日检查清单 - [ ] 完善了DataSourceAutoConfig类 - [ ] 实现了数据源创建逻辑 - [ ] 实现了配置读取 - [ ] 实现了getGlobalProps和injectGlobal方法 - [ ] 理解了数据源连接池配置 - [ ] 完成了拓展阅读 --- ## 🎯 明日预告 明天我们将创建spring.factories文件,完成自动配置的入口。 --- ## 💡 思考题 1. 为什么需要全局配置和数据源特定配置? 2. 如何支持其他连接池(如Druid)? 3. 配置读取的优先级是什么? --- ## 📚 参考资源 - [HikariCP文档](https://github.com/brettwooldridge/HikariCP) - [Spring Boot数据源配置](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.sql.datasource) - [多数据源配置](https://www.baeldung.com/spring-boot-configure-multiple-datasources)