Spring动态创建数据源

Spring动态创建数据源

Spring内置了一个AbstractRoutingDataSource,可以根据Key找到对应的数据源。那么我们可以把多个数据源存放到Map中,然后根据key去切换数据源。

  1. 创建一个类继承AbstractRoutingDataSource
public class RoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return RoutingDataSourceContext.getDataSourceRoutingKey();
    }
}

这里determineCurrentLookupKey返回key,会自动去查找对应的DataSource

  1. 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);
    }

}
  1. 创建数据源
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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值