一、工程配置
1. 依赖导入
在工程中引入 jdbc-starter
与 spring-data
依赖,内容如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
2. 文件配置
在工程 YML
配置文件中添加多个数据源信息,这里我以两个为例。
spring:
datasource1:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
datasource2:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
二、对象管理
1. 常量定义
为了提高程序的可维护性,这里将后续涉及的常量统一外置定义。
public class DatasourceConst {
public static final String DS_MASTER = "datasource1";
public static final String DS_SLAVE = "datasource2";
public static final String TM_MASTER = "transactionManager1";
public static final String TM_SLAVE = "transactionManager2";
}
2. Bean注入
通过 @Bean
将配置文件中的多个数据源信息注入 Spring
容器,其中 @Primary
标识数据源为默认源。
@Configuration
public class DatasourceConfig {
@Bean(name = DatasourceConst.DS_MASTER)
@ConfigurationProperties("spring.datasource1")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
@Bean(name = DatasourceConst.DS_SLAVE)
@ConfigurationProperties("spring.datasource2")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource dynamicDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DatasourceConst.DS_MASTER, dataSource1());
dataSourceMap.put(DatasourceConst.DS_SLAVE, dataSource2());
// 设置动态数据源
DynamicDataSource dataSources = new DynamicDataSource();
dataSources.setTargetDataSources(dataSourceMap);
dataSources.setDefaultTargetDataSource(dataSource1());
return dataSources;
}
}
3. 事务配置
同理,当配置多个数据源时若需要使用事务需要将对应的事务管理注入 Spring
容器。
@Configuration
public class TransactionConfig {
@Bean(name = DatasourceConst.TM_MASTER)
@Autowired
@Primary
DataSourceTransactionManager transactionManager1(@Qualifier(DatasourceConst.DS_MASTER) DataSource datasource) {
return new DataSourceTransactionManager(datasource);
}
@Bean(name = DatasourceConst.TM_SLAVE)
@Autowired
DataSourceTransactionManager transactionManager2(@Qualifier(DatasourceConst.DS_SLAVE) DataSource datasource) {
return new DataSourceTransactionManager(datasource);
}
@Bean(name = DatasourceConst.TM_CHAIN)
public ChainedTransactionManager chainedTransactionManager(@Qualifier(DatasourceConst.TM_MASTER) DataSourceTransactionManager tm1,
@Qualifier(DatasourceConst.TM_SLAVE) DataSourceTransactionManager tm2) {
return new ChainedTransactionManager(tm1, tm2);
}
}
三、数据切换
1. 管理工具
新建 DynamicDataSourceHolder
用于标识管理当前激活的数据源。
基于 Web
请求的特性,每个请求线程都可能使用到不同的数据源,因此选择 ThreadLocal
变量存储。
public class DynamicDataSourceHolder {
/**
* 动态数据源名称上下文
*/
private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();
/**
* 获取数据源名称
*/
public static String getContextKey() {
String key = DATASOURCE_HOLDER.get();
return key == null ? DatasourceConst.DS_MASTER : key;
}
/**
* 设置/切换数据源
*/
public static void setContextKey(String key) {
DATASOURCE_HOLDER.set(key);
}
/**
* 删除当前数据源名称
*/
public static void remove() {
DATASOURCE_HOLDER.remove();
}
}
2. 切换配置
新建类继承于 AbstractRoutingDataSource
,当程序在获取数据源连接池时将会执行 determineCurrentLookupKey()
方法。
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* Through the key(bean name) to get which datasource to use.
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getContextKey();
}
}
3. 演示示例
在应用中切换数据源时即可通过 DynamicDataSourceHolder
工具类快速切换,为了更直观显示效果,建议多个数据源选择不同的库执行查询并拼接。
@RestController
@RequestMapping("/api/sysUser")
public class SysUserController {
@Resource
private SysUserService sysUserService;
@GetMapping("list")
public ResponseEntity<Object> list() {
List<SysUser> list1;
try {
// Query data with default datasource
list1 = this.sysUserService.list();
// Change datasource to slave node
DynamicDataSourceHolder.setContextKey(DatasourceConst.DS_SLAVE);
List<SysUser> list2 = this.sysUserService.list();
// Merge data
list1.addAll(list2);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
DynamicDataSourceHolder.remove();
}
return ResponseEntity.ok(list1);
}
}
参考文章