
介绍
在大型项目开发中,我们经常会遇到一个应用需要连接多个数据库的场景。比如,在一个后台管理系统中,我们可能需要连接一个核心的业务数据库,同时还需要读取另一个系统的数据(如禅道、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中。
简单来说,我们的思路就是:
- 在
application.yml中为每个数据库定义独立的配置节。 - 创建两个配置类,分别对应一个数据源,并在配置类中生成对应的
DataSource和SqlSessionFactoryBean。 - 使用
@MapperScan注解,将不同包下的Mapper接口指定给不同的SqlSessionFactory。
这样,当我们在service层调用zentao包下的Mapper时,它会自动使用禅道的数据源;调用dtstacktools包下的Mapper时,则会使用自定义业务库的数据源。两者互不干扰。
三、方案对比
在着手实现之前,我们先来对比一下常见的两种方案:
-
方案一:使用
@Primary+ 多数据源配置类- 原理:为默认数据源(如原来的禅道库)添加
@Primary注解,使其成为首选数据源。然后为其他数据源创建独立的配置类。 - 优点:代码结构清晰,职责分明,易于维护和扩展。
- 缺点:配置相对复杂,需要手动管理
SqlSessionFactory。 - 适用场景:绝大多数多数据源场景,特别是新增数据源时,推荐使用。
- 原理:为默认数据源(如原来的禅道库)添加
-
方案二:使用动态数据源(
AbstractRoutingDataSource)- 原理:创建一个主数据源,内部维护一个
Map,通过lookupKey来动态决定使用哪个数据源。 - 优点:对业务代码侵入小,可以通过AOP或注解动态切换。
- 缺点:实现更复杂,需要处理事务、连接池等问题,适合数据源切换频繁的场景。
- 适用场景:读写分离、分库分表等场景。
- 原理:创建一个主数据源,内部维护一个
对于我们这种新增一个独立业务库的场景,方案一(多数据源配置类)足够简洁且易于维护,我们这次就采用这种方案。
四、实现思路(架构设计)
根据上述思路,我们的整体架构设计如下:
- 配置文件重构:将原有的单一数据源配置,拆分为
spring.datasource.zentao和spring.datasource.dtstacktools两部分。 - 配置类创建:
ZentaoDataSourceConfig:负责创建禅道数据源DataSource和SqlSessionFactory,并扫描com.changlu.dtstacktools.mapper.zentao包下的Mapper。DtstackToolsDataSourceConfig:负责创建自定义业务库数据源DataSource和SqlSessionFactory,并扫描com.changlu.dtstacktools.mapper.dtstacktools包下的Mapper。
- Mapper迁移:将原有的禅道相关Mapper(如
ZTBugMapper)移动到zentao包下,并将新创建的AiImageVisionMapper放在dtstacktools包下。 - 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 创建数据源配置类
接下来,我们为每个数据源创建一个配置类。

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文件


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数据源。
核心要点回顾:
- 配置分离:在
application.yml中为每个数据源定义独立的配置节。 - 配置类驱动:为每个数据源创建一个独立的
@Configuration配置类,用于生成DataSource和SqlSessionFactoryBean。 - Mapper隔离:使用
@MapperScan注解,将不同业务领域的Mapper接口扫描到不同的SqlSessionFactory中,实现代码级别的物理隔离。 - 主数据源标识:如果有一个数据源是项目的核心,需要使用
@Primary注解标记,确保依赖注入时不发生歧义。
这种方案将配置与代码分离,结构清晰,易于维护和扩展。无论是新增业务库,还是接入第三方系统数据库,都可以轻松应对。希望这篇博客能为你解决实际开发中的多数据源问题提供一些帮助。
评论区请在客户端页面查看