logo

SpringBoot多数据源配置实战与避坑指南

作者:KAKAKA2026.11.07 02:02浏览量:0

简介:本文系统梳理SpringBoot项目中配置多数据源的适用场景、完整流程、关键代码与常见问题。读者可掌握如何按包路径隔离数据源、配置独立事务管理器,并避开多数据源使用中的典型陷阱,适合有MyBatis与SpringBoot基础的后端开发者参考。

SpringBoot多数据源配置实战与避坑指南

多数据源是很多后端项目中绕不开的话题,但真正把它用对、用稳并不容易。本文不堆砌概念,而是从实际开发视角出发,把“什么时候该用、怎么配置、如何避坑”讲清楚,并给出一套可以在SpringBoot项目里直接落地的配置方案。

一、先泼一盆冷水:你未必需要多数据源

很多开发者在刚接触多数据源时,第一反应是“我能不能给我的项目也接上”。但在动手之前,请先确认你真的有业务需求,而不是技术冲动。

多数据源的本质是:一个应用同时连接并操作多个物理数据库,并且在代码层面能够区分“哪个Mapper走哪个库”。这会引入额外的配置成本、事务管理复杂度和排查难度。

通常来说,只有以下几种业务场景才值得引入多数据源:

  • 业务上需要关联查询多个不同库的数据,且公司没有统一的数据同步中间件;
  • 老系统迁移期间,需要同时读写新库和老库进行数据校验;
  • 读写分离需求明显,但暂时不具备引入分库分表或数据路由中间件的条件。

如果你的项目是典型的单库业务,强行引入多数据源只会让事务边界变模糊、连接池资源容易被耗尽、后续维护难度成倍上升。所以在配置之前,先确认自己属于上述场景之一。

二、整体配置思路

SpringBoot要实现多数据源,核心思路是:手动构建多个DataSource,并分别绑定不同的Mapper扫描路径和SqlSessionFactory。默认的自动配置此时需要退居幕后,由我们手工接管数据源与MyBatis的装配。

整个配置流程可以概括为以下几步:

  1. 引入JDBC、MyBatis和连接池相关依赖;
  2. 在配置文件中声明多套数据源参数;
  3. 编写配置类,创建多个DataSource并标记主从;
  4. 为每个DataSource配置对应的SqlSessionFactory;
  5. 通过MapperScannerConfigurer把不同包路径的Mapper绑定到各自的SqlSessionFactory上;
  6. 配置事务管理器,确保不同数据源的事务互不干扰。

三、依赖准备:别让缺包成为第一个坑

很多多数据源问题到最后发现根本不是逻辑问题,而是依赖没引全。一个完整的SpringBoot多数据源项目,至少需要以下依赖:

  • spring-boot-starter-web:提供基础Web能力,便于接口验证;
  • mybatis-spring-boot-starter:MyBatis与SpringBoot的整合启动器;
  • 数据库驱动:根据实际使用的数据库选择,常见为关系型数据库驱动;
  • 连接池依赖:可以使用SpringBoot默认的连接池,也可以引入更强大的连接池组件以便于监控和参数调优。

以SpringBoot 2.7.x版本为例,基础依赖结构如下(版本号可根据项目实际调整):

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.7.14</version>
  5. <relativePath/>
  6. </parent>
  7. <dependencies>
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-starter-web</artifactId>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.mybatis.spring.boot</groupId>
  14. <artifactId>mybatis-spring-boot-starter</artifactId>
  15. <version>2.2.2</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>mysql</groupId>
  19. <artifactId>mysql-connector-java</artifactId>
  20. <scope>runtime</scope>
  21. </dependency>
  22. <dependency>
  23. <groupId>com.alibaba</groupId>
  24. <artifactId>druid-spring-boot-starter</artifactId>
  25. <version>1.2.16</version>
  26. </dependency>
  27. </dependencies>

注意:如果引入的是连接池启动器,它会覆盖SpringBoot默认的Hikari连接池,因此配置文件中与连接池相关的参数也要相应调整,避免出现“配置了但没生效”的情况。

四、配置文件:多套数据源参数要清晰隔离

多数据源的配置不宜混在一起,建议用前缀区分不同数据源的连接信息。例如主库用spring.datasource.primary前缀,从库用spring.datasource.secondary前缀:

  1. # 主数据源
  2. spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/main_db?useSSL=false&serverTimezone=Asia/Shanghai
  3. spring.datasource.primary.username=root
  4. spring.datasource.primary.password=123456
  5. spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
  6. # 第二数据源
  7. spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/secondary_db?useSSL=false&serverTimezone=Asia/Shanghai
  8. spring.datasource.secondary.username=root
  9. spring.datasource.secondary.password=123456
  10. spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

重要提示:在使用自定义数据源时,SpringBoot默认的spring.datasource.url配置项不再适用。请务必使用jdbc-url作为自定义DataSource的属性名,否则自定义数据源在绑定属性时会因为找不到url属性而初始化失败。

五、配置类编写:逐个构建数据源和SqlSessionFactory

这是整个多数据源配置的核心环节。我们需要编写一个配置类,完成以下工作:

5.1 创建两个DataSource

