ApiBoot DataSource Switch

顾名思义,DataSource Switch是用于数据源选择切换的框架,这是一款基于Spring AOP切面指定注解实现的,通过简单的数据源注解配置就可以完成访问时的自动切换,DataSource Switch切换过程中是线程安全的。

添加依赖

使用DataSource Switch很简单,在pom.xml配置文件内添加如下依赖:

  1. <!--ApiBoot DataSource Switch-->
  2. <dependency>
  3. <groupId>org.minbox.framework</groupId>
  4. <artifactId>api-boot-starter-datasource-switch</artifactId>
  5. </dependency>

ApiBoot所提供的依赖都不需要添加版本号,具体查看ApiBoot版本依赖

集成数据源实现

目前ApiBoot DataSource Switch集成了DruidHikariCP两种数据源实现依赖,在使用方面也有一定的差异,因为每一个数据源的内置参数不一致。

  • Druid:参数配置前缀为api.boot.datasource.druid
  • HikariCP:参数配置前缀为api.boot.datasource.hikari 具体使用请查看下面功能配置介绍。

配置参数

参数名参数默认值是否必填参数描述
api.boot.datasource.primarymaster主数据源名称
api.boot.datasource.druid.{poolName}.url数据库连接字符串
api.boot.datasource.druid.{poolName}.username用户名
api.boot.datasource.druid.{poolName}.password密码
api.boot.datasource.druid.{poolName}.driver-class-namecom.mysql.cj.jdbc.Driver驱动类型
api.boot.datasource.druid.{poolName}.filtersstat,wall,slf4jDruid过滤
api.boot.datasource.druid.{poolName}.max-active20最大连接数
api.boot.datasource.druid.{poolName}.initial-size1初始化连接数
api.boot.datasource.druid.{poolName}.max-wait60000最大等待市场,单位:毫秒
api.boot.datasource.druid.{poolName}.validation-queryselect 1 from dual检查sql
api.boot.datasource.druid.{poolName}.test-while-idletrue建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
api.boot.datasource.druid.{poolName}.test-on-borrowfalse申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
api.boot.datasource.druid.{poolName}.test-on-returnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
api.boot.datasource.hikari.{poolName}.url数据库连接字符串
api.boot.datasource.hikari.{poolName}.username用户名
api.boot.datasource.hikari.{poolName}.password密码
api.boot.datasource.hikari.{poolName}.driver-class-namecom.mysql.cj.jdbc.Driver数据库驱动类全限定名
api.boot.datasource.hikari.{poolName}.propertyHikariCP属性配置

HikariCP数据源是SpringBoot2.x自带的,配置参数请访问HikariCP

单主配置

ApiBoot DataSource Switch支持单主数据源的配置,application.yml配置文件如下所示:

  1. api:
  2. boot:
  3. datasource:
  4. # 配置使用hikari数据源
  5. hikari:
  6. # master datasource config
  7. master:
  8. url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai
  9. username: root
  10. password: 123456

修改主数据源名称

master为默认的主数据源的poolName,这里可以进行修改为其他值,不过需要对应修改primary参数,如下所示:

  1. api:
  2. boot:
  3. datasource:
  4. # 主数据源,默认值为master
  5. primary: main
  6. # 配置使用hikari数据源
  7. hikari:
  8. # main datasource config
  9. main:
  10. url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai
  11. username: root
  12. password: 123456

在上面配置主数据源的poolName修改为main

主从配置

如果你的项目内存在单主单从一主多从的配置方式,如下所示:

  1. api:
  2. boot:
  3. datasource:
  4. # 配置使用hikari数据源
  5. hikari:
  6. # master datasource config
  7. master:
  8. url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai
  9. username: root
  10. password: 123456
  11. # 默认值为【com.mysql.cj.jdbc.Driver】
  12. #driver-class-name: com.mysql.cj.jdbc.Driver
  13. # slave 1 datasource config
  14. slave_1:
  15. url: jdbc:mysql://localhost:3306/oauth2?characterEncoding=utf8&serverTimezone=Asia/Shanghai
  16. username: root
  17. password: 123456
  18. # slave 2 datasource config
  19. slave_2:
  20. url: jdbc:mysql://localhost:3306/resources?characterEncoding=utf8&serverTimezone=Asia/Shanghai
  21. username: root
  22. password: 123456

在上面是一主多从的配置方式,分别是masterslave_1slave_2

多类型数据库配置

ApiBoot DataSource Switch提供了一个项目内连接多个不同类型的数据库,如:MySQLOracle…等,如下所示:

  1. api:
  2. boot:
  3. # 主数据源,默认值为master
  4. primary: mysql
  5. hikari:
  6. mysql:
  7. url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai
  8. username: root
  9. password: 123456
  10. oracle:
  11. url: jdbc:oracle:thin:@172.16.10.25:1521:torcl
  12. username: root
  13. password: 123456
  14. driver-class-name: oracle.jdbc.driver.OracleDriver

在上面配置中,master主数据源使用的MySQL驱动连接MySQL数据库,而slave从数据源则是使用的Oracle驱动连接的Oracle数据库。

动态创建数据源

