本文分享自华为云社区《springboot动态切换数据源》,作者:小陈没烦恼 。
前言
在公司的系统里,由于数据量较大,所以配置了多个数据源,它会根据用户所在的地区去觉得查询那个一个数据库。这样就产生了动态切换数据源的场景。
今天,就模拟一下在主库查询订单信息查询不到的时候,切换数据源去历史库里面查询。
实现效果
首先我们设置查询的数据库为 db1,可以看到通过订单号没有查到订单信息,然后我们重置数据源,重新设置为 db2,同样的订单号就可以查询到信息。
数据库准备
新建两个数据库 db1 和 db2,db1 作为主库,db2 作为历史库
两个库中都有一个订单表biz_order
,主库中没有数据,历史库中有我们要查询的数据。
代码编写
1.新建一个 springboot 项目,引入所需依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--引入druid-替换默认数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.15</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
复制代码
2.application.yaml 配置数据库信息
这里我们配置两个数据库的信息
spring:
datasource:
db1:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/db1?characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
db2:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/db2?characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
mapper-locations: classpath:mapper/*.xml
复制代码
3.创建数据源对象,并注入 spring 容器中
新建DynamicDataSourceConfig.java
文件,在该配置文件中读取 yaml 配置的数据源信息,并且通过该信息构造数据源对象,然后通过 @Bean 注解注入到 spring 容器中。
package com.it1997.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
public class DynamicDataSourceConfig {
@Bean("dataSource1")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource oneDruidDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean("dataSource2")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource twoDruidDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager1(@Qualifier("dataSource1") DataSource dataSource1) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource1);
return dataSourceTransactionManager;
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager2(@Qualifier("dataSource2") DataSource dataSource2) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource2);
return dataSourceTransactionManager;
}
}
复制代码
4.数据源配置上下文信息
新建DynamicDataSourceHolder.java
文件,该文件通过 ThreadLocal,实现为每一个线程创建一个保存数据源配置的上下文。并且提供setDataSource
和getDataSource
静态方法来设置和获取数据源的名称。
package com.it1997.config;
public class DynamicDataSourceHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
复制代码
5.重写数据源配置类
新建 DynamicDataSource.java 文件,该类继承 AbstractRoutingDataSource 类,重写父类determineCurrentLookupKey
和afterPropertiesSet
方法。这里我们重写父类中afterPropertiesSet
方法(为什么要重写在这个方法,可以看文章最后对于 druid 的源码的讲解),在这个方法里我们将 spring 容器中的所有的数据源,都给放到 map 里,然后后续我们根据 map 中的 key 来获取不同的数据源,super.afterPropertiesSet();
通过这个方法设置上数据源在类上加上@Primary
注解,让 spring 容器优先使用我们自定义的数据源,否则还是会使用默认的数据源配置。
package com.it1997.config;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
@Resource
DataSource dataSource1;
@Resource
DataSource dataSource2;
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSource();
}
@Override
public void afterPropertiesSet() {
// 初始化所有数据源
Map<Object, Object> targetDataSource = new HashMap<>();
targetDataSource.put("db1", dataSource1);
targetDataSource.put("db2", dataSource2);
super.setTargetDataSources(targetDataSource);
super.setDefaultTargetDataSource(dataSource1);
super.afterPropertiesSet();
}
}
复制代码
druid 数据源配置解读
点开我们刚刚继承的AbstractRoutingDataSource
抽象类,可以看到它又继承了
AbstractDataSource
实现了InitializingBean
接口。
然后我们在看一下 druid 的数据源配置是怎么实现的,点开DruidDataSourceWrapper
类,可以看到它也是继承了AbstractDataSource
实现了InitializingBean
接口。并且,读取的是 yaml 文件中spring.datasource.druid
下面配置的数据库连接信息。
而我们自定的一的数据源读取的是spring.datasource.db1
下面配置的数据库连接信息。
druid 的数据源配置,实现了接口中afterPropertiesSet
,在这个方法中设置了数据库的基本信息,例如,数据库连接地址、用户名、密码以及数据库连接驱动信息。
点击关注,第一时间了解华为云新鲜技术~
评论