使用配置类结合@ConfigurationProperties注解读取不同前缀的配置,并手动创建数据源对象:

  1. @Configuration
  2. public class DataSourceConfig {
  3. @Bean(name = "primaryDataSource")
  4. @ConfigurationProperties(prefix = "spring.datasource.primary")
  5. public DataSource primaryDataSource() {
  6. return DataSourceBuilder.create().build();
  7. }
  8. @Bean(name = "secondaryDataSource")
  9. @ConfigurationProperties(prefix = "spring.datasource.secondary")
  10. public DataSource secondaryDataSource() {
  11. return DataSourceBuilder.create().build();
  12. }
  13. }

如果使用连接池组件,建议直接在DataSource实现类上设置连接池相关参数,以便后续做SQL监控和连接数管理。

5.2 配置主数据源的SqlSessionFactory

  1. @Bean(name = "primarySqlSessionFactory")
  2. public SqlSessionFactory primarySqlSessionFactory(
  3. @Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
  4. SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
  5. bean.setDataSource(dataSource);
  6. // 如果有XML映射文件,可以设置mapperLocations
  7. // bean.setMapperLocations(new PathMatchingResourcePatternResolver()
  8. // .getResources("classpath:mapper/primary/*.xml"));
  9. return bean.getObject();
  10. }

第二个数据源的SqlSessionFactory同理,只需替换数据源引用和Bean名称。

5.3 配置Mapper扫描路径

这一步决定了“哪个包下的Mapper接口使用哪个数据源”。我们通过MapperScannerConfigurer来实现:

  1. @Bean
  2. public MapperScannerConfigurer primaryMapperScanner() {
  3. MapperScannerConfigurer configurer = new MapperScannerConfigurer();
  4. configurer.setBasePackage("com.example.mapper.primary");
  5. configurer.setSqlSessionFactoryBeanName("primarySqlSessionFactory");
  6. return configurer;
  7. }
  8. @Bean
  9. public MapperScannerConfigurer secondaryMapperScanner() {
  10. MapperScannerConfigurer configurer = new MapperScannerConfigurer();
  11. configurer.setBasePackage("com.example.mapper.secondary");
  12. configurer.setSqlSessionFactoryBeanName("secondarySqlSessionFactory");
  13. return configurer;
  14. }

这样,mapper.primary包下的所有Mapper接口会自动使用主数据源对应的SqlSessionFactory,mapper.secondary包下的则使用第二数据源。包结构隔离是多数据源项目中最清晰、最容易维护的边界划分方式。

5.4 配置事务管理器

多数据源场景下,默认的事务管理器只能管理其中一个数据源。如果需要分别管理不同数据源的事务,需要为每个数据源配置独立的事务管理器,并通过@Transactional注解指定:

  1. @Bean(name = "primaryTransactionManager")
  2. public DataSourceTransactionManager primaryTransactionManager(
  3. @Qualifier("primaryDataSource") DataSource dataSource) {
  4. return new DataSourceTransactionManager(dataSource);
  5. }
  6. @Bean(name = "secondaryTransactionManager")
  7. public DataSourceTransactionManager secondaryTransactionManager(
  8. @Qualifier("secondaryDataSource") DataSource dataSource) {
  9. return new DataSourceTransactionManager(dataSource);
  10. }

使用时,在Service方法上显式指定事务管理器:

  1. @Transactional(transactionManager = "primaryTransactionManager")
  2. public void updatePrimaryData() {
  3. // 操作主库
  4. }
  5. @Transactional(transactionManager = "secondaryTransactionManager")
  6. public void updateSecondaryData() {
  7. // 操作第二数据源
  8. }

需要特别强调:多数据源下无法天然支持跨库的强一致事务。如果业务上必须保证两个库的数据同时成功或同时失败,应当考虑引入分布式事务方案,而不是简单地在同一个本地事务里混用两个数据源。

六、常见问题与排查思路

即使配置完全按照上述步骤执行,实际开发中依然会遇到不少问题。以下是最常见的几类:

6.1 启动时提示找不到DataSource

这通常是因为SpringBoot的自动配置和手动配置冲突,或者自定义数据源没有正确绑定配置。检查配置前缀是否与@ConfigurationProperties中的前缀一致,并确认是否在启动类上排除了MyBatis自动配置(如有必要)。

6.2 Mapper走了错误的数据源

几乎100%是因为Mapper所在的包路径与MapperScannerConfigurer中指定的basePackage不匹配。务必保证各数据源对应的Mapper严格隔离在不同包下,不要出现交叉放的情况。

6.3 事务不生效

排查顺序建议为:

  • 确认@Transactional注解是否指定了正确的事务管理器Bean名称;
  • 确认当前方法是否是通过Spring代理调用的(同类内部调用会导致事务失效);
  • 确认该Mapper对应的数据源是否确实被指定的事务管理器管理。

6.4 连接池参数未生效

如果使用连接池组件,需要确认配置文件中的参数前缀与连接池启动器要求的前缀一致。例如引入Druid的启动器后,建议使用spring.datasource.druid.xxx或自定义前缀,而不是沿用Hikari的参数格式。

七、写在最后

多数据源配置本身并不复杂,复杂的是它带来的后续影响:事务边界如何划分、连接池资源如何隔离、业务代码如何避免误用数据源。因此,在引入之前先想清楚业务是否真的需要,在配置时严格控制包结构边界,在使用时明确事务管理器,这样才能让多数据源成为解决业务的利器,而不是维护的噩梦。

发表评论

活动