Spring多数据源动态切换
原理
java提供了一个接口 java.sql.DataSource 用来获取数据库连接 getConnnection(),动态多数据源就是在该接口基础上实现的。
Spring中 AbstractRoutingDataSource 实现了该 DataSource 接口
@Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } ... /** * 根据determineCurrentLookupKey()决定使用哪个数据源,如果为空,则使用默认数据源 */ protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; } /** * 抽象方法,返回数据源map的key,用来决定具体使用哪个数据源 */ @Nullable protected abstract Object determineCurrentLookupKey();
抽象方法 determineCurrentLookupKey()
是暴露给开发者的,我们可以通过实现该方法在不同数据源之间切换。
SpringBoot实践
1. 配置多数据源
在 application.yml 如下配置
spring: datasource: # 数据源类型 type: com.alibaba.druid.pool.DruidDataSource # 默认数据源 default-datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db0?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8 username: root password: 123456 # 多数据源 target-datasources: datasource1: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8 username: root password: 123456 datasource2: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8 username: root password: 123456 # druid 默认配置 druid: # 初始连接数 initial-size: 10 # 最大连接池数量 max-active: 100 # 最小连接池数量 min-idle: 10 # 配置获取连接等待超时的时间 max-wait: 60000 # 打开PSCache,并且指定每个连接上PSCache的大小 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: false stat-view-servlet: enabled: true url-pattern: /monitor/druid/* filter: stat: log-slow-sql: true slow-sql-millis: 1000 merge-sql: false wall: config: multi-statement-allow: true # MyBatis mybatis: # 搜索指定包别名 typeAliasesPackage: com.guide # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml
此处配置的名称(如 defaultDataSource、targetDataSources)的命名并无特殊要求,只要和下面第3步的 DataSourceConfig 中对应起来就可以
使用 Druid 数据源的话,要在 pom.xml 中引入依赖
<!--阿里数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency>
2. 实现数据源
DynamicDataSource 动态数据源,在多个数据源之间切换
public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceType(); } }
DataSourceContextHolder 数据源上下文,使用线程变量来存储代表当前使用的数据源的key值(每个key值都对应一个数据源,用以区分多数据源)
public class DataSourceContextHolder { public static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDataSourceType(String dsType) { CONTEXT_HOLDER.set(dsType); } public static String getDataSourceType() { return CONTEXT_HOLDER.get(); } public static void removeDataSourceType() { CONTEXT_HOLDER.remove(); } }
DataSourceType 数据源对应的key(其实单纯的用字符串来表示数据源,替换枚举类DataSourceType也是可以的,但是写代码时要注意字符串统一)
public enum DataSourceType { /** 默认数据源key */ DEFAULT_DATASOURCE, /** 数据源1key*/ DATASOURCE1, /** 数据源2key*/ DATASOURCE2; }
3. 将数据源添加到 Spring 容器中
@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.default-datasource") public DataSource defaultDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.target-datasources.datasource1") public DataSource dataSource1() { return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.target-datasources.datasource2") public DataSource dataSource2() { return DruidDataSourceBuilder.create().build(); } /** * Spring 容器里实际上有4个数据源bean * 分别是 defaultDataSource、dataSource1、dataSource2、dynamicDataSource * 自动注入dataSource时,spring就会疑惑到底注入哪一个bean * 在dynamicDataSource上加注解@Primary就是为了解决这个问题,加了这个注解就表示优先注入这个bean */ @Bean @Primary public DataSource dynamicDataSource(DataSource defaultDataSource, DataSource dataSource1, DataSource dataSource2) { // 注意:该方法的参数名称要和前面前面三个datasource对象在Spring容器中的bean名称一样 // 或者使用 @Qualifier 指定具体的bean Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.DEFAULT_DATASOURCE.name(), defaultDataSource); targetDataSources.put(DataSourceType.DATASOURCE1.name(), dataSource1); targetDataSources.put(DataSourceType.DATASOURCE2.name(), dataSource2); return new DynamicDataSource(defaultDataSource, targetDataSources); } }
测试
测试代码
为了方便,省略了 Service 层
@RestController @RequestMapping("/test") public class TestController { @Autowired private TestMapper testMapper; @GetMapping public List<Map<String, Object>> test(String dataSourceIndex) { // 根据参数值的不同,切换数据源 if ("1".equals(dataSourceIndex)) { DataSourceContextHolder.setDataSourceType(DataSourceType.DATASOURCE1.name()); } else if ("2".equals(dataSourceIndex)) { DataSourceContextHolder.setDataSourceType(DataSourceType.DATASOURCE2.name()); } List<Map<String, Object>> mapList = testMapper.selectList(); // 清除线程内部变量数据源key DataSourceContextHolder.removeDataSourceType(); return mapList; } }
TestMapper
@Repository public interface TestMapper { /** * 查询列表 * @return */ List<Map<String, Object>> selectList(); }
TestMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.guide.datasource.mapper.TestMapper"> <select id="selectList" resultType="java.util.Map"> SELECT * FROM test </select> </mapper>
启动类
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @MapperScan("com.guide.*.mapper") public class DatasourceApplication { public static void main(String[] args) { SpringApplication.run(DatasourceApplication.class, args); } }
测试数据
别忘了要准备数据哦!
下面SQL语句,创建3个数据库,然后在3个数据库中都创建一张test表,并各自插入不同的数据。
-- 创建数据库 create database db0 character set utf8 collate utf8_general_ci; create database db1 character set utf8 collate utf8_general_ci; create database db2 character set utf8 collate utf8_general_ci; -- 在数据库db1下执行以下SQL use db0; create table test( id int(11) primary key auto_increment, name varchar(20) ) ; insert into test(name) values('张三'); -- 在数据库db1下执行以下SQL use db1; create table test( id int(11) primary key auto_increment, name varchar(20) ) ; insert into test(name) values('李四'); -- 在数据库db2下执行以下SQL use db2; create table test( id int(11) primary key auto_increment, name varchar(20) ) ; insert into test(name) values('王五');
OK,一切准备就绪,启动应用吧!!!
测试结果
访问 http://localhost:8080/test ,此时使用的是默认数据源 defaultDataSource,页面显示:
[{"name":"张三","id":1}]
访问 http://localhost:8080/test?dataSourceIndex=1 ,使用数据源 dataSource1,页面显示:
[{"name":"李四","id":1}]
访问 http://localhost:8080/test?dataSourceIndex=2 ,使用数据源 dataSource2,页面显示:
[{"name":"王五","id":1}]
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律