昨天,同事拿到一个需求,要在已有的配置上新增一个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>
最后修改:2025 年 04 月 09 日
给我一点小钱钱也很高兴啦!o(* ̄▽ ̄*)ブ