mybatis-plus DataSource 源码分析

简介

javax.sql.DataSource是JDBC规范的一个核心接口,作为 DriverManager 机制的替代方案,用于管理数据库连接。
mybatis的SqlSessionFactory操作数据库就是基于DataSource的实现类,参见org.apache.ibatis.mapping.Environment

public final class Environment {
   private final DataSource dataSource;
}

当你使用mybatis作为操作数据库的组件库,需要提供一个DataSource设置到SqlSessionFactory。
如果选择了hikari线程池,实际上就是将hikari的DataSource实现设置到mybatis的SqlSessionFactory

@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
    HikariConfig config = new HikariConfig();
    DataSource dataSource = HikariDataSource(config);
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(dataSource);
    return sessionFactory;
}

现实的业务场景可能需要连接多个数据库,就需要引入mybatis-plus的多数据源组件。

mybatis-plus的多数据源DynamicRoutingDataSource也是DataSource的实现,并且集成了各种线程池和p6spy日志功能。
其原理如图所示:

图片替代文本

mybatis-plus的DynamicRoutingDataSource,hikari的HikariDataSource,p6spy的P6DataSource如同俄罗斯套娃,通过
DataSource嵌套的方式,实现了多数据源切换,线程池,日志的功能。

创建DataSource

抽象接口的链路设计:

DataSource --> DynamicDataSourceProvider -->DataSourceCreator

DynamicRoutingDataSource

DynamicRoutingDataSource是mybatis-plus的DataSource接口实现

图片替代文本

自动装配DynamicDataSourceAutoConfiguration将DynamicRoutingDataSource类注册为DataSource接口类型的Bean到Spring容器

public class DynamicDataSourceAutoConfiguration implements InitializingBean {
    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        //省略...
        return dataSource;
    }
}

DynamicRoutingDataSource本身不实现数据库操作,而是通过引入其他DataSource实现。

DynamicRoutingDataSource内部方法afterPropertiesSet调用了DynamicDataSourceProvider的loadDataSources方法加载不同的DataSource实现,

@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean {
    @Autowired
    private List<DynamicDataSourceProvider> providers;
    /**
     * 所有数据库
     */
    private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        // 添加并分组数据源
        Map<String, DataSource> dataSources = new HashMap<>(16);
        for (DynamicDataSourceProvider provider : providers) {
            dataSources.putAll(provider.loadDataSources());
        }
        for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) {
            addDataSource(dsItem.getKey(), dsItem.getValue());
        }
    }
}

DynamicDataSourceProvider

DynamicDataSourceProvider是多数据源加载接口,默认的实现YmlDynamicDataSourceProvider类是从yml信息中加载所有数据源

可以通过实现这个接口并且注册到spring的方式重写数据源加载逻辑

图片替代文本

自动装配注册了YmlDynamicDataSourceProvider

public class DynamicDataSourceAutoConfiguration implements InitializingBean {
    @Bean
    public DynamicDataSourceProvider ymlDynamicDataSourceProvider() {
        return new YmlDynamicDataSourceProvider(properties.getDatasource());
    }
}

YmlDynamicDataSourceProvider通过其父类AbstractDataSourceProvider的createDataSourceMap方法
调用到DefaultDataSourceCreator接口的createDataSource方法创建数据源

public class YmlDynamicDataSourceProvider extends AbstractDataSourceProvider {
    @Override
    public Map<String, DataSource> loadDataSources() {
        return createDataSourceMap(dataSourcePropertiesMap);
    }
}

@Slf4j
public abstract class AbstractDataSourceProvider implements DynamicDataSourceProvider {
    @Autowired
    private DefaultDataSourceCreator defaultDataSourceCreator;

    protected Map<String, DataSource> createDataSourceMap(
            Map<String, DataSourceProperty> dataSourcePropertiesMap) {
        Map<String, DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);
        for (Map.Entry<String, DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {
            String dsName = item.getKey();
            DataSourceProperty dataSourceProperty = item.getValue();
            String poolName = dataSourceProperty.getPoolName();
            if (poolName == null || "".equals(poolName)) {
                poolName = dsName;
            }
            dataSourceProperty.setPoolName(poolName);
            dataSourceMap.put(dsName, defaultDataSourceCreator.createDataSource(dataSourceProperty));
        }
        return dataSourceMap;
    }
}

DataSourceCreator

DataSourceCreator是不同DataSource的创建接口,选用hikariDataSource,对应就是由HikariDataSourceCreator创建

可以通过实现自己的DataSourceCreator增加数据源,或者覆盖默认的数据源创建器

图片替代文本

