Spring动态创建数据源
Spring内置了一个AbstractRoutingDataSource,可以根据Key找到对应的数据源。那么我们可以把多个数据源存放到Map中,然后根据key去切换数据源。
- 创建一个类继承AbstractRoutingDataSource
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return RoutingDataSourceContext.getDataSourceRoutingKey();
}
}
这里determineCurrentLookupKey返回key,会自动去查找对应的DataSource
- key用一个线程类RoutingDataSourceContext来存取,RoutingDataSourceContext类如下:
public class RoutingDataSourceContext {
static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>();
/**
* 获取主数据库的key
* @return
*/
public static String getMainKey() {
return "fan_main";
}
/**
* 获取数据库key
* @return
*/
public static String getDataSourceRoutingKey() {
String key = threadLocalDataSourceKey.get();
return key == null ? getMainKey() : key;
}
/**
* 设置数据库的key
* @param key
*/
public static void setThreadLocalDataSourceKey(String key) {
threadLocalDataSourceKey.set(key);
}
}
- 创建数据源
private synchronized void createAndSaveDataSource(String currentAccountSuit) {
DruidDataSource dataSource = createDataSource(currentAccountSuit);
log.info("{}数据源创建成功", currentAccountSuit);
dataSources.put(currentAccountSuit, dataSource);
super.setTargetDataSources(dataSources);
afterPropertiesSet();
}
这里根据currentAccountSuit创建DruidDataSource,创建成功之后就存储到一个Map中,然后把数据源设置进去
super.setTargetDataSources(dataSources);
afterPropertiesSet();
这里用的是阿里的Druid,createDataSource方法如下:
/**
* 创建数据源
* @param currentAccountSuit
* @return
*/
private DruidDataSource createDataSource(String currentAccountSuit) {
FanDataSource fanDataSource;
if (currentAccountSuit.equalsIgnoreCase("fan_main")) {
fanDataSource = new FanDataSource();
fanDataSource.setName("fan_main");
fanDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/fan_main?useUnicode=true&allowMultiQueries=true&useSSL=false");
fanDataSource.setUsername("root");
fanDataSource.setPassword("fxl123");
} else {
fanDataSource = getFanDataSource(currentAccountSuit);
}
if (fanDataSource == null) {
throw new InvalidParameterException("账套不存在");
}
return createDruidDataSource(fanDataSource);
}
如果是主账套fan_main,直接读取连接信息,其他账套的话通过getFanDataSource获取,如果获取不到的话,就抛出异常,如果找到数据库连接配置,那么就创DruidDataSource,createDruidDataSource(fanDataSource)方法如下:
/**
* 根据配置创建DruidDataSource
* @param fanDataSource
* @return
*/
public static DruidDataSource createDruidDataSource(FanDataSource fanDataSource) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setName(fanDataSource.getName());
dataSource.setUrl(fanDataSource.getUrl());
dataSource.setUsername(fanDataSource.getUsername());
dataSource.setPassword(fanDataSource.getPassword());
dataSource.setInitialSize(2);
// 从池中取得链接时做健康检查,该做法十分保守
dataSource.setTestOnBorrow(true);
// 如果连接空闲超过1小时就断开
dataSource.setMinEvictableIdleTimeMillis(1 * 60000 * 60);
// 每十分钟验证一下连接
dataSource.setTimeBetweenEvictionRunsMillis(600000);
// 运行ilde链接测试线程,剔除不可用的链接
dataSource.setTestWhileIdle(true);
dataSource.setMaxWait(-1);
return dataSource;
}
在构造方法中初始化默认主库连接, 并且根据主数据库创建JdbcTemplate
public RoutingDataSource() {
log.info("初始化动态数据源");
createAndSaveDataSource(RoutingDataSourceContext.getMainKey());
log.info("创建jdbcTemplate");
DruidDataSource dataSource = getDruidDataSource("fan_main");
jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
}
后面其他库的连接资料通过jdbcTemplate来获取
/**
* 通过jdbc从数据库中查找数据源配置
* @param name
* @return
*/
private FanDataSource getFanDataSource(String name) {
String sql = "select name, url, username, password from fan_datasource where name = ?";
RowMapper<FanDataSource> rowMapper = new BeanPropertyRowMapper<>(FanDataSource.class);
return jdbcTemplate.queryForObject(sql, rowMapper, name);
}
determineCurrentLookupKey最终实现如下,当用户做数据库操作的时候,会获取当前设置的key,如果key不存在对应的数据源,那么就创建,如果存在就直接返回
@Override
protected Object determineCurrentLookupKey() {
String currentAccountSuit = RoutingDataSourceContext.getDataSourceRoutingKey();
if (StringUtils.isEmpty(currentAccountSuit)) {
currentAccountSuit = RoutingDataSourceContext.getMainKey();
}
log.info("当前操作账套:{}", currentAccountSuit);
if (!dataSources.containsKey(currentAccountSuit)){
log.info("{}数据源不存在, 创建对应的数据源", currentAccountSuit);
createAndSaveDataSource(currentAccountSuit);
} else {
log.info("{}数据源已存在不需要创建", currentAccountSuit);
}
log.info("切换到{}数据源", currentAccountSuit);
return currentAccountSuit;
}
数据库主库fan_main,另外三个库fan_001, fan_002, fan_003
完整项目代码 github