前言:在项目中需要用到动态切换多数据源,查阅Mybatis-plus文档得知可以通过@DS注解,但该方法主要针对不同内容的数据源,而目前场景是相同内容的数据库需要在运行时根据请求头动态切换,因此文档方法不适用。
注意,不要使用dynamic-datasource-spring-boot-starter依赖包。
网上文章非常多,大体思路都差不多,笔者在这里不重复放置代码了,例如:《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类。
点进去,发现有一个这样的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: