Springboot + DruidDataSource 实现不重启项目加载修改后的数据源

背景

由于公司项目需求,研究了一下如何在项目不重启的情况下,修改了数据源配置后能正常使用新的数据源配置。在百度查找解决方案时找到了动态修改数据源和动态刷新配置两种方式,但是都不适合我的实际场景。百度找不到解决方案,自己尝试解析springboot自动加载配置数据源的源码,依然没有找到。最后无意间看DruidDataSource 的源码时发现里面有个restart方法就可以实现,只需要监听到配置有改动过,刷新数据源配置,执行restart方法即可实现不重启项目加载最新的数据源配置,闲话说到这,接下来实际操作。

Spring动态修改数据源和动态刷新配置

  1. 使用spring 动态修改数据源需要继承AbstractRoutingDataSource类,实现determineCurrentLookupKey方法就能动态的修改数据源,具体的实现流程感兴趣的朋友可以自行去了解下
  2. 不重启项目动态刷新配置我们需要spring-boot-starter-actuator提供刷新接口,spring-cloud-starter-config提供动态监测注解,具体实现流程可以参考链接:https://www.jianshu.com/p/230af40377cf
    由于公司项目使用的是Apollo配置中心,已经提供了监听配置修改的方式

引入maven依赖

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>io.shardingsphere</groupId>
			<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
			<version>3.0.0.M3</version>
		</dependency>

		<!-- 阿里的druid 依赖log4j -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.0</version>
		</dependency>

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>

		<!-- 引入mybatis-spring -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.3.1</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
			<version>5.1.46</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

1. 使用springboot自动加载数据源

  1. 配置数据源
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
  1. 定义启动类
@SpringBootApplication
@EnableTransactionManagement
@ComponentScan("com")
@MapperScan("com.example.demo.datasource.mapper")
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}
  1. 由于DruidDataSource实例已经交由spring容器管理,只要把实例获取到就能调用到restart方法。以下是核心代码块,获取到DruidDataSource的实例,修改数据源的配置信息后执行restart方法即可。
DruidDataSource master = SpringUtils.getBean("dataSource");
master.setUrl("jdbc:mysql://192.168.64.145:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false");
master.setUsername("root");
master.setPassword("123456");
master.setDriverClassName("com.mysql.jdbc.Driver");
master.restart();

SpringUtils:

@Component
public class SpringUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }

    public static <T> T getBean(String beanName) {
        if(applicationContext.containsBean(beanName)){
            return (T) applicationContext.getBean(beanName);
        }else{
            return null;
        }
    }

    public static <T> Map<String, T> getBeansOfType(Class<T> baseType){
        return applicationContext.getBeansOfType(baseType);
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}
  1. 验证过程,这里使用mybatis框架操作数据库。本地环境和虚拟机环境各建一个数据库test。新建一个TestController开放两个接口,第一个接口获取数据里的数据-query,第二个接口用来动态修改数据源信息-change。项目启动后,调用query接口查询数据,成功后调用change接口修改数据源,再次调用query接口查询数据,是否查到不一样的数据。
@RestController
public class TestController {

    @Resource
    private UserMapper userMapper;

    @GetMapping("/query")
    public String query() {
        return userMapper.selectAll().get(0).getName();
    }

    @GetMapping("/changeSource")
    public String changeSource() {
        try {
            DruidDataSource master = SpringUtils.getBean("master");
            master.setUrl("jdbc:mysql://192.168.64.145:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false");
            master.setUsername("root");
            master.setPassword("123456");
            master.setDriverClassName("com.mysql.jdbc.Driver");
            master.restart();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return "success";
    }
}

2. 自定义加载数据源配置信息

  1. 数据源配置信息
master.db.driverClassName=com.mysql.jdbc.Driver
master.db.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
master.db.username=root
master.db.password=
  1. Java代码方式加载配置
@Configuration
public class MasterDataSourceConfig {

    @Value("${master.db.url}")
    private String url;
    @Value("${master.db.username}")
    private String username;
    @Value("${master.db.password}")
    private String password;
    @Value("${master.db.driverClassName}")
    private String driverClassName;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }
}
@Configuration
public class DataSourceComponent {

    @Resource
    private MasterDataSourceConfig masterDataSourceConfig;

    @Bean(name = "master")
    public DataSource masterDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(masterDataSourceConfig.getUrl());
        dataSource.setUsername(masterDataSourceConfig.getUsername());
        dataSource.setPassword(masterDataSourceConfig.getPassword());
        dataSource.setDriverClassName(masterDataSourceConfig.getDriverClassName());
        return dataSource;
    }

    @Primary//不加这个会报错。
    @DependsOn({ "master"}) //解决数据库循环依赖问题
    @Bean(name = "multiDataSource")
    public MultiRouteDataSource exampleRouteDataSource() {
        MultiRouteDataSource multiDataSource = new MultiRouteDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource());
        multiDataSource.setTargetDataSources(targetDataSources);
        multiDataSource.setDefaultTargetDataSource(masterDataSource());
        return multiDataSource;
    }

    /**
     * 注册ServletRegistrationBean
     *
     * @return
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean reg = new ServletRegistrationBean();
        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("allow", ""); // 白名单 return reg;
        reg.addInitParameter("loginUsername", "root");
        reg.addInitParameter("loginPassword", "123456");
        reg.addInitParameter("resetEnable", "false");
        return reg;
    }

    /**
     * 注册FilterRegistrationBean
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
        filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
        filterRegistrationBean.addInitParameter("DruidWebStatFilter", "/*");
        return filterRegistrationBean;
    }
  1. 由于DruidDataSource实例已经交由spring容器管理,只要把实例获取到就能调用到restart方法。以下是核心代码块,获取到DruidDataSource的实例,修改数据源的配置信息后执行restart方法即可。
// 注意这里的bean名称要和自定义的名称一致,否则无法找到对应的实例
DruidDataSource master = SpringUtils.getBean("master");
master.setUrl("jdbc:mysql://192.168.64.145:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false");
master.setUsername("root");
master.setPassword("123456");
master.setDriverClassName("com.mysql.jdbc.Driver");
master.restart();
  1. 定义启动类
// 加上exclude = {DataSourceAutoConfiguration.class} 关闭自动加载数据源
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableTransactionManagement
@ComponentScan("com")
@MapperScan("com.example.demo.datasource.mapper")
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}
  1. 验证方式参考第一种方法

写到最后,不知道有没有遗漏,如哪里写的不通或者不对,请大神们指正并多包涵

你可能感兴趣的:(DruidDataSource)