微信优化

weixinyouhua

如何配置多个数据源,多数据源动态切换有哪些坑?

2026-08-31 11:26:42

在Spring Boot中通过自定义DataSource配置类、绑定不同前缀的配置项,并配合@Primary和@Qualifier注解,即可在单个应用中同时连接多个数据库,实现读写分离或业务隔离。

很多人第一次接触配置多个数据源时,第一反应是“直接在application.yml里写两个url不就完了?”实际上远没那么简单,Spring Boot默认只自动装配一个数据源,一旦你在配置里出现两个数据源,应用启动就会直接报错,因为DataSourceAutoConfiguration不知道该听谁的,正确的做法是手动创建两个数据源Bean,并明确告诉容器哪个是主数据源,下面这套方案,是我在实际项目中反复验证过的,你照着做基本不会踩坑。

多数据源的核心配置:从yml到两个Bean

在动手写代码之前,先理清楚整体思路,我们要做的是把默认的自动配置排除掉,自己接管数据源的创建过程,这样做的优势很直接:你想配几个就配几个,每个数据源拥有独立的连接池、事务管理器和实体扫描路径。

第一步:修改application.yml,定义两个数据源

我习惯用primary和secondary来区分主从,或者用业务名称比如order和user,下面这个例子用的是最常见的读写分离场景:

spring:
  autoconfigure:
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  datasource:
    primary:
      jdbc-url: jdbc:mysql://localhost:3306/main_db
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      jdbc-url: jdbc:mysql://localhost:3306/report_db
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver

注意这里用的是jdbc-url而不是url,这是很多教程忽略的坑:当数据源不是由Spring Boot自动配置创建时,url属性不会被DataSourceBuilder识别,你会看到一个“Failed to determine a suitable driver class”的报错,改成jdbc-url就能完美避开。

第二步:创建主数据源配置类

在主数据源配置类上,你需要做三件事:标注@Configuration、指定扫描的Mapper包、设置@Primary。

@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", 
            sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryDataSourceConfig {
    @Primary
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }
    @Primary
    @Bean(name = "primarySqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }
    @Primary
    @Bean(name = "primarySqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory factory) {
        return new SqlSessionTemplate(factory);
    }
}

如何配置多个数据源,多数据源动态切换有哪些坑?

@Primary的作用是告诉Spring:当有多个同类型Bean时,优先注入带这个注解的那个,如果不加,启动时你会收到NoUniqueBeanDefinitionException,因为容器里有多个DataSource实例,Spring不知道你要用哪个。

第三步:创建从数据源配置类

从数据源的写法几乎一模一样,唯一的区别是去掉@Primary,并换成不一样的包名和Bean名。

@Configuration
@MapperScan(basePackages = "com.example.mapper.secondary", 
            sqlSessionTemplateRef = "secondarySqlSessionTemplate")
public class SecondaryDataSourceConfig {
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }
    @Bean(name = "secondarySqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }
    @Bean(name = "secondarySqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("secondarySqlSessionFactory") SqlSessionFactory factory) {
        return new SqlSessionTemplate(factory);
    }
}

多数据源实现读写分离时的注意点

很多人问配置多个数据源有什么要注意的,尤其是想用多数据源实现读写分离的场景,这里有几个实际运行中才会遇到的关键点,建议你提前收藏。

事务管理器的拆分

如果一个方法需要同时操作两个库的数据,事务该归谁管?答案是:分布式事务,但在大多数中小项目中,更现实的做法是让主数据源管理默认事务,从数据源的方法单独声明事务管理器。

@Bean(name = "primaryTransactionManager")
@Primary
public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "secondaryTransactionManager")
public DataSourceTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

使用时在Service方法上用@Transactional("primaryTransactionManager")显式指定,如果不指定,默认走@Primary那个事务管理器,这会导致从库的方法事务不生效。

动态切换数据源的进阶方案

如果你不想在每个Service方法里写死数据源,可以用AbstractRoutingDataSource做一个动态路由,这属于配置多个数据源的进阶玩法,适合业务场景复杂但又不愿意引入中间件的团队,核心思路是:用ThreadLocal保存当前要用的数据源key,用

如何配置多个数据源,多数据源动态切换有哪些坑?

afterDetermination方法拦截注解,实现自动切换。

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

动态路由的优点是代码侵入小,但调试难度会明显上升,特别是当线程池复用线程时,ThreadLocal里的key没有及时清理,就会出现“跑到从库的数据源去查主库数据”这种诡异问题,所以我个人建议:如果项目刚起步,老老实实用两套配置类最稳妥。

多数据源和单数据源怎么选

