SpringBoot+Druid 运行时切换自定义数据源,前端页面切换数据源

1.功能说明

功能说明:运行中的程序,根据前端定义的ip和databaseName,不重启切换数据源,切换后调用其他接口不会再切回原来的数据源,永久切换。

2.application.yml 定义数据源和druid配置

注:启动类加上@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    dynamic:
      primary: db1
      datasource:
        db1:
          url: jdbc:sqlserver://localhost:1433;databaseName=aaa;useAffectedRows=true
          username: admin
          password: 123456
          driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
        db2:
          url: jdbc:sqlserver://192.168.2.01:1433;databaseName=bbb;useAffectedRows=true
          username: admin
          password: 123456
          driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
        db3:
          url: jdbc:sqlserver://192.168.2.02:1433;databaseName=ccc;useAffectedRows=true
          username: admin
          password: 123456
          driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    druid:
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        login-username: admin
        login-password: 1234567
#       初始化大小,最小,最大
      initialSize: 5
      minIdle: 10
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      #用来检测连接是否有效的sql 必须是一个查询语句
      #mysql中为 select 'x'
      #oracle中为 select 1 from dual
      validationQuery: select 1
      testWhileIdle: true
      #申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      testOnBorrow: false
      #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      testOnReturn: false
      #是否缓存preparedStatement,mysql5.5+建议开启
      poolPreparedStatements: true
      #当值大于0时poolPreparedStatements会自动修改为true
      maxPoolPreparedStatementPerConnectionSize: 20
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
#      filters: stat,wall,log4j
      filter:
        stat:
          enabled: true
          log-slow-sql: true
          slow-sql-millis: 2000
          db-type: sqlserver
        #开启log4jFilter
        log4j2:
          enabled: true
          data-source-log-enabled: false
          connection-log-enabled: false
          statement-log-enabled: false
          result-set-log-enabled: false
        #开启WallFilter
        wall:
          enabled: true
          db-type: sqlserver
          log-violation: true
          throw-exception: false
          config:
            delete-where-none-check: true
      #通过connectProperties属性来打开mergeSql功能;慢SQL记录
	  #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      web-stat-filter:
        # 添加过滤规则
        url-pattern: /*
        # 忽略过滤格式
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"

3.DataSourceConfig 定义默认数据源

@Slf4j
@Configuration
public class DataSourceConfig {
    @Value("${spring.datasource.dynamic.datasource.db1.url}")
    private String url;
    @Value("${spring.datasource.dynamic.datasource.db1.username}")
    private String username;
    @Value("${spring.datasource.dynamic.datasource.db1.password}")
    private String password;
    @Value("${spring.datasource.dynamic.datasource.db1.driver-class-name}")
    private String driverClassName;

    @Bean
    public DataSource defaultDataSource() {
        DataSourceBuilder dsb = DataSourceBuilder.create();
        dsb.url(this.url);
        dsb.username(this.username);
        dsb.password(this.password);
        dsb.driverClassName(this.driverClassName);
        return dsb.build();
    }

    @Bean
    @Primary
    @DependsOn({"springUtils", "defaultDataSource"})
    public DynamicDataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(DynamicDataSource.dataSourcesMap);
        return dynamicDataSource;
    }
}

4.DynamicDataSource 定义数据源切换和重置方法

@Slf4j
@AutoConfigureAfter(DataSourceConfig.class)
public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> dataSourceKey = ThreadLocal.withInitial(() -> "defaultDataSource");

    public static Map<Object, Object> dataSourcesMap = new ConcurrentHashMap<>(10);

    static {
        dataSourcesMap.put("defaultDataSource", SpringUtils.getBean("defaultDataSource"));
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSource.dataSourceKey.get();
    }

    public static void setDataSource(String dataSource) {
        DynamicDataSource.dataSourceKey.set(dataSource);
        DynamicDataSource dynamicDataSource = (DynamicDataSource) SpringUtils.getBean("dataSource");
        dynamicDataSource.afterPropertiesSet();
    }

    public static String getDataSource() {
        return DynamicDataSource.dataSourceKey.get();
    }

    public static void clear() {
        DynamicDataSource.dataSourceKey.remove();
    }

}

5.BaseServiceImpl 实现数据源切换和重置默认数据源

    @Value("${spring.datasource.dynamic.datasource.db1.url}")
    private String url;
    @Value("${spring.datasource.dynamic.datasource.db1.username}")
    private String username;
    @Value("${spring.datasource.dynamic.datasource.db1.password}")
    private String password;
    @Value("${spring.datasource.dynamic.datasource.db1.driver-class-name}")
    private String driverClassName;
    
@Override
    public String changeDataSource(String ip, String dataBaseName) {
        if(!this.pingIp(ip)){
            return "ip地址连接失败!";
        }
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl("jdbc:sqlserver://"+ip+":1433;databaseName="+dataBaseName+";useAffectedRows=true");
        druidDataSource.setUsername(this.username);
        druidDataSource.setPassword(this.password);
        druidDataSource.setDriverClassName(this.driverClassName);
        druidDataSource.setRemoveAbandoned(false);//true是否自动回收超时连接
        // druidDataSource.setRemoveAbandonedTimeout(600);//超时时间(以秒数为单位)
        druidDataSource.setLogAbandoned(true);//是否在自动回收超时连接的时候打印连接的超时错误
        druidDataSource.setBreakAfterAcquireFailure(true);//true关闭重连
        druidDataSource.setTimeBetweenConnectErrorMillis(60);//设置多久重试
        druidDataSource.setConnectionErrorRetryAttempts(0);//重连次数,关闭重连需设为0
        druidDataSource.setMaxWait(3000);// 超时等待时间以毫秒为单位
        DynamicDataSource.dataSourcesMap.put("defaultDataSource", druidDataSource);
        DynamicDataSource.setDataSource("defaultDataSource");//切换数据源
        try {
            DruidPooledConnection dpc = druidDataSource.getConnection();//关键!尝试连接,否则无法捕获druid报错
            if(dpc!=null){
                return "success";
            }
        } catch (SQLException e) {
            log.error(e.getMessage());
            this.resetDefaultDataBase();//连接失败后,重置默认数据源
        }
        return "数据库名称错误!";
    }
    @Override
    public Boolean resetDefaultDataBase() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(this.url);
        druidDataSource.setUsername(this.username);
        druidDataSource.setPassword(this.password);
        druidDataSource.setDriverClassName(this.driverClassName);
        druidDataSource.setRemoveAbandoned(false);
        druidDataSource.setLogAbandoned(true);
        druidDataSource.setBreakAfterAcquireFailure(true);
        druidDataSource.setTimeBetweenConnectErrorMillis(60);
        druidDataSource.setConnectionErrorRetryAttempts(0);
        druidDataSource.setMaxWait(3000);
        DynamicDataSource.dataSourcesMap.put("defaultDataSource", druidDataSource);
        DynamicDataSource.setDataSource("defaultDataSource");//切换数据源
        try {
            DruidPooledConnection dpc = druidDataSource.getConnection();
            if(dpc!=null){
                return true;
            }
        } catch (SQLException e) {
        ...
        }
        return false;
    }

水平有限,有错误之处或者有好的方法欢迎指点!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值