SpringBoot业务开发 14、Spring Boot + MyBatis 配置多MySQL数据源,这一篇就够了

coverImg

介绍

在大型项目开发中,我们经常会遇到一个应用需要连接多个数据库的场景。比如,在一个后台管理系统中,我们可能需要连接一个核心的业务数据库,同时还需要读取另一个系统的数据(如禅道、Jira),或者将部分业务数据写入一个独立的统计库。

最近,我们在开发 dtstack-tools 这个集成工具时,就遇到了一个非常典型的需求:原有的系统已经连接了禅道数据库,用于读取Bug和Action信息;现在新增加了一个AI图像识别的功能模块,需要将识别记录写入一个独立的自定义业务库 dtstacktools。

如何优雅地在一个Spring Boot项目中配置和管理多个MySQL数据源呢?本文将结合我们的实战经验,为你提供一套完整、清晰的解决方案。

一、背景与问题引入

我们的项目原本是一个单一数据源应用,所有Mapper都指向禅道数据库(zentao)。application.yml 中的配置非常简单:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/zentao?useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456

但是,随着新需求的出现——我们需要将AI图像识别的结果持久化到另一个库 dtstacktools 中。这就引出了一个问题:如何在同一个Spring Boot应用里,同时操作两个不同的MySQL数据库?

OK,我们一起来看一下解决方案。

二、核心概念讲解

在Spring Boot中,实现多数据源的关键在于动态地创建多个DataSource和SqlSessionFactory,并为不同的Mapper指定不同的工厂。

我们来看下核心组件:

  • DataSource:代表一个数据源实例,包含数据库连接信息。
  • SqlSessionFactory:是MyBatis的核心,负责创建SqlSession。每个数据源都应该有自己独立的SqlSessionFactory。
  • MapperScan:这个注解可以指定扫描哪些包下的Mapper接口,并将它们注册到特定的SqlSessionFactory中。

简单来说,我们的思路就是:

  1. 在application.yml中为每个数据库定义独立的配置节。
  2. 创建两个配置类,分别对应一个数据源,并在配置类中生成对应的DataSource和SqlSessionFactory Bean。
  3. 使用@MapperScan注解,将不同包下的Mapper接口指定给不同的SqlSessionFactory。

这样,当我们在service层调用zentao包下的Mapper时,它会自动使用禅道的数据源;调用dtstacktools包下的Mapper时,则会使用自定义业务库的数据源。两者互不干扰。

三、方案对比

在着手实现之前,我们先来对比一下常见的两种方案:

  • 方案一:使用@Primary + 多数据源配置类

    • 原理:为默认数据源(如原来的禅道库)添加@Primary注解,使其成为首选数据源。然后为其他数据源创建独立的配置类。
    • 优点:代码结构清晰,职责分明,易于维护和扩展。
    • 缺点:配置相对复杂,需要手动管理SqlSessionFactory。
    • 适用场景:绝大多数多数据源场景,特别是新增数据源时,推荐使用。
  • 方案二:使用动态数据源(AbstractRoutingDataSource)

    • 原理:创建一个主数据源,内部维护一个Map,通过lookupKey来动态决定使用哪个数据源。
    • 优点:对业务代码侵入小,可以通过AOP或注解动态切换。
    • 缺点:实现更复杂,需要处理事务、连接池等问题,适合数据源切换频繁的场景。
    • 适用场景:读写分离、分库分表等场景。

对于我们这种新增一个独立业务库的场景,方案一(多数据源配置类)足够简洁且易于维护,我们这次就采用这种方案。

四、实现思路(架构设计)

根据上述思路,我们的整体架构设计如下:

  1. 配置文件重构:将原有的单一数据源配置,拆分为spring.datasource.zentao和spring.datasource.dtstacktools两部分。
  2. 配置类创建:
    • ZentaoDataSourceConfig:负责创建禅道数据源DataSource和SqlSessionFactory,并扫描com.changlu.dtstacktools.mapper.zentao包下的Mapper。
    • DtstackToolsDataSourceConfig:负责创建自定义业务库数据源DataSource和SqlSessionFactory,并扫描com.changlu.dtstacktools.mapper.dtstacktools包下的Mapper。
  3. Mapper迁移:将原有的禅道相关Mapper(如ZTBugMapper)移动到zentao包下,并将新创建的AiImageVisionMapper放在dtstacktools包下。
  4. XML文件迁移:同步迁移对应的XML文件到新的资源目录下(resources/mapper/zentao/和resources/mapper/dtstacktools/)。

这样一来,我们就在代码层面实现了数据源的物理隔离。

五、实现步骤与代码

