使用场景
主要在多租户场景中,常常新的一个租户进来需要动态的添加一个数据源到库中,使得系统不用重启即可切换数据源。
如何实现
引入相关依赖,版本用最新的就行,注意引入 parent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <!-- 版本管理,由spring-boot统一整合管理版本 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid-version}</version> </dependency> <!-- 重点是这个包,这里有个天坑,2.x版本手动排除依赖不行!!建议使用parent标签 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>${dynamic-datasource-version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus-version}</version> </dependency> </dependencies> |
多数据源配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | spring: datasource: dynamic: primary: master #设置默认的数据源或者数据源组,默认值即为master strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源 lazy: true #默认false非懒启动,系统加载到数据源立即初始化连接池 datasource: master: url: jdbc:mysql://192.168.10.46:3306/xxx?useUnicode=true&characterEncoding=utf-8 username: xxx password: xxx driver-class-name: com.mysql.cj.jdbc.Driver slave_1: url: jdbc:mysql://192.168.10.46:3306/xxx?useUnicode=true&characterEncoding=utf-8 username: xxx password: xxx driver-class-name: com.mysql.cj.jdbc.Driver slave_2: url: jdbc:mysql://192.168.10.46:3306/xxx?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true username: xxx password: xxx driver-class-name: com.mysql.cj.jdbc.Driver |
常规的多数据源配置方式如上所示,原理大致是每次启动时,将这些配置加载到某个地方进行管理,而要实现动态数据源,就需要直接操作,将数据源放入到这个个地方。因此,dynamic-datasource 提供了这么一个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public interface DataSourceCreator { /** * 通过属性创建数据源 * * @param dataSourceProperty 数据源属性 * @return 被创建的数据源 */ DataSource createDataSource(DataSourceProperty dataSourceProperty); /** * 当前创建器是否支持根据此属性创建 * * @param dataSourceProperty 数据源属性 * @return 是否支持 */ boolean support(DataSourceProperty dataSourceProperty); } |
而由于接入方式的不同,会有不同的实现类,这些实现类都在 creator 包下
我们来看两个常用的实现类
- DruidDataSourceCreator:适配了 Druid 数据源
- DefaultDataSourceCreator:这是一个通用的创建器,其根据环境自动选择连接池,默认的顺序为 druid>hikaricp>beecp>dbcp>spring basic
动态数据源使用
添加数据源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Autowired private DataSource dataSource; @Autowired private DefaultDataSourceCreator dataSourceCreator; private void cacheAddSourceDTO(DataSourceDTO dto) { DataSourceProperty dataSourceProperty = new DataSourceProperty(); // 添加至缓存 BeanUtils.copyProperties(dto, dataSourceProperty); DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource; // 默认的顺序为druid>hikaricp>beecp>dbcp>spring basic DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty); ds.addDataSource(dto.getPoolName(), dataSource); } |
删除数据源
1 2 | DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource; ds.removeDataSource(poolName); |
源码解析
动态添加数据源的源码比较简单,一句话概括来说就是添加数据源的配置!下面是 druid 的实现
可以看到对外暴露了三个方法,主要关注 doCreateDataSource 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public DataSource doCreateDataSource(DataSourceProperty dataSourceProperty) { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(dataSourceProperty.getUsername()); dataSource.setPassword(dataSourceProperty.getPassword()); dataSource.setUrl(dataSourceProperty.getUrl()); dataSource.setName(dataSourceProperty.getPoolName()); String driverClassName = dataSourceProperty.getDriverClassName(); if (!StringUtils.isEmpty(driverClassName)) { dataSource.setDriverClassName(driverClassName); } DruidConfig config = dataSourceProperty.getDruid(); Properties properties = config.toProperties(gConfig); List<Filter> proxyFilters = this.initFilters(dataSourceProperty, properties.getProperty("druid.filters")); dataSource.setProxyFilters(proxyFilters); dataSource.configFromPropety(properties); //连接参数单独设置 dataSource.setConnectProperties(config.getConnectionProperties()); //设置druid内置properties不支持的的参数 this.setParam(dataSource, config); if (Boolean.FALSE.equals(dataSourceProperty.getLazy())) { try { dataSource.init(); } catch (SQLException e) { throw new ErrorCreateDataSourceException("druid create error", e); } } return dataSource; } |
不难看出,返回类型为 DataSource,是 jdk 中 javax.sql 包提供的,用来连接数据库的实现。而前面所有的操作都是用来封装成 DataSource 这个对象的。
在实际使用过程中,有这么一行代码 (DynamicRoutingDataSource) dataSource
,不难猜出,这里是管理所有数据源的地方,操作了这里才能实现动态添加。