Springboot 配置动态多数据源(Mybatis-plus)

前言:在项目中需要用到动态切换多数据源,查阅Mybatis-plus文档得知可以通过@DS注解,但该方法主要针对不同内容的数据源,而目前场景是相同内容的数据库需要在运行时根据请求头动态切换,因此文档方法不适用。

注意,不要使用dynamic-datasource-spring-boot-starter依赖包。

一、动态多数据源的场景

  1. 应用不拆,数据库拆
  2. 读写分离

二、动态多数据源的实现

网上文章非常多,大体思路都差不多,笔者在这里不重复放置代码了,例如:《springboot 中动态切换数据源》
不过目前找到的文章方法在项目整合了Mybatis-plus的情况下基本都有问题,以下是这几天遇到的问题和解决方案。

三、问题与解决方案

1. 找不到事务管理器

No qualifying bean of type 'org.springframework.transaction.TransactionManager

可以参照《mybatis-plus多数据源事务报错 》,在Springboot中,每一个事务管理器都需要对应一个datasource,而多数据源操作时,需要在@Transactional注解中指定事务管理器,或者配置默认事务管理器。
不过笔者在《springboot动态切换多个数据源》中发现,可以直接将自定义的dynamicDataSource作为dataSource传给事务管理器,并设置为默认事务管理器,这样就不用配置多个事务管理器了,代码如下:

    @Bean(name = "platformTransactionManager")
    @Primary
    public PlatformTransactionManager platformTransactionManager() {
        PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dynamicDataSource());
        return transactionManager;
    }

2. DataSource没有初始化

DataSource router not initialized

这个问题比较奇特,具体解决方法是在stack overflow中看到的。在自定义的DynamicDataSource类的实现方法中添加如下一行代码:

dataSource.afterPropertiesSet();

该方法目的是让Bean设置好所有属性后再执行初始化操作。

3. 找不到数据源

Invalid bound statement (not found)

这个问题是最坑的,具体原因参照《mybatis升级为mybatis-plus踩到的坑》,而网上很多多数据源文章都没有提到这一点。解决方法也很简单,就是将配置的SqlSessionFactory改为MybatisSqlSessionFactoryBean。

    @Bean
    public MybatisSqlSessionFactoryBean SqlSessionFactory()
            throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource()); // dynamicDataSource()为自己定义的实现方法
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver()
                        .getResources("classpath*:Mapper/*.xml") // 填写自己的XML路径
        );
        return sqlSessionFactoryBean;
    }

该问题的起源,也是因为导入了Mybatis plus才出现的,导入mybatis-plus-boot-starter依赖包,可以看到有一个MybatisPlusAutoConfiguration类。
Springboot 配置动态多数据源(Mybatis-plus)_第1张图片
点进去,发现有一个这样的Bean,以及注解TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean,以下都是源码。所以可以知道,在Mybatis plus框架中,使用的都是封装后的MybatisSqlSessionFactoryBean。

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
        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()));
        }
        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());
        }

        // TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配)
        Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
        if (!ObjectUtils.isEmpty(this.languageDrivers)) {
            factory.setScriptingLanguageDrivers(this.languageDrivers);
        }
        Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);

        // TODO 自定义枚举包
        if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
            factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
        }
        // TODO 此处必为非 NULL
        GlobalConfig globalConfig = this.properties.getGlobalConfig();
        // TODO 注入填充器
        if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class,
            false, false).length > 0) {
            MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class);
            globalConfig.setMetaObjectHandler(metaObjectHandler);
        }
        // TODO 注入主键生成器
        if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false,
            false).length > 0) {
            IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class);
            globalConfig.getDbConfig().setKeyGenerator(keyGenerator);
        }
        // TODO 注入sql注入器
        if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false,
            false).length > 0) {
            ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class);
            globalConfig.setSqlInjector(iSqlInjector);
        }
        // TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
        factory.setGlobalConfig(globalConfig);
        return factory.getObject();
    }

4. jdbc not connection问题
该问题主要需要检查配置文件,尤其是url要改成jdbc-url,格式如下

spring:
  datasource:
    database01: 
      jdbc-url: 
      username: 
      password: 
      driver-class-name: 
    database02:
      jdbc-url: 
      username: 
      password: 
      driver-class-name: 

你可能感兴趣的:(mybatis,spring,boot,java)