背景: 实现的项目有一个多数据源的功能需求,我的设想是项目中配置一个数据源的配置库,里面有一张表存数据源的配置,其余的数据源都是根据配置表自动生成的。
实现:
1. 搭建环境
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>FFAPIJson</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 基于Springboot 2.0 及 json 开发 -->
<dependencies>
<!--spring boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.9</version>
</dependency>
<!-- faster-json-->
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
</project>
2. 多数据源配置
1. 实体类
@TableName("mydatasource")
public class FfDataSource {
private Integer id;
@TableField("ip")
private String url;
private String port;
@TableField("ff_schema")
private String ffSchema;
@TableField("driver_name")
private String driverName;
private String username;
private String password;
@TableField("key_name")
private String keyName;
@TableField("is_valid")
private boolean isValid;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private String creator;
private String updator;
//------get set 方法------
}
2. 数据表结构
CREATE TABLE `mydatasource` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'ip 127.0.0.1',
`port` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '端口号 3306',
`ff_schema` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '数据库名',
`username` varchar(255) DEFAULT NULL COMMENT '用户名',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`driver_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '驱动',
`key_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '唯一标识',
`is_valid` tinyint DEFAULT '1' COMMENT '是否有效,默认有效',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`creator` varchar(255) DEFAULT NULL COMMENT '创建人',
`updator` varchar(255) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
3. 创建主数据源
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@Configuration
@MapperScan(basePackages = "com.fivefu.core.mapper")
@Order(5)
public class DynamicDataSourceConfig {
private static String jdbcUrl;
private static String username;
private static String password;
private static String driver;
@Bean(DataSourceConstants.DS_KEY_MASTER)
public static DataSource masterDataSource() {
return DataSourceBuilder.create().url(jdbcUrl).username(username).password(password).driverClassName(driver).build();
}
@Bean
@Primary
public DataSource dynamicDataSource() {
Map<Object, Object> datasourceMap = new HashMap<>(1);
datasourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
return new FFDynamicDS(masterDataSource(), datasourceMap);
}
@Value("${spring.datasource.master.jdbc-url}")
public void setJdbcUrl(String jdbcUrl) {
DynamicDataSourceConfig.jdbcUrl = jdbcUrl;
}
@Value("${spring.datasource.master.username}")
public void setUsername(String username) {
DynamicDataSourceConfig.username = username;
}
@Value("${spring.datasource.master.password}")
public void setPassword(String password) {
DynamicDataSourceConfig.password = password;
}
@Value("${spring.datasource.master.driver-class-name}")
public void setDriver(String driver) {
DynamicDataSourceConfig.driver = driver;
}
}
4. 实现多数据源
多数据源实质上就是一个Map集合,需要哪个数据源的时候从Map中取即可,参考文章中讲的更详细,继承 AbStractRoutingDataSource (抽象路由数据源)即可实现。
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
@Nullable
private Map<Object, Object> targetDataSources;//数据源集合
@Nullable
private Object defaultTargetDataSource; //默认数据源 我们上面初始化的数据源就是设置成默认的
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;
//----- 后面的不看---
}
,上面dynamicDataSource() 调用额的是下面的构造方法,通过@Bean注解加入到Spring环境里,@Order 是为了一个顺序,先加载默认数据眼,从默认数据源里加载其它数据源的配置,获得后加入到Map中。下面开始实现
public class FFDynamicDS extends AbstractRoutingDataSource {
private Map<Object,Object> backupTargetDataSources;
public FFDynamicDS(DataSource defaultDataSource,Map<Object,Object> targetDataSOurce){
backupTargetDataSources = targetDataSOurce;
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(backupTargetDataSources);
super.afterPropertiesSet();
}
// 添加多个数据源
public void addDataSource(List<FfDataSource> ffDataSources){
for(FfDataSource item : ffDataSources){
this.backupTargetDataSources
.put(item.getKeyName(), DataSourceUtil.makeNewDataSource(item));
}
//覆盖原来的多数据源
super.setTargetDataSources(backupTargetDataSources);
//这个相当于数据源的设置
//AbstractRoutingDataSource 是实现了 InitializingBean
//AbstractRoutingDataSource 实例化之后回调用afterPropertiesSet
//通过这个方法 我们覆盖掉的多数据源才会配置
super.afterPropertiesSet();
}
//决定当前数据源的锁 -- 我们用数据源的key
@Override
protected Object determineCurrentLookupKey()
{
return DynamicDataSourceContextHolder.getContextKey();
}
}
//然后就是获得其它数据源了
@Component
@Order(10)
public class DemoInitDataSource implements ApplicationRunner {
@Autowired
FfDataSourceService ffDataSourceService;
public void initDataSource() {
QueryWrapper<FfDataSource> queryWrapper = new QueryWrapper<>();
List<FfDataSource> list = ffDataSourceService.list(queryWrapper.eq("is_valid", 1));
DataSourceUtil.addDataSourceListToDynamic(list);
}
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("开始执行初始化数据源操作");
initDataSource();
}
}
这里Order(10)就是为了比初始化默认数据源慢,不然数据源还没加载好,先请求就会出错。
项目地址 : gitee.com/own\_3\_0/f…