数据源切换(整个切换)通过 配置页面切换整个项目的数据源(springboot +mybatisplus+druid)

背景

在正常项目中数据源是可以配置多个数据源使用切面(aop+注解)实现使用时的切换即当前线程中切换。还有就是在配置文件配置多个数据源。我这里想要实现的时同一个数据库 不同类型的切换比如 mysql 和sql server之间的切换、

导入需要的依赖

       
    <dependency>
      <groupId>org.yamlgroupId>
      <artifactId>snakeyamlartifactId>
      <version>1.23version>
    dependency>
    
		<dependency>
		    <groupId>com.baomidougroupId>
		    <artifactId>mybatis-plus-boot-starterartifactId>
		    <version>3.1.2version>
		dependency>
		  
		<dependency>
		    <groupId>com.alibabagroupId>
		    <artifactId>druid-spring-boot-starterartifactId>
		    <version>1.1.18version>
		dependency>

其他springboot的依赖我就不贴了。。

yml文件

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/库?characterEncoding=utf8&useSSL=false&serverTimezone=Hongkong&allowPublicKeyRetrieval=true
    username: root
    password: '123456'
    driverClassName: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
  mvc:
    favicon:
      enabled: false
    view:
      prefix: /WEB-INF/jsp/
      suffix: .jsp
    static-path-pattern: /*
  devtools:
    restart:
      enabled: true
      additional-paths: main/java
      exclude: WEB-INF/*
mybatis-plus:
  type-aliases-package: 实体类包路径 com/自己的/mapper/
  mapper-locations: classpath*:com/自己的/mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql
logging:
  file: C:\\springboot\\info.log
server:
  port: 18080

数据源配置类

@Configuration
@EnableTransactionManagement
public class DruidDBConfig {
    private final Logger log = LoggerFactory.getLogger(getClass());

    // adi数据库连接信息
    @Value("${spring.datasource.url}")
    private String dbUrl;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;
    @Value("${spring.datasource.driverClassName}")
    private String driverClassName;
    // 连接池连接信息
    @Value("${spring.datasource.initialSize}")
    private int initialSize;
    @Value("${spring.datasource.minIdle}")
    private int minIdle;
    @Value("${spring.datasource.maxActive}")
    private int maxActive;
    @Value("${spring.datasource.maxWait}")
    private int maxWait;


    @Bean // 声明其为Bean实例
    @Primary // 在同样的DataSource中,首先使用被标注的DataSource
    @Qualifier("adiDataSource")
    public DataSource dataSource() throws SQLException {
        DruidDataSource datasource = new DruidDataSource();
        // 基础连接信息
        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);
        // 连接池连接信息
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
        datasource.setMaxPoolPreparedStatementPerConnectionSize(50);
        datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
        datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
        datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
        String validationQuery = "select 1 from dual";
        datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
        datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
        datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
        datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
        datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
        datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
        datasource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
        return datasource;
    }

    @Bean(name = "dynamicDataSource")
    @Qualifier("dynamicDataSource")
    public DynamicDataSource dynamicDataSource() throws SQLException {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDebug(false);
        //配置缺省的数据源
        dynamicDataSource.setDefaultTargetDataSource(dataSource());
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        targetDataSources.put("adiDataSource", dataSource());
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }

    /**
     * SqlSessionFactorybean  整合必须替换MybatisSqlSessionFactoryBean 重点
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
    	MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:*.xml"));
        sqlSessionFactoryBean.setTypeAliasesPackage("实体类包名路径");
        //导入mybatis配置
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactoryBean.setConfiguration(configuration);
        return sqlSessionFactoryBean.getObject();
    }
    
    
    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

数据源路由 AbstractRoutingDataSource

public class DynamicDataSource extends AbstractRoutingDataSource{
    private boolean debug = true;
    private final Logger log = LoggerFactory.getLogger(getClass());
    private Map<Object, Object> dynamicTargetDataSources;
    private Object dynamicDefaultTargetDataSource;


    /**
     * 这个方法会去检查数据源适用 aop的话这里是用不到的
     * 因为使用的 是修改mybatis的数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
//        String datasource = DBContextHolder.getDataSource();
//        System.err.println(datasource);
//        if (!StringUtils.isEmpty(datasource)) {
//            Map dynamicTargetDataSources2 = this.dynamicTargetDataSources;
//            if (dynamicTargetDataSources2.containsKey(datasource)) {
//                log.info("---当前数据源:" + datasource + "---");
//            } else {
//                log.info("不存在的数据源:");
//                return null;
                    throw new ADIException("不存在的数据源:"+datasource,500);
//            }
//        } else {
//            log.info("---当前数据源:默认数据源---");
//        }

        return "";
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {

        super.setTargetDataSources(targetDataSources);

        this.dynamicTargetDataSources = targetDataSources;

    }


    // 创建数据源
    public boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype) {
        try {
            try { // 排除连接不上的错误
                Class.forName(driveClass);
                DriverManager.getConnection(url, username, password);// 相当于连接数据库
            } catch (Exception e) {
                return false;
            }
            DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.setName(key);
            druidDataSource.setDriverClassName(driveClass);
            druidDataSource.setUrl(url);
            druidDataSource.setUsername(username);
            druidDataSource.setPassword(password);
            druidDataSource.setInitialSize(50); //初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
            druidDataSource.setMaxActive(200); //最大连接池数量
            druidDataSource.setMaxWait(60000); //获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象
            druidDataSource.setMinIdle(40); //最小连接池数量
            String validationQuery = "select 1 from dual";
            druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
            druidDataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
            druidDataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
            druidDataSource.setFilters("stat");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
            druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
            druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
            druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
            druidDataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
            druidDataSource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
            druidDataSource.init();
//            Map dynamicTargetDataSources2 = this.dynamicTargetDataSources;
//            dynamicTargetDataSources2.put(key, createDataSource);// 加入map
            this.dynamicTargetDataSources.put(key, druidDataSource);
            setTargetDataSources(this.dynamicTargetDataSources);// 将map赋值给父类的TargetDataSources
//            setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
            super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
            log.info(key+"数据源初始化成功");
            //log.info(key+"数据源的概况:"+druidDataSource.dump());
            
            /**
            ** 修改mybatis的数据源
             */
            //修改MyBatis的数据源
              SqlSessionFactory SqlSessionFactory = (SqlSessionFactory) SpringContextUtils.getBean(SqlSessionFactory.class);
            Environment environment =SqlSessionFactory.getConfiguration().getEnvironment();
            Field dataSourceField = environment.getClass().getDeclaredField("dataSource");
            dataSourceField.setAccessible(true);//跳过检查
            dataSourceField.set(environment,druidDataSource);//修改mybatis的数据源
            //修改完成后所有线程使用此数据源
            return true;
        } catch (Exception e) {
            log.error(e + "");
            return false;
        }
    }
    // 删除数据源
    public boolean delDatasources(String datasourceid) {
        Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
        if (dynamicTargetDataSources2.containsKey(datasourceid)) {
            Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
            for (DruidDataSource l : druidDataSourceInstances) {
                if (datasourceid.equals(l.getName())) {
                    dynamicTargetDataSources2.remove(datasourceid);
                    DruidDataSourceStatManager.removeDataSource(l);
                    setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
                    super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
                    return true;
                }
            }
            return false;
        } else {
            return false;
        }
    }

    // 测试数据源连接是否有效
    public boolean testDatasource(String key, String driveClass, String url, String username, String password) {
        try {
            Class.forName(driveClass);
            DriverManager.getConnection(url, username, password);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Specify the default target DataSource, if any.
     * 

* The mapped value can either be a corresponding * {@link javax.sql.DataSource} instance or a data source name String (to be * resolved via a {@link #setDataSourceLookup DataSourceLookup}). *

* This DataSource will be used as target if none of the keyed * {@link #setTargetDataSources targetDataSources} match the * {@link #determineCurrentLookupKey()} current lookup key. */ @Override public void setDefaultTargetDataSource(Object defaultTargetDataSource) { super.setDefaultTargetDataSource(defaultTargetDataSource); this.dynamicDefaultTargetDataSource = defaultTargetDataSource; } /** * @param debug * the debug to set */ public void setDebug(boolean debug) { this.debug = debug; } /** * @return the debug */ public boolean isDebug() { return debug; } /** * @return the dynamicTargetDataSources */ public Map<Object, Object> getDynamicTargetDataSources() { return dynamicTargetDataSources; } /** * @param dynamicTargetDataSources * the dynamicTargetDataSources to set */ public void setDynamicTargetDataSources(Map<Object, Object> dynamicTargetDataSources) { this.dynamicTargetDataSources = dynamicTargetDataSources; } /** * @return the dynamicDefaultTargetDataSource */ public Object getDynamicDefaultTargetDataSource() { return dynamicDefaultTargetDataSource; } /** * @param dynamicDefaultTargetDataSource * the dynamicDefaultTargetDataSource to set */ public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) { this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource; } public void createDataSourceWithCheck(com.jksd.model.DataSource dataSource) throws Exception { String datasourceId = dataSource.getDatasourceId(); log.info("准备创建数据源createDataSourceWithCheck"+datasourceId); Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources; //判断数据源已经创建这里还是aop会用到的 if (dynamicTargetDataSources2.containsKey(datasourceId)) { log.info("数据源"+datasourceId+"之前已经创建,准备测试数据源是否正常..."); //DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId); DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSources2.get(datasourceId); boolean rightFlag = true; Connection connection = null; try { log.info(datasourceId+"数据源的概况->当前闲置连接数:"+druidDataSource.getPoolingCount()); long activeCount = druidDataSource.getActiveCount(); log.info(datasourceId+"数据源的概况->当前活动连接数:"+activeCount); if(activeCount > 0) { log.info(datasourceId+"数据源的概况->活跃连接堆栈信息:"+druidDataSource.getActiveConnectionStackTrace()); } log.info("准备获取数据库连接..."); connection = druidDataSource.getConnection(); log.info("数据源"+datasourceId+"正常"); } catch (Exception e) { log.error(e.getMessage(),e); //把异常信息打印到日志文件 rightFlag = false; log.info("缓存数据源"+datasourceId+"已失效,准备删除..."); if(delDatasources(datasourceId)) { log.info("缓存数据源删除成功"); } else { log.info("缓存数据源删除失败"); } } finally { if(null != connection) { connection.close(); } } if(rightFlag) { log.info("不需要重新创建数据源"); return; } else { log.info("准备重新创建数据源..."); createDataSource(dataSource); log.info("重新创建数据源完成"); } } else { //去创建数据源 createDataSource(dataSource); } } private void createDataSource(com.jksd.model.DataSource dataSource) throws Exception { String datasourceId = dataSource.getDatasourceId(); log.info("准备创建数据源createDataSource***"+datasourceId); String databasetype = dataSource.getDatabasetype(); String username = dataSource.getUserName(); String password = dataSource.getPassWord(); String url = dataSource.getUrl(); String driveClass = dataSource.getDrverclass(); if(testDatasource(datasourceId,driveClass,url,username,password)) { boolean result = this.createDataSource(datasourceId, driveClass, url, username, password, databasetype); if(!result) { log.error("数据源"+datasourceId+"配置正确,但是创建失败"); } } else { log.error("数据源配置有错误"); } }

建一个实体类

@Data
@TableName(value = "tbl_account")
public class DataSources {
	@TableId(value = "datasourceId", type = IdType.AUTO)
	private String datasourceId;
	 @TableField(value = "url")
	private    String url;
	 @TableField(value = "userName")
	private    String userName;
	 @TableField(value = "passWord")
	private    String passWord;
	 @TableField(value = "code")
	private    String code;
	 @TableField(value = "drverclass")
	private   String drverclass;
	 @TableField(value = "databasetype")
		private   String databasetype;

}

controller调用

@RequestMapping("updatedatasours")
	@ResponseBody
	public Map<String,Object> updatedatasours(String type,String ip,String account ,String pwd) throws SQLException{

DataSources d = new DataSources();
		
		if(type.equals("mysql")) {
			d.setDatabasetype(type);
			d.setDrverclass("com.mysql.cj.jdbc.Driver");
			d.setUserName(account);
	        d.setPassWord(pwd);
	        d.setUrl("jdbc:mysql://"+ip+":3306/jksd?characterEncoding=utf8&useSSL=false&serverTimezone=Hongkong&allowPublicKeyRetrieval=true");
	        d.setDatasourceId("dasours1");
		}else if(type.equals("MS SQL Server")){
			d.setDatabasetype("sqlserver");
			d.setDrverclass("com.microsoft.sqlserver.jdbc.SQLServerDriver");
			d.setUserName(account);
	        d.setPassWord(pwd);
	        d.setUrl("jdbc:sqlserver://"+ip+":1433;DatabaseName=jksd");
	        d.setDatasourceId("dasours2");
		}
        try {
			dynamicDataSource.createDataSourceWithCheck(d);
			//同时去修改配置文件实现下次启动自动加载切换的数据源
			  YmlUtil.updateYamlFile(d);
			//DBContextHolder.setDataSource(d.getDatasourceId());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.err.println(e.toString());
		}

}

yml工具类

public class YmlUpdateUtil {
	
	
    public static void updateYamlFile(DataSources data) {
        String src = "src/main/resources/application.yml";
        Yaml yaml = new Yaml();
        FileWriter fileWriter = null;
        //层级map变量
        Map<String, Object> springMap, dataSourceMap, resultMap,helperDialect;
        try {
        	//读取yaml文件,默认返回根目录结构
            resultMap = (Map<String, Object>) yaml.load(new FileInputStream(new File(src)));
            //get出spring节点数据
            springMap = (Map<String, Object>) resultMap.get("spring");
            //get出数据库节点数据
            dataSourceMap = (Map<String, Object>) springMap.get("datasource");
            //修改数据库url,我这个是封装的参数,你们测试可以写死一个值尝试修改即可
            dataSourceMap.put("url", data.getUrl());
            //登录名
            dataSourceMap.put("username",data.getUserName());
            //驱动
            dataSourceMap.put("driverClassName", data.getDrverclass());
            //密码
            dataSourceMap.put("password", data.getPassWord());
            helperDialect = (Map<String, Object>) resultMap.get("pagehelper");
            //分页插件配置也需要修改
            helperDialect.put("helperDialect",data.getDatabasetype());
            //字符输出
            fileWriter = new FileWriter(new File(src));
            //用yaml方法把map结构格式化为yaml文件结构
            fileWriter.write(yaml.dumpAsMap(resultMap));
            //刷新
            fileWriter.flush();
            //关闭流
            fileWriter.close();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("对不起,yaml文件修改失败!");
        }
    }

实现页面配置连接信息进行数据库的切换
yml工具类是为了实现一次切换后面项目 启动时去加载切换过来的数据源。。。
页面就不贴了 记录下学习感兴趣可以去试试。。。

你可能感兴趣的:(小柠檬的代码人生)