多数据源 (1)AbstractRoutingDataSource

3,271 阅读2分钟

之前MP系列讲的都是单数据源,在业务开发中很多场景都需要用到不同数据源的情况,本篇将讲述多数据源实现之一的AbstractRoutingDataSource

实现与原理

AbstractRoutingDataSource是DataSource接口的实现类,位于spring-jdbcjar包,它是一个抽象类,子类需要实现其determineCurrentLookupKey()方法,顾名思义,这是一个根据路由定位数据源的类。

首先需要自定义一个类来实现AbstractRoutingDataSource抽象类,本例中是DynamicDataSource

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        log.debug("数据源为{}", DataSourceContextHolder.getDB());
        return DataSourceContextHolder.getDB();
    }
}

随后分别手动创建数据源,本例中创建了master和demo两个数据源,它们返回的都是DruidDataSourceWrapper

    @Bean(name = "master")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource mainSource() {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }

    @Bean(name = "demo")
    @ConfigurationProperties(prefix = "spring.datasource.demo")
    public DataSource demoSource() {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }

配置DynamicDataSource,将多数据源以map形式进行管理,最后注入IoC容器。

    /**
     * 在实例化dynamicDataSource之前,需要保证master和demo数据源已注入IoC容器
     * @Primary必不可少,多个数据源的前提下,引入MybatisPlusAutoConfiguration时需要有一个@Primary数据源才会后续注入SqlSessionFactory
     * 且依赖注入DataSource时,返回DynamicDataSource
     */
    @Bean(name = "dynamicDataSource")
    @DependsOn({"master", "demo"})
    @Primary
    public DynamicDataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap<>(5);
        // 这里的key需要与调用determineCurrentLookupKey()方法的返回值相对应
        dsMap.put("master", mainSource());
        dsMap.put("demo", demoSource());
        dynamicDataSource.setTargetDataSources(dsMap);
        // 设置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(mainSource());
        return dynamicDataSource;
    }

获取连接时,入口方法仍然是getConnection()

AbstractRoutingDataSource->getConnection():
    // 先获取数据源,再调用数据源的getConnection()方法
    return determineTargetDataSource().getConnection();
AbstractRoutingDataSource->determineTargetDataSource():
    // 调用子类DynamicDataSource的determineCurrentLookupKey()方法
    Object lookupKey = determineCurrentLookupKey();
    // 根据key获得数据源,这里是DruidDataSourceWrapper
    DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    return dataSource;

DynamicDataSource在启动阶段将数据源以Map形式存储,获取连接的时候根据key返回对应的数据源,再调用数据源的getConnection()方法,完成数据源的切换

事务下的数据源

在事务中曾介绍过,新建事务时决定了数据源与连接对象,多数据源的情况下,此时determineCurrentLookupKey()方法返回空,但因为设置了默认数据源,所以返回默认数据源的连接。

DataSourceUtils->doGetConnection():
    // 获取连接对象
    // 如果conHolder不为空,说明处于事务下
    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
        conHolder.requested();
        // 没有连接对象则确定连接对象,一般情况下不会发生
        if (!conHolder.hasConnection()) {
            conHolder.setConnection(fetchConnection(dataSource));
        }
        return conHolder.getConnection();
    }
    ......
    // 以非事务的方式获取数据源,调用DynamicDataSource的getConnection()方法,每次都会确定数据源
    Connection con = fetchConnection(dataSource);

从代码中得出结论,同一事务下不支持数据源的切换,这一点需要注意。