为了账号安全,请及时绑定邮箱和手机立即绑定

Spring多数据源、动态数据源源码解析

标签:
Java

在Java中所有的连接池都按照规范实现DataSource接口,在获取连接的时候即可通过getConnection()获取连接而不用关系底层究竟是何数据库连接池。


复制代码

1 public interface DataSource  extends CommonDataSource, Wrapper {2 3   Connection getConnection() throws SQLException;4 5   Connection getConnection(String username, String password) throws SQLException;6 }

复制代码

 

在大多数系统中我们只需要一个数据源,而现在WEB系统通常是Spring为基石。不管你是xml配置,javaBean配置还是yml,properties配置文件配置,其核心就是注入一个数据源交给spring的进行管理。

而在部分系统中我们可能会面临一些情况,连接多个表,主从,甚至多个不同的库等等情况,核心需求就是我们可能需要配置多个连接池。

在mybatis系统中我们使用多数据源可以配置配置多个DataSource,SqlSessionFactory,SqlSessionTemplate,然后在xml和mapper也分开管理。具体可以参考https://blog.csdn.net/neosmith/article/details/61202084 这篇博客。

这种方案在小的系统足够使用,作者认为更适合于多个不同的数据库。

回归正题,在Spring中从2.0.1版本默认提供了AbstractRoutingDataSource,我们继承它实现相关方法,把所有需要的数据源设置进去即可动态的切换数据源。我们可以看下核心方法的源码。

?