自动装配将HikariDataSourceCreator类注册为DataSourceCreator接口类型的Bean到Spring容器

public class DynamicDataSourceCreatorAutoConfiguration {
    @ConditionalOnClass(HikariDataSource.class)
    @Configuration
    static class HikariDataSourceCreatorConfiguration {
        @Bean
        @Order(HIKARI_ORDER)
        public HikariDataSourceCreator hikariDataSourceCreator() {
            return new HikariDataSourceCreator();
        }
    }

    @Primary
    @Bean
    @ConditionalOnMissingBean
    public DefaultDataSourceCreator dataSourceCreator(List<DataSourceCreator> dataSourceCreators) {
        DefaultDataSourceCreator defaultDataSourceCreator = new DefaultDataSourceCreator();
        defaultDataSourceCreator.setCreators(dataSourceCreators);
        return defaultDataSourceCreator;
    }
}

多个注册的DataSourceCreator,List又被设置到DefaultDataSourceCreator类的属性

public class DynamicDataSourceCreatorAutoConfiguration {
    @Primary
    @Bean
    @ConditionalOnMissingBean
    public DefaultDataSourceCreator dataSourceCreator(List<DataSourceCreator> dataSourceCreators) {
        DefaultDataSourceCreator defaultDataSourceCreator = new DefaultDataSourceCreator();
        defaultDataSourceCreator.setCreators(dataSourceCreators);
        return defaultDataSourceCreator;
    }
}

DefaultDataSourceCreator的createDataSource方法循环调用DataSourceCreator接口实现类的createDataSource方法

特别的,如果你实现了的DataSourceCreator和系统默认实现是同一个,比如都是hikari,并且注册了,那么这里使用顺序靠前的那个创建器

public class DefaultDataSourceCreator {

    private List<DataSourceCreator> creators;

    public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
        DataSourceCreator dataSourceCreator = null;
        for (DataSourceCreator creator : this.creators) {
            if (creator.support(dataSourceProperty)) {
                dataSourceCreator = creator;
                break;
            }
        }
        if (dataSourceCreator == null) {
            throw new IllegalStateException("creator must not be null,please check the DataSourceCreator");
        }
        return dataSourceCreator.createDataSource(dataSourceProperty);
    }

}

DataSourceCreator接口实现类的createDataSource方法会先调用其父类AbstractDataSourceCreator的createDataSource方法

AbstractDataSourceCreator的createDataSource方法调用的wrapDataSource方法实现了p6spay数据源的包裹

mybatis-plus是在这里实现对p6spy功能的整合

@Slf4j
public abstract class AbstractDataSourceCreator implements DataSourceCreator {
    @Override
    public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
        DataSource dataSource = doCreateDataSource(dataSourceProperty);
        return wrapDataSource(dataSource, dataSourceProperty);
    }

    private DataSource wrapDataSource(DataSource dataSource, DataSourceProperty dataSourceProperty) {
        DataSource targetDataSource = dataSource;
        Boolean enabledP6spy = properties.getP6spy() && dataSourceProperty.getP6spy();
        if (enabledP6spy) {
            targetDataSource = new P6DataSource(dataSource);
        }
        return new ItemDataSource(name, dataSource, targetDataSource, enabledP6spy, enabledSeata, seataMode);
    }
}

HikariDataSourceCreator

HikariDataSourceCreator负责创建HikariDataSource

public class HikariDataSourceCreator extends AbstractDataSourceCreator implements DataSourceCreator, InitializingBean {
    @Override
    public DataSource doCreateDataSource(DataSourceProperty dataSourceProperty) {
        HikariConfig config = MERGE_CREATOR.create(gConfig, dataSourceProperty.getHikari());
        //省略。。。
        HikariDataSource dataSource = new HikariDataSource();
        //省略。。。
        return dataSource;
    }
}

DriverDataSource

hikari的DriverDataSource类构造方法加载指定的JDBC实现作为操作数据库的驱动

package com.zaxxer.hikari.util;

public final class DriverDataSource implements DataSource {
    public DriverDataSource(String jdbcUrl, String driverClassName, Properties properties, String username, String password)
    {
        driver = DriverManager.getDriver(jdbcUrl);
    }
}

DataSource注入SqlSessionFactory

主要的调用链路如下

SqlSessionTemplate --> SqlSessionFactory --> DynamicRoutingDataSource

MybatisPlusAutoConfiguration这个自动装配将DynamicRoutingDataSource设置到SqlSessionFactory,完成和mybatis的DataSource整合

public class MybatisPlusAutoConfiguration implements InitializingBean {
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        // 省略
        return factory.getObject();
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }
}