下面我们一步步来实现这个方案。

5.1 修改配置文件 application.yml

首先,修改我们的配置文件,定义两个数据源。

文件路径:conf/application.yml (或 dtstack-tools-backend/dtstack-web/conf/application.yml)

spring:
  # ... 其他配置
  datasource:
    # 1. 禅道数据源 (保留原有的业务逻辑)
    zentao:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/zentao?useUnicode=true&characterEncoding=utf8&useSSL=false
      username: root
      password: 123456
      initialization-mode: always
      hikari:
        maximum-pool-size: 10  # 建议加上连接池配置

    # 2. 自定义业务库
    dtstacktools:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://172.16.114.23:3310/dtstacktools?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      hikari:
        maximum-pool-size: 5

mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    mapUnderscoreToCamelCase: false   # 保持原有配置

说明:

  • spring.datasource.zentao:保留了原来所有针对禅道库的配置,并增加了hikari连接池配置,使配置更健壮。
  • spring.datasource.dtstacktools:新增的配置,指向我们新的dtstacktools库。
  • 注意jdbc-url的写法,Spring Boot 2.x之后,如果手动指定hikari配置,推荐使用jdbc-url而不是url。

5.2 创建数据源配置类

接下来,我们为每个数据源创建一个配置类。

image-20260321223405790

5.2.1 禅道数据源配置类 ZentaoDataSourceConfig

文件路径:src/main/java/com/changlu/dtstacktools/config/ZentaoDataSourceConfig.java

package com.changlu.dtstacktools.config;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
@MapperScan(
        basePackages = "com.changlu.dtstacktools.mapper.zentao", // 指定Mapper所在的包
        sqlSessionFactoryRef = "zentaoSqlSessionFactory"         // 指定使用的SqlSessionFactory
)
public class ZentaoDataSourceConfig {

    @Primary // 标记为默认数据源,确保原有业务不受影响
    @Bean(name = "zentaoDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.zentao") // 读取以spring.datasource.zentao开头的配置
    public DataSource zentaoDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "zentaoSqlSessionFactory")
    public SqlSessionFactory zentaoSqlSessionFactory(@Qualifier("zentaoDataSource") DataSource dataSource) throws Exception {
        // 注意这里使用的是MybatisSqlSessionFactoryBean,以支持MyBatis-Plus特性
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);

        // 指定该数据源对应的XML文件路径
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources("classpath:mapper/zentao/*.xml"));

        // 手动配置MyBatis-Plus的Configuration,保持与原配置一致
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setMapUnderscoreToCamelCase(false);
        configuration.setLogImpl(StdOutImpl.class);
        bean.setConfiguration(configuration);

        return bean.getObject();
    }
}

关键点:

  • @Primary:确保当有多个同类型Bean时,这个被标记的Bean会被优先注入,保证了原有依赖禅道数据源的地方不会出错。
  • @MapperScan:指定了basePackages和sqlSessionFactoryRef,将zentao包下的所有Mapper都注册到这个SqlSessionFactory。
  • MybatisSqlSessionFactoryBean:由于项目使用了MyBatis-Plus,这里需要引入其专用的工厂类,以支持MyBatis-Plus的功能。

5.2.2 自定义业务库数据源配置类 DtstackToolsDataSourceConfig

文件路径:src/main/java/com/changlu/dtstacktools/config/DtstackToolsDataSourceConfig.java

package com.changlu.dtstacktools.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
@MapperScan(
    basePackages = "com.changlu.dtstacktools.mapper.dtstacktools", // 指定Mapper所在的包
    sqlSessionFactoryRef = "dtstackToolsSqlSessionFactory"          // 指定使用的SqlSessionFactory
)
public class DtstackToolsDataSourceConfig {

    @Bean(name = "DtstackToolsDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.dtstacktools") // 读取配置
    public DataSource knowledgeDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dtstackToolsSqlSessionFactory")
    public SqlSessionFactory dtstackToolsSqlSessionFactory(@Qualifier("DtstackToolsDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 指定该数据源对应的XML文件路径
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources("classpath:mapper/dtstacktools/*.xml"));
        return bean.getObject();
    }
}

说明:

  • 这个配置类没有使用@Primary,因为dtstacktools只是一个辅助库。
  • SqlSessionFactoryBean使用的是MyBatis的原生工厂类,因为AiImageVisionMapper是一个纯MyBatis的Mapper,没有使用MyBatis-Plus的增强功能。
  • 同样通过@MapperScan指定了Mapper接口的扫描路径和对应的SqlSessionFactory。

5.3 迁移Mapper和XML文件

image-20260321223429352

image-20260321223445276

5.3.1 创建新的Mapper和XML

我们新增了一个用于AI图像识别的Mapper和对应的XML文件。

  • Mapper接口:src/main/java/com/changlu/dtstacktools/mapper/dtstacktools/AiImageVisionMapper.java
package com.changlu.dtstacktools.mapper.dtstacktools;

import com.changlu.dtstacktools.module.ai.task.demo.bugvision.pojo.AiImageVisionPojo;
import org.apache.ibatis.annotations.Param;
import java.util.List;

/**
 * AI 图像识别 DAO
 * @author changlu
 * @since 2026-03-21
 */
public interface AiImageVisionMapper {
    int insert(AiImageVisionPojo pojo);
    int deleteById(@Param("id") Long id);
    int update(AiImageVisionPojo pojo);
    AiImageVisionPojo selectById(@Param("id") Long id);
    List<AiImageVisionPojo> selectList(AiImageVisionPojo query);
}
  • XML文件:src/main/resources/mapper/dtstacktools/AiImageVisionMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.changlu.dtstacktools.mapper.dtstacktools.AiImageVisionMapper">
    <resultMap id="BaseResultMap" type="com.changlu.dtstacktools.module.ai.task.demo.bugvision.pojo.AiImageVisionPojo">
        <id column="id" property="id" />
        <result column="zentao_id" property="zentaoId" />
        <result column="image_url" property="imageUrl" />
        <!-- 其他字段映射 -->
    </resultMap>
    <!-- SQL语句 -->
</mapper>

5.3.2 迁移原有Mapper和XML

我们将原有的ZTBugMapper、ZtActionMapper等移动到zentao包下,并修改包名。

  • Mapper接口:移动到 src/main/java/com/changlu/dtstacktools/mapper/zentao/
  • XML文件:移动到 src/main/resources/mapper/zentao/
  • 修改包名和命名空间:将所有Java文件和XML文件中的package和namespace改为新的包名com.changlu.dtstacktools.mapper.zentao。

5.4 调整启动类和测试类

最后,我们需要修改启动类,将全局的@MapperScan注释掉,因为现在每个数据源都有自己的@MapperScan了。

// DtstacktoolsApplication.java
@SpringBootApplication
//@MapperScan("com.changlu.dtstacktools.mapper") // 注释掉全局扫描
@EnableScheduling
public class DtstacktoolsApplication {
    // ...
}

在测试类中,我们可以分别注入不同数据源的Mapper进行测试。

六、测试验证

我们来写一个简单的单元测试,验证多数据源是否配置成功。

文件路径:src/test/java/com/changlu/dtstacktools/DtstacktoolsApplicationTests.java

package com.changlu.dtstacktools;

import com.changlu.dtstacktools.mapper.dtstacktools.AiImageVisionMapper;
import com.changlu.dtstacktools.mapper.zentao.ZTBugMapper;
import com.changlu.dtstacktools.module.ai.task.demo.bugvision.pojo.AiImageVisionPojo;
import com.changlu.dtstacktools.service.BugService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;

@SpringBootTest
class DtstacktoolsApplicationTests extends BaseIntegrationTest {


    @Autowired
    private AiImageVisionMapper aiImageVisionMapper; // 依赖于自定义业务库数据源


    @Test
    void testImageVision() {
        // 测试自定义数据源是否能正常工作
        List<AiImageVisionPojo> list = aiImageVisionMapper.selectList(null);
        System.out.println(list);
    }
}

运行结果:

  • testZTBugMapper 能够正常从禅道库中查询出数据。
  • testImageVision 能够正常从dtstacktools库中查询出数据。

完美!两个数据源都正常工作了。


七、总结说明

通过以上步骤,我们成功地在Spring Boot应用中配置了多个MySQL数据源。

核心要点回顾:

  1. 配置分离:在application.yml中为每个数据源定义独立的配置节。
  2. 配置类驱动:为每个数据源创建一个独立的@Configuration配置类,用于生成DataSource和SqlSessionFactory Bean。
  3. Mapper隔离:使用@MapperScan注解,将不同业务领域的Mapper接口扫描到不同的SqlSessionFactory中,实现代码级别的物理隔离。
  4. 主数据源标识:如果有一个数据源是项目的核心,需要使用@Primary注解标记,确保依赖注入时不发生歧义。

这种方案将配置与代码分离,结构清晰,易于维护和扩展。无论是新增业务库,还是接入第三方系统数据库,都可以轻松应对。希望这篇博客能为你解决实际开发中的多数据源问题提供一些帮助。

评论区请在客户端页面查看