日常的业务开发项目中只会配置一套数据源,如果需要获取其他系统的数据往往是通过调用接口, 或者是通过第三方工具比如kettle将数据同步到自己的数据库中进行访问。
但是也会有需要在项目中引用多数据源的场景。比如如下场景:
忽略掉controller/service/entity/mapper/xml介绍。

Spring Boot 的动态数据源,本质上是把多个数据源存储在一个 Map 中,当需要使用某个数据源时,从 Map 中获取此数据源进行处理。

在 Spring 中已提供了抽象类 AbstractRoutingDataSource 来实现此功能,继承AbstractRoutingDataSource类并覆写其determineCurrentLookupKey()方法即可,该方法只需要返回数据源key即可,也就是存放数据源的Map的key。
因此,我们在实现动态数据源的,只需要继承它,实现自己的获取数据源逻辑即可。AbstractRoutingDataSource顶级继承了DataSource,所以它也是可以做为数据源对象,因此项目中使用它作为主数据源。

AbstractRoutingDataSource中有一个重要的属性:


AbstractRoutingDataSource中所有的方法和属性:

比较重要的是determineTargetDataSource方法。
- protected DataSource determineTargetDataSource() {
- 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;
- }
-
- /**
- * Determine the current lookup key. This will typically be
- * implemented to check a thread-bound transaction context.
- *
Allows for arbitrary keys. The returned key needs
- * to match the stored lookup key type, as resolved by the
- * {@link #resolveSpecifiedLookupKey} method.
- */
- @Nullable
- protected abstract Object determineCurrentLookupKey();
-
这个方法主要就是返回一个DataSource对象,主要逻辑就是先通过方法determineCurrentLookupKey获取一个Object对象的lookupKey,然后通过这个lookupKey到resolvedDataSources中获取数据源(resolvedDataSources就是一个Map,上面已经提到过了);如果没有找到数据源,就返回默认的数据源。determineCurrentLookupKey就是程序员配置动态数据源需要自己实现的方法。
配置多数据源后启动项目报错:Property 'sqlSessionFactory' or 'sqlSession Template' are required。
翻译过来就是:需要属性“sqlSessionFactory”或“sqlSessionTemplate”。也就是说 注入数据源的时候需要这两个数据,但是这两个属性在启动容器中没有找到。
当引入mybatis-plus依赖mybatis-plus-boot-starter后,会添加一个自动配置类 MybatisPlusAutoConfiguration 。 其中有两个方法sqlSessionFactory()、sqlSessionTemplate()。这两个方法就是 给容器中注入“sqlSessionFactory”或“sqlSessionTemplate”两个属性。
-
- @Configuration(
- proxyBeanMethods = false
- )
- @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
- @ConditionalOnSingleCandidate(DataSource.class)
- @EnableConfigurationProperties({MybatisPlusProperties.class})
- @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
- public class MybatisPlusAutoConfiguration implements InitializingBean {
- @Bean
- @ConditionalOnMissingBean
- public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
- MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
- factory.setDataSource(dataSource);
- factory.setVfs(SpringBootVFS.class);
- if (StringUtils.hasText(this.properties.getConfigLocation())) {
- factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
- }
-
- this.applyConfiguration(factory);
- if (this.properties.getConfigurationProperties() != null) {
- factory.setConfigurationProperties(this.properties.getConfigurationProperties());
- }
-
- if (!ObjectUtils.isEmpty(this.interceptors)) {
- factory.setPlugins(this.interceptors);
- }
-
- if (this.databaseIdProvider != null) {
- factory.setDatabaseIdProvider(this.databaseIdProvider);
- }
-
- if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
- factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
- }
-
- if (this.properties.getTypeAliasesSuperType() != null) {
- factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
- }
-
- if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
- factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
- }
-
- if (!ObjectUtils.isEmpty(this.typeHandlers)) {
- factory.setTypeHandlers(this.typeHandlers);
- }
-
- if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
- factory.setMapperLocations(this.properties.resolveMapperLocations());
- }
-
- Objects.requireNonNull(factory);
- this.getBeanThen(TransactionFactory.class, factory::setTransactionFactory);
- Class extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
- if (!ObjectUtils.isEmpty(this.languageDrivers)) {
- factory.setScriptingLanguageDrivers(this.languageDrivers);
- }
-
- Optional var10000 = Optional.ofNullable(defaultLanguageDriver);
- Objects.requireNonNull(factory);
- var10000.ifPresent(factory::setDefaultScriptingLanguageDriver);
- this.applySqlSessionFactoryBeanCustomizers(factory);
- GlobalConfig globalConfig = this.properties.getGlobalConfig();
- Objects.requireNonNull(globalConfig);
- this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
- this.getBeansThen(IKeyGenerator.class, (i) -> {
- globalConfig.getDbConfig().setKeyGenerators(i);
- });
- Objects.requireNonNull(globalConfig);
- this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
- Objects.requireNonNull(globalConfig);
- this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
- factory.setGlobalConfig(globalConfig);
- 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);
- }
- }
-
这里主要关注配置类上面的注解,详细如下:
- @Configuration(
- proxyBeanMethods = false
- )
- // 当类路径下有SqlSessionFactory.class、SqlSessionFactoryBean.class时才生效
- @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
- // 容器中只能有一个符合条件的DataSource
- // 因为容器中有3个数据源,且没有指定主数据源,这个条件不通过,就不会初始化这个配置类了
- @ConditionalOnSingleCandidate(DataSource.class)
- @EnableConfigurationProperties({MybatisPlusProperties.class})
- @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
- public class MybatisPlusAutoConfiguration implements InitializingBean {
-
- }
-
因为MybatisPlusAutoConfiguration不满足@ConditionalOnSingleCandidate(DataSource.class) 条件,所以自动配置不会生效,就不会执行sqlSessionFactory()、sqlSessionTemplate()两个方法。 容器中就不会有sqlSessionFactory、sqlSessionTemplate 两个bean对象,因此会报错。
所以在数据源配置类中的动态数据源配置上添加@Primary注解即可。
- @Configuration
- @PropertySource("classpath:config/jdbc.properties")
- @MapperScan(basePackages = {"com.xinxing.learning.datasource.mapper"})
- public class DynamicDataSourceConfig {
- @Bean
- @Primary
- public DataSource dynamicDataSource() {
- Map<Object, Object> targetDataSource = new HashMap<>();
- targetDataSource.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
- targetDataSource.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
-
- DynamicDataSource dataSource = new DynamicDataSource();
- dataSource.setTargetDataSources(targetDataSource);
- dataSource.setDefaultTargetDataSource(masterDataSource());
-
- return dataSource;
- }
- }