public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {    //设置所有的数据源    private Map<Object, Object> targetDataSources;    //设置默认的数据源,在没有找到相关数据源的时候会返回默认数据源    private Object defaultTargetDataSource;    //快速失败,可忽略    private boolean lenientFallback = true;    //Jndi相关,可忽略    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();    //经过解析后的所有数据源,核心    private Map<Object, DataSource> resolvedDataSources;    //经过解析后的默认数据源,核心    private DataSource resolvedDefaultDataSource;    //设置相关参数方法    public void setTargetDataSources(Map<Object, Object> targetDataSources) {        this.targetDataSources = targetDataSources;    }    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {        this.defaultTargetDataSource = defaultTargetDataSource;    }    public void setLenientFallback(boolean lenientFallback) {        this.lenientFallback = lenientFallback;    }    public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {        this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());    }    @Override    public void afterPropertiesSet() {        //检测是否设置所有的数据源        if (this.targetDataSources == null) {            throw new IllegalArgumentException("Property 'targetDataSources' is required");        }        //解析所有数据源,一般没什么用,主要是如果Map<Object, Object> targetDataSources的value是string则会从Jndi数据源查找        this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());        for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {            Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());            DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());            this.resolvedDataSources.put(lookupKey, dataSource);        }        //同上解析默认数据源        if (this.defaultTargetDataSource != null) {            this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);        }    }    @Override    public Connection getConnection() throws SQLException {        //核心,获取数据源先查找当前连接池再获取数据源        return determineTargetDataSource().getConnection();    }    @Override    public Connection getConnection(String username, String password) throws SQLException {        return determineTargetDataSource().getConnection(username, password);    }    protected DataSource determineTargetDataSource() {        //调用determineCurrentLookupKey,然后去resolvedDefaultDataSource查找,有就返回对应数据源,没有返回默认数据源        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");        Object lookupKey = determineCurrentLookupKey();        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 + "]");        }        return dataSource;    }         //而determineCurrentLookupKey需要我们自己去实现。    //通常需要结合Aop和ThreadLocal。我们Aop从注解上获取当前用户用户所希望的数据源,然后设置到当前线程。在determineCurrentLookupKey再从当前线程拿出来返回给determineTargetDataSource由其决定最终数据源    protected abstract Object determineCurrentLookupKey();}

  

以上具体的实现可以参考这篇博客 https://blog.csdn.net/u012881904/article/details/77449710 需要注意的是AOP的order必须在事物的order之前。

优点:方便配置,便捷使用。缺点:默认实现有一定局限性,大多数人足够使用。如果你有更复杂的使用场景,多库数据源,分组数据源,多主多从等等较复杂场景可以尝试

https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter

一个基于springboot的快速集成多数据源的启动器。

一个标准的主从的配置如下,引入相关配置即可使用。更多使用的方式查看相关文档。


?

 spring:  datasource:    dynamic:      primary: master #设置默认的数据源或者数据源组,默认值即为master,如果你主从默认下主库的名称就是master可不定义此项。      datasource:        master:          username: root          password: 123456          driver-class-name: com.mysql.jdbc.Driver          url: jdbc:mysql://47.100.20.186:3306/dynamic?characterEncoding=utf8&useSSL=false        slave_1:          username: root          password: 123456          driver-class-name: com.mysql.jdbc.Driver          url: jdbc:mysql://47.100.20.186:3307/dynamic?characterEncoding=utf8&useSSL=false        slave_2:          username: root          password: 123456          driver-class-name: com.mysql.jdbc.Driver          url: jdbc:mysql://47.100.20.186:3308/dynamic?characterEncoding=utf8&useSSL=false

  

实现核心源码如下

 public class DynamicRoutingDataSource extends AbstractRoutingDataSource {     /**     * 所有库     */    private Map<String, DataSource> dataSourceMap;     /**     * 分组数据库     */    private Map<String, DynamicGroupDatasource> groupDataSources = new HashMap<>();     @Setter    private DynamicDataSourceProvider dynamicDataSourceProvider;     @Setter    private Class<? extends DynamicDataSourceStrategy> dynamicDataSourceStrategyClass;     /**     * 默认数据源名称,默认master,可为组数据源名,可为单数据源名     */    @Setter    private String primary;     @Override    protected Object determineCurrentLookupKey() {        return DynamicDataSourceContextHolder.getDataSourceLookupKey();    }     @Override    protected DataSource determineTargetDataSource() {        String lookupKey = (String) determineCurrentLookupKey();        if (groupDataSources.containsKey(lookupKey)) {            log.debug("从 {} 组数据源中返回数据源", lookupKey);            return groupDataSources.get(lookupKey).determineDataSource();        } else if (dataSourceMap.containsKey(lookupKey)) {            log.debug("从 {} 单数据源中返回数据源", lookupKey);            return dataSourceMap.get(lookupKey);        }        log.debug("从默认数据源中返回数据");        return groupDataSources.containsKey(primary) ? groupDataSources.get(lookupKey).determineDataSource() : dataSourceMap.get(primary);    }     @Override    public void afterPropertiesSet() {        this.dataSourceMap = dynamicDataSourceProvider.loadDataSources();        log.debug("共加载 {} 个数据源", dataSourceMap.size());        //分组数据源        for (Map.Entry<String, DataSource> dsItem : dataSourceMap.entrySet()) {            String dsName = dsItem.getKey();            if (dsName.contains("_")) {                String[] groupDs = dsName.split("_");                String groupName = groupDs[0];                DataSource dataSource = dsItem.getValue();                if (groupDataSources.containsKey(groupName)) {                    groupDataSources.get(groupName).addDatasource(dataSource);                } else {                    try {                        DynamicGroupDatasource groupDatasource = new DynamicGroupDatasource(groupName, dynamicDataSourceStrategyClass.newInstance());                        groupDatasource.addDatasource(dataSource);                        groupDataSources.put(groupName, groupDatasource);                    } catch (Exception e) {                        e.printStackTrace();                    }                }            }        }        //检测组数据源设置        Iterator<Map.Entry<String, DynamicGroupDatasource>> groupIterator = groupDataSources.entrySet().iterator();        while (groupIterator.hasNext()) {            Map.Entry<String, DynamicGroupDatasource> item = groupIterator.next();            log.debug("组 {} 下有 {} 个数据源", item.getKey(), item.getValue().size());            if (item.getValue().size() == 1) {                log.warn("请注意不要设置一个只有一个数据源的组,{} 组将被移除", item.getKey());                groupIterator.remove();            }        }        //检测默认数据源设置        if (groupDataSources.containsKey(primary)) {            log.debug("当前的默认数据源是组数据源,组名为 {} ,其下有 {} 个数据源", primary, groupDataSources.size());        } else if (dataSourceMap.containsKey(primary)) {            log.debug("当前的默认数据源是单数据源,数据源名为{}", primary);        } else {            throw new RuntimeException("请检查primary默认数据库设置,当前未找到" + primary + "数据源");        }    } }

  

原文出处:https://www.cnblogs.com/taoyu-cd/p/9463955.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消