Spring Boot多数据源案例:从入门到实战的架构设计指南
目录导读
- 为什么需要多数据源?——场景与痛点
- 核心方案对比:动态数据源 vs 独立配置
- 实战案例:基于Spring Boot + MyBatis的多数据源路由
- 事务管理与坑点规避
- 常见问题问答(FAQ)
- 性能优化与监控建议
为什么需要多数据源?——场景与痛点
在微服务或单体应用中,单一数据库往往无法满足复杂业务,典型场景包括:

- 读写分离:主库负责写入,从库负责查询,分摊压力。
- 多租户隔离:不同客户数据存储在不同库中,实现物理隔离。
- 业务分库:订单库、用户库、日志库拆分,独立维护。
- 第三方数据接入:对接老系统或外部API,需同时访问多个异构数据库。
痛点:
- 配置繁琐:每个数据源都需要独立的
DataSource、SqlSessionFactory、TransactionManager。 - 切换逻辑易错:硬编码
@Primary或线程本地变量管理不当会导致连接串库。 - 事务边界模糊:多库操作时,本地事务无法跨库回滚。
核心方案对比:动态数据源 vs 独立配置
方案A:动态数据源(推荐)
基于AbstractRoutingDataSource实现,通过ThreadLocal或注解动态切换目标数据源。
优点:切换灵活,代码统一,支持AOP切面。 缺点:需自己管理上下文,注意线程复用后的清理。
方案B:独立配置多套Mapper
为每个数据源独立创建SqlSessionFactory,通过包名或注解区分Mapper。
优点:结构清晰,事务隔离性好。 缺点:代码冗余,维护成本高。
绝大多数场景推荐方案A,配合Spring Boot的自动配置可高效实现。
实战案例:基于Spring Boot + MyBatis的多数据源路由
1 依赖与基础配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
2 配置文件(application.yml)
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
3 动态数据源核心组件
数据源路由枚举与上下文
public enum DataSourceType {
MASTER, SLAVE
}
public class DataSourceContextHolder {
private static final ThreadLocal<DataSourceType> CONTEXT = new ThreadLocal<>();
public static void set(DataSourceType type) { CONTEXT.set(type); }
public static DataSourceType get() { return CONTEXT.get(); }
public static void clear() { CONTEXT.remove(); }
}
继承AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.get();
}
}
配置类
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER, master);
targetDataSources.put(DataSourceType.SLAVE, slave);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(master); // 默认主库
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
AOP切面实现自动切换
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(ds)")
public void switchDataSource(JoinPoint point, @annotation ds) {
DataSourceContextHolder.set(ds.value());
}
@After("@annotation(ds)")
public void restoreDataSource() {
DataSourceContextHolder.clear();
}
}
自定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
DataSourceType value() default DataSourceType.MASTER;
}
4 使用示例
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@DataSource(DataSourceType.MASTER)
public void createOrder(Order order) {
orderMapper.insert(order);
}
@DataSource(DataSourceType.SLAVE)
public List<Order> listOrders() {
return orderMapper.selectAll();
}
}
事务管理与坑点规避
1 关键坑点
- 连接串库:使用
@Transactional时,事务管理器会提前获取连接,导致动态切换失效。 - 线程池复用:未清理
ThreadLocal,导致后续请求错误使用旧数据源。 - Lazy初始化:IoC容器加载顺序问题,需在
@Primary上指定默认数据源。
2 解决方案
- 对于需要事务的写操作,手动指定
DataSourceTransactionManager并设置路由。 - 在
finally中务必调用clear()方法。 - 配置
@Primary标记的默认数据源,防止启动失败。
常见问题问答(FAQ)
Q1:动态数据源支持多主多从吗?
A:支持,只需在DynamicDataSource的targetDataSources中放入更多键值对,并通过负载均衡策略(如随机、轮询)选择目标。
Q2:如何保证事务一致性? A:本地事务无法跨库,建议采用分布式事务(如Seata、LCN),或调整业务设计为最终一致性。
Q3:能否与JPA共存?
A:可以,但需注意JPA的EntityManagerFactory与TransactionManager需要分别绑定到不同数据源,配置复杂度会上升。
Q4:如何监控当前线程使用的数据源?
A:在日志中打印DataSourceContextHolder.get()的值,或使用MDC记录。
性能优化与监控建议
- 连接池调优:Druid可配置初始化大小、最大活跃数、空闲检测间隔等。
- 读写分离策略:在服务层通过注解或API网关层做路由,避免底层SQL判断。
- 使用
@Transactional(readOnly = true):对只读操作,可提示底层驱动优化。 - 集成监控:通过
actuator暴露数据源连接池状态,结合Prometheus+Grafana可视化。
延伸建议:对于数量超过3个的数据源,考虑引入分布式数据库中间件(如ShardingSphere),它天然支持多数据源、读写分离和分库分表,减少手动维护成本。
本文基于实际项目经验及主流社区实践总结,适用于Spring Boot 2.x/3.x版本。