Spring jdbc ,druid动态数据源、固定容量Map

前言

最近做了一个数据源平台,主要功能是它可以存在多个数据源在一个工程下,并且在执行sql时能找到正确的数据源切换并执行。也就是动态数据源切换执行。

原工程使用的是springJdbc + druid进行的数据源管理,因此数据源获取结构发生了如下改变:

 核心代码就是实现 AbstractRoutingDataSource类determineCurrentLookupKey()方法。

原来工程使用的数据源也要被AbstractRoutingDataSource管理,在这里暂且称为主数据源用于区分。

依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>        
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>12.2.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <version>9.4.1.jre8</version>
        </dependency>

源码

AbstractRoutingDataSource类中实现的getConnection()方法
    public Connection getConnection() throws SQLException {
        return this.determineTargetDataSource().getConnection();
    }

调用了determineTargetDataSource(),其内部又调用了唯一抽象方determineCurrentLookupKey(),因此得知这个方法需要返回具体key值,然后才能从resolvedDataSources这个map中取到DataSource,从而获取数据库连接。

    private Map<Object, DataSource> resolvedDataSources;
    
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource 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 + "]");
        } else {
            return dataSource;
        }
    }

    @Nullable
    protected abstract Object determineCurrentLookupKey();

代码

  • 创建本地线程维护类,用于切换数据源
public class RoutingDataSourceContext {
    private static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>();

    /**
     * 获取主数据库的key
     *
     * @return
     */
    public static String getMainKey() {
        return "mainDB";
    }

    /**
     * 获取数据库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);
    }

}
  • 自定义类RoutingDataSource ,继承AbstractRoutingDataSource
@Configuration
@MapperScan("com.your.package.mapper")
public class RoutingDataSource extends AbstractRoutingDataSource
  •  创建sqlSession工厂。这里一定要把this传入dataSource
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.apache.ibatis.session.SqlSessionFactory;

@Configuration
@MapperScan("com.your.package.mapper")
public class RoutingDataSource extends AbstractRoutingDataSource {

    private Map<Object, Object> dataSourcesMap = new ConcurrentHashMap<>();

    public RoutingDataSource() {
        log.info("初始化动态数据源");
        //这里是指定用什么容器去存所有路由的数据源,当前使用ConcurrentHashMap
        super.setTargetDataSources(dataSourcesMap);
    }
   
    @Resource
    private GlobalConfig globalConfig;
    
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
        factoryBean.setDataSource(this);
        factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
        //读取mapper.xml
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("mapper/*.xml"));
        factoryBean.setTypeAliasesPackage("com.your.package.po");
        factoryBean.setGlobalConfig(globalConfig);
        return factoryBean.getObject();
    }
}
  • 实现determineCurrentLookupKey()方法
    @Override
    //本地线程没有则使用主数据源
    protected Object determineCurrentLookupKey() {
        String dbSourceKey = RoutingDataSourceContext.getDataSourceRoutingKey();
        if (StringUtils.isEmpty(dbSourceKey)) {
            dbSourceKey = RoutingDataSourceContext.getMainKey();
        }
        log.info("当前操作数据源ID:{}", dbSourceKey);
        if (!dataSourcesMap.containsKey(dbSourceKey)) {
            log.info("{}数据源不存在, 创建对应的数据源", dbSourceKey);
            createAndSaveDataSource(dbSourceKey);
        } else {
            log.info("{}数据源已存在不需要创建", dbSourceKey);
        }
        log.info("切换到{}数据源", dbSourceKey);
        return dbSourceKey;
    }


    private synchronized void createAndSaveDataSource(String dbSourceKey) {
        DruidDataSource dataSource;
        if (dbSourceKey.equals(RoutingDataSourceContext.getMainKey())) {
            //创建主数据源,主数据源的durid配置要区别于其他
            dataSource = getMainDBSource();
            //设置默认的数据源
            super.setDefaultTargetDataSource(dataSource);
             //加入本地容器中
            dataSourcesMap.put(dbSourceKey, dataSource);
        } else {
            dataSource = createDruidDataSource(dbSourceKey);
            //在执行过程中不必调用druid.init(),在获取链接时自动调用
            log.info("{}数据源创建成功", dbSourceKey);
            //加入本地容器中
            dataSourcesMap.put(dbSourceKey, dataSource);
            //设置路由容器
            super.setTargetDataSources(dataSourcesMap);
        }
        //对创建的数据源初始化
        afterPropertiesSet();
    }

    private static DruidDataSource createDruidDataSource(DynamicDataSourcePO dataSourceBean) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(dataSourceBean.getUrl());
        dataSource.setUsername(dataSourceBean.getUsername());
        dataSource.setPassword(dataSourceBean.getPassword());
        dataSource.setInitialSize(1);
        dataSource.setMaxActive(2);
        dataSource.setMinIdle(1);
        dataSource.setTestOnBorrow(false);
        dataSource.setValidationQuery("select 1");
        // 如果连接空闲超过1小时就断开
        dataSource.setMinEvictableIdleTimeMillis(60000 * 60);
        // 每十分钟验证一下连接
        dataSource.setTimeBetweenEvictionRunsMillis(600000);
        // 运行ilde链接测试线程,剔除不可用的链接
        dataSource.setTestWhileIdle(true);
        dataSource.setMaxWait(5000);
        return dataSource;
    }
  • 使用时先设置当前需要用到的数据源的key值,然后执行sql就可以了
    public List<Map<String, Object>> doQuery(String dbid, String sql) {
        //切换
        RoutingDataSourceContext.setThreadLocalDataSourceKey(dbid);
        //执行
        return dynamicDataSourceMapper.query(sql);
    }

问题

ConcurrentHashMap随着使用增多而导致内存、线程等开销增大,有没有固定容量的map作为容器?

答案是有的,LinkedHashMap就可以控制

public class DynamicDataSourceContainer extends LinkedHashMap<Object, Object> {

    //返回true就是移除第1个插入的元素,FIFO策略
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        //eldest 为首个元素
        final int maxSize = 50;
        boolean b = size() > maxSize;
        if (b) {
            //要移除之前,释放资源
            DruidDataSource dataSource = (DruidDataSource) eldest.getValue();
            dataSource.close();
        }
        return b;
    }
}
//private Map<Object, Object> dataSourcesMap = new ConcurrentHashMap<>();
//替换为
private Map<Object, Object> dataSourcesMap = new DynamicDataSourceContainer();

这样又会产生新的问题,就是主数据源会被移除,那么如何保证主数据源不被移除呢?

public class DynamicDataSourceContainer extends LinkedHashMap<Object, Object> {
    //主数据源的临时变量
    private static volatile Object tempDs;

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        //eldest 为首个元素
        final int maxSize = 50;
        boolean b = size() > maxSize;
        if (b) {
            if (DataSourceTypeConstant.mainDB.equals(eldest.getKey())) {
                //先缓存下来
                tempDs = super.get(DataSourceTypeConstant.mainDB);
                //移除,让出容器大小
                super.remove(DataSourceTypeConstant.mainDB);
                //重新添加到末尾
                super.put(DataSourceTypeConstant.mainDB, tempDs);
                //不移除首个元素
                return false;
            } else {
                DruidDataSource dataSource = (DruidDataSource) eldest.getValue();
                dataSource.close();
            }
        }
        return b;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Boot支持使用多个数据源,可以通过配置多个数据源,以及配置动态数据源来实现连接N个数据库。 首先,需要在项目中引入多数据源的依赖,例如Druid或HikariCP。 然后,在配置文件中配置多个数据源的信息,如下所示: ``` spring.datasource.primary.url=jdbc:mysql://localhost:3306/db1 spring.datasource.primary.username=root spring.datasource.primary.password=root spring.datasource.secondary.url=jdbc:mysql://localhost:3306/db2 spring.datasource.secondary.username=root spring.datasource.secondary.password=root ``` 在配置文件中还需要配置动态数据源,如下所示: ``` spring.datasource.type=com.zaxxer.hikari.HikariDataSource spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-active=20 spring.datasource.druid.test-on-borrow=true spring.datasource.dynamic.primary=primary spring.datasource.dynamic.secondary=secondary spring.datasource.dynamic.datasource-names=primary,secondary ``` 这样就可以实现动态数据源的配置。在代码中,可以使用`@Primary`和`@Qualifier`注解来指定默认数据源和特定的数据源。 例如: ``` @Primary @Bean(name = "primaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); } @Bean(name = "secondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); } @Bean(name = "dynamicDataSource") public DataSource dynamicDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource, @Qualifier("secondaryDataSource") DataSource secondaryDataSource) { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("primary", primaryDataSource); dataSourceMap.put("secondary", secondaryDataSource); dynamicDataSource.setTargetDataSources(dataSourceMap); dynamicDataSource.setDefaultTargetDataSource(primaryDataSource); return dynamicDataSource; } ``` 在使用时,可以通过`@Autowired`注解注入动态数据源,并使用`@Qualifier`注解指定特定的数据源,例如: ``` @Autowired @Qualifier("dynamicDataSource") private DataSource dataSource; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

as350144

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值