ApiBoot DataSource Switch内部提供了动态创建数据源的方法,可以通过注入ApiBootDataSourceFactoryBean来进行添加,如下所示:

  1. @Autowired
  2. private ApiBootDataSourceFactoryBean factoryBean;

  3. public void createNewDataSource() throws Exception {

  4. // 创建Hikari数据源

  5. // 如果创建Druid数据源,使用DataSourceDruidConfig

  6. DataSourceHikariConfig config = new DataSourceHikariConfig();

  7. // 数据库连接:必填

  8. config.setUrl("jdbc:mysql://localhost:3306/resources");

  9. // 用户名:必填

  10. config.setUsername("root");

  11. // 密码:必填

  12. config.setPassword("123456");

  13. // 数据源名称:必填(用于@DataSourceSwitch注解value值使用)

  14. config.setPoolName("dynamic");

  15. // 创建数据源

  16. DataSource dataSource = factoryBean.newDataSource(config);

  17. Connection connection = dataSource.getConnection();

  18. System.out.println(connection.getCatalog());

  19. connection.close();

  20. }

自动切换

ApiBoot DataSource Switch的数据源自动切换主要归功于SpringAOP,通过切面@DataSourceSwitch注解,获取注解配置的value值进行设置当前线程所用的数据源名称,从而通过AbstractRoutingDataSource进行数据源的路由切换。

我们沿用上面一主多从的配置进行代码演示,配置文件application.yml参考上面配置,代码示例如下:

从数据源示例类

  1. @Service
  2. @DataSourceSwitch("slave")
  3. public class SlaveDataSourceSampleService {
  4. /**
  5. * DataSource Instance
  6. */
  7. @Autowired
  8. private DataSource dataSource;
  9.  
  10. /**
  11. * 演示输出数据源的catalog
  12. *
  13. * @throws Exception
  14. */
  15. public void print() throws Exception {
  16. // 获取链接
  17. Connection connection = dataSource.getConnection();
  18. // 输出catalog
  19. System.out.println(this.getClass().getSimpleName() + " ->" + connection.getCatalog());
  20. // 关闭链接
  21. connection.close();
  22. }
  23. }

主数据源示例类

  1. @Service
  2. @DataSourceSwitch("master")
  3. public class MasterDataSourceSampleService {
  4. /**
  5. * DataSource Instance
  6. */
  7. @Autowired
  8. private DataSource dataSource;
  9. /**
  10. * Slave Sample Service
  11. */
  12. @Autowired
  13. private SlaveDataSourceSampleService slaveDataSourceSampleService;
  14.  
  15. /**
  16. * 演示输出主数据源catalog
  17. * 调用从数据源类演示输出catalog
  18. *
  19. * @throws Exception
  20. */
  21. public void print() throws Exception {
  22. Connection connection = dataSource.getConnection();
  23. System.out.println(this.getClass().getSimpleName() + " ->" + connection.getCatalog());
  24. connection.close();
  25. slaveDataSourceSampleService.print();
  26. }
  27. }
  • 主数据源的示例类内,我们通过@DataSourceSwitch("master")注解的value进行定位连接master数据源数据库。
  • 同样在从数据库的示例类内,我们也可以通过@DataSourceSwitch("slave")注解的value进行定位连接slave数据源数据库。

单元测试示例

在上面的测试示例中,我们使用交叉的方式进行验证数据源路由是否可以正确的进行切换,可以编写一个单元测试进行验证结果,如下所示:

  1. @Autowired
  2. private MasterDataSourceSampleService masterDataSourceSampleService;
  3. @Test
  4. public void contextLoads() throws Exception {
  5. masterDataSourceSampleService.print();
  6. }

运行上面测试方法,结果如下所示:

  1. MasterDataSourceSampleService ->test
  2. 2019-04-04 10:20:45.407 INFO 7295 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Starting...
  3. 2019-04-04 10:20:45.411 INFO 7295 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Start completed.
  4. SlaveDataSourceSampleService ->oauth2

单次执行数据源切换没有任何的问题,master数据源获取catalog输出后,调用slave示例类进行输出catalog

ApiBoot DataSource Switch会在项目启动时首先初始化master节点DataSource实例,其他实例会在第一次调用时进行初始化。

压力性能测试

单次执行单线程操作没有问题,不代表多线程下不会出现问题,在开头说到过ApiBoot DataSource Switch是线程安全的,所以接下来我们来验证这一点,我们需要添加压力测试的依赖,如下所示:

  1. <dependency>
  2. <groupId>org.databene</groupId>
  3. <artifactId>contiperf</artifactId>
  4. <version>2.3.4</version>
  5. <scope>test</scope>
  6. </dependency>

接下来把上面的单元测试代码改造下,如下所示:

  1. // 初始化压力性能测试对象
  2. @Rule
  3. public ContiPerfRule i = new ContiPerfRule();
  4.  
  5. @Autowired
  6. private MasterDataSourceSampleService masterDataSourceSampleService;
  7. /**
  8. * 开启500个线程执行10000次
  9. */
  10. @Test
  11. @PerfTest(invocations = 10000, threads = 500)
  12. public void contextLoads() throws Exception {
  13. masterDataSourceSampleService.print();
  14. }

测试环境:

硬件:i7、16G、256SSD

系统:OS X

整个过程大约是10秒左右,ApiBoot DataSource Switch并没有发生出现切换错乱的情况。

注意事项

  • 在使用ApiBoot DataSource Switch时需要添加对应数据库的依赖
  • 如果使用Druid连接池,不要配置使用druid-starter的依赖,请使用druid依赖。
  • 配置poolName时不要添加特殊字符、中文、中横线等。