在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的过渡期,不建议作为长期架构。