这个问题经常出现在技术选型讨论中。多数据源和单数据源的区别,本质上不是性能优劣,而是架构取舍。

对比维度 单数据源 多数据源
配置复杂度 低,开箱即用 较高,需要手动配置Bean
故障隔离 共享一个库,故障影响所有业务 按库隔离,单个库宕机不影响其他
开发效率 高,无需关心数据源切换 低,要管理事务和Mapper扫描路径
适用场景 小型系统、单体应用 微服务拆库、读写分离、报表独立库

业内专家指出,多数据源的适用范围主要集中在两个场景:一是不同业务域的数据天然不需要强一致,比如订单库和日志库;二是报表查询压力大,需要独立从库来避免拖垮主库。

如果你是个人项目或者起步阶段的创业项目,用单数据源完全够用,强行上多数据源,反而会因为事务悬挂、连接池耗尽问题把自己拖垮。配置多个数据源的正确姿势是:明确知道自己要解决什么问题,再动手去配。

常见报错和解决办法

很多人在配置多数据源时都会遇到报错,下面这几个高频问题,你几乎一定会碰到其中一个。

Failed to configure a DataSource

这个报错的意思是Spring Boot找不到数据库连接配置,原因是你的application.yml里没有使用spring.datasource.url这个默认属性,而是用了自定义前缀,解决办法很简单:在启动类上排除自动配置。

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

排除了自动配置后,Spring就不会再尝试用默认配置去建数据源,全部交给你手写的配置类管理。

Invalid bound statement (not found)

这个报错通常是Mapper扫描路径写错了,你配置了@MapperScan扫描com.example.mapper.primary,但XML文件放在resources/mapper/primary下,却没有在application.yml里声明mybatis.mapper-locations

如何配置多个数据源,多数据源动态切换有哪些坑?

,加上即可:

mybatis:
  mapper-locations: classpath:mapper//.xml

多数据源配置了却还是连同一个库

很多情况下,这是因为你在实体类或方法上使用了@Transactional,但没有指定事务管理器,事务管理器默认绑定主数据源,所以从库的操作实际上还是走的主库连接,检查一下你的@Transactional注解,改成@Transactional(transactionManager = "secondaryTransactionManager")。

多数据源项目中的配置目录划分

如果你在一个团队里负责基础架构,多数据源项目怎么组织代码是个很实际的问题,下面是我觉得比较清晰的一种结构,分享给你参考:

  • config/ 存放数据源配置类、事务管理配置类
  • datasource/ 存放动态路由、ThreadLocal工具类
  • mapper/primary/ 主库的Mapper接口
  • mapper/secondary/ 从库的Mapper接口
  • resources/mapper/primary/ 主库对应的XML文件
  • resources/mapper/secondary/ 从库对应的XML文件

这样划分的好处是,新人进来一看目录就明白哪个Mapper对应哪个数据库,如果你把所有的Mapper混在一起,过一个月你自己也分不清哪些操作走主库、哪些走从库。

关于多数据源的一些补充建议

最后说几句实在话。配置多个数据源不是万金油,它带来的复杂度远比你想象中高,连接池配置、监控告警、慢查询日志、数据迁移,每一项都要付出额外成本,如果你的应用读多写少,考虑先引入读写分离中间件(比如ShardingSphere),而不是手动写死两个数据源配置。

如果你的业务必须手写多个数据源,请记住三个原则:主数据源必须加@Primary、不同Mapper包必须隔离清晰、事务管理器必须显式指定,这三条记住了,你在生产环境里翻车的概率至少降低八成。

配置多个数据源常见问题解答

配置多个数据源时,Spring Boot会自动扫描所有Mapper吗?

不会,当你手动创建数据源配置类后,Spring Boot的自动配置已经失效,每个数据源配置类中的@MapperScan只负责扫描自己指定的包,如果一个Mapper接口没有被任何@MapperScan覆盖到,对应的SQL语句将无法执行,运行时会抛出Invalid bound statement异常。

多数据源可以同时支持JPA和MyBatis吗?

可以,但复杂度较高,你需要在配置类中分别定义两个工厂Bean,并将它们的包扫描范围错开,一个数据源配LocalContainerEntityManagerFactoryBean用于JPA,另一个配SqlSessionFactory用于MyBatis,同时要注意事务管理器的隔离,JPA使用JpaTransactionManager,MyBatis使用DataSourceTransactionManager,两者互不干扰,行业共识认为,这种方式主要用于在一个项目中逐步从JPA迁移到MyBatis的过渡期,不建议作为长期架构。

相关文章

2024年,SaaS软件行业碰到获客难、增长慢等问题吗?

我们努力让每一次邂逅总能超越期待