昨天,同事拿到一个需求,要在已有的配置上新增一个SQLserver数据库,实现多数据源。
找到我的时候,我寻思这不有手就行嘛?直接用mybatis-plus的dynamic-datasource
就好了啊。然后通过@DS
注解在mapper接口上面指定对应的数据库就……结果很打脸,按照mybatis-plus官方的配置指定默认数据库以后一直报错:
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (the profiles db,redis,mq,config,sap,pre are currently active).
进程已结束,退出代码为 1
不得不说,这还是我职业生涯中第一次遇到这种问题。我首先考虑到的是:会不会是阿里巴巴的连接池配置影响到的?
所以我尝试把阿里巴巴的连接池配置迁移到MySQL数据库的配置下面,还是报错。
网上搜了一堆教程,给新的SQLserver也加了连接池配置,依旧无效。
可能是驱动没有生效?把SQLserver的驱动换成jTDS试试……
就这么折腾了一个多小时,期间我还使用了最近非常火的trae
,希望它可以解决我的燃眉之急。结果它在获取了项目文件列表和报错信息的情况下还是一通乱答,排查只能是靠自己了……
毕竟帮人帮到底嘛,虽然这不是我自己的工作~就在我最终打算放弃的时候,突然想到会不会是优先级的问题,可能在某个地方定义了一个配置,导致项目启动的时候默认走到了那个配置文件,导致我们后面新增的配置没有被扫描到……
就这样从下午五点多折腾到了晚上八点半,除去中间吃饭的半个小时。终于有点头绪了。。。
就是这个配置类,项目里面定义了一个MysqlDataSourceConfig
:
package cn.com.demo.infras.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Slf4j
@Configuration
@MapperScan(basePackages = {"cn.com.demo.**.mapper"}, sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class MysqlDataSourceConfig {
@Bean(name = "mysqlDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.mysql")
@Primary
public DataSource dataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Scope(value = "prototype")
@ConfigurationProperties(prefix = "mybatis-plus.configuration")
public MybatisConfiguration getCfg() {
return new MybatisConfiguration();
}
@Bean(name = "mysqlSqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource,
@Qualifier("mybatisPlusInterceptor") MybatisPlusInterceptor interceptor,
BaseEntityMetaObjectHandler baseEntityMetaObjectHandler
) throws Exception {
MybatisSqlSessionFactoryBean mysqlBean = new MybatisSqlSessionFactoryBean();
mysqlBean.setDataSource(dataSource);
mysqlBean.setConfiguration(getCfg());
mysqlBean.setPlugins(interceptor);
mysqlBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/**/*.xml"));
mysqlBean.setGlobalConfig(new GlobalConfig().setBanner(false).setMetaObjectHandler(baseEntityMetaObjectHandler));
return mysqlBean.getObject();
}
@Bean(name = "mysqlTransactionManager")
@Primary
public DataSourceTransactionManager transactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "mysqlSqlSessionTemplate")
@Primary
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("mysqlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
这一行:
@ConfigurationProperties(prefix = "spring.datasource.druid.mysql")
它每次都是读取yml里面的阿里巴巴连接池配置下面的mysql。
因为@Primary
注解的缘故,在自动装配规则中Spring会优先注入带有 @Primary 的 Bean。
所以使用@DS
注解才会不生效,因为它依然取的是默认数据源下面的数据。
怎么解决?由于项目10号就要上线,为了避免改出bug来,所以我想尽可能的不要影响现有的业务。
所以按照它之前的配置又写了一个SqlserverDataSourceConfig
:
package cn.com.demo.infras.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @Author zF.
* @ClassName SqlserverDataSourceConfig.java
* @ProjectName app-bx-inventory
* @Description Sqlserver自定义数据源配置
*/
@Slf4j
@Configuration
@MapperScan(basePackages = "cn.com.demo.**.sqlserver", sqlSessionFactoryRef = "sqlserverSqlSessionFactory")
public class SqlserverDataSourceConfig {
@Bean(name = "sqlserverDataSource")
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.sqlserver")
public DataSource sqlserverDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "sqlserverSqlSessionFactory")
public SqlSessionFactory sqlserverSqlSessionFactory(@Qualifier("sqlserverDataSource") DataSource dataSource, @Qualifier("mybatisPlusInterceptor") MybatisPlusInterceptor interceptor) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setConfiguration(new MybatisConfiguration());
bean.setPlugins(interceptor);
// 注释掉这行,如果不使用SQL Server的Mapper XML文件
// bean.setMapperLocations(new PathMatchingResourcePatternResolver()
// .getResources("classpath:/sqlserver/**/*.xml"));
return bean.getObject();
}
@Bean(name = "sqlserverTransactionManager")
public DataSourceTransactionManager sqlserverTransactionManager(@Qualifier("sqlserverDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "sqlserverSqlSessionTemplate")
public SqlSessionTemplate sqlserverSqlSessionTemplate(@Qualifier("sqlserverSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
在不影响当前规则的情况下新定义一套数据连接的规则:
因为之前是按照功能划分的,每个功能一个包,然后在功能包的下面创建entity、mapper、service、controller。
使用以上Sqlserver自定义数据源配置
,指定basePackages = "cn.com.demo.**.sqlserver"
,在功能包下面新建一个sqlserver包,在里面写Mapper接口即可。这样在接口被请求的时候,上面的配置就能自动将其识别为SQLserver数据源。
然后用@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.sqlserver")
指定该SQLserver数据源在yml中的具体位置。
如果想要使用xml编写sql语句,则需要解开上面配置中的注释,并且在resources
目录下新建sqlserver
文件夹,然后再按照模块名称创建文件夹,最后在模块文件夹内创建对应的Mapper.xml
,其他的和之前正常开发同理。此时就不再需要@DS
注解了,同样不需要在yml里面的多数据源配置中指定primary
,因为这是无效操作!
对应的yml配置文件:
spring:
datasource:
dynamic:
datasource:
sqlserver:
url: jdbc:sqlserver://10.10.92.210:1433;databaseName=***
username: ***
password: ***
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
type: com.alibaba.druid.pool.DruidDataSource
druid:
mysql:
database: ***
username: ***
password: ***
address: 172.29.20.133:3306
initialSize: 10
minIdle: 10
maxActive: 30
maxPoolPreparedStatementPerConnectionSize: 30
test-while-idle: true
test-on-borrow: false
test-on-return: false
web-stat-filter:
enabled: true
profile-enable: true
session-stat-enable: true
stat-view-servlet:
enabled: true
login-username: ***
login-password: ***
reset-enable: false
filters: stat,wall,log4j2,config
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 6000
merge-sql: false
wall:
config:
multi-statement-allow: true
config:
enabled: true
至于我为什么要使用dynamic
的方式新增sqlserver
的配置,而不是像mysql
那样将配置写在druid
下面,这大概是其不支持自定义数据库驱动的原因,具体的底层逻辑你可以自行研究。按照dynamic
的方式进行分库其实是一种暂时性的妥协,可能未来会有更好的方法,但是截止当前,为了不影响现有的这套业务正常运行,只能如此。
一套下来折腾了两三个小时,回过头来,梳理清楚,发现也没有想象中的那么难……
附上当前所需的maven依赖:
<!--数据源依赖(sqlserver)-->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>9.2.1.jre8</version>
</dependency>
<!--数据源依赖(多数据源)-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>