Spring Boot多数据源案例

wen java案例 2

Spring Boot多数据源案例:从入门到实战的架构设计指南

目录导读

  1. 为什么需要多数据源?——场景与痛点
  2. 核心方案对比:动态数据源 vs 独立配置
  3. 实战案例:基于Spring Boot + MyBatis的多数据源路由
  4. 事务管理与坑点规避
  5. 常见问题问答(FAQ)
  6. 性能优化与监控建议

为什么需要多数据源?——场景与痛点

在微服务或单体应用中,单一数据库往往无法满足复杂业务,典型场景包括:

Spring Boot多数据源案例

  • 读写分离:主库负责写入,从库负责查询,分摊压力。
  • 多租户隔离:不同客户数据存储在不同库中,实现物理隔离。
  • 业务分库:订单库、用户库、日志库拆分,独立维护。
  • 第三方数据接入:对接老系统或外部API,需同时访问多个异构数据库。

痛点

  • 配置繁琐:每个数据源都需要独立的DataSourceSqlSessionFactoryTransactionManager
  • 切换逻辑易错:硬编码@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:支持,只需在DynamicDataSourcetargetDataSources中放入更多键值对,并通过负载均衡策略(如随机、轮询)选择目标。

Q2:如何保证事务一致性? A:本地事务无法跨库,建议采用分布式事务(如Seata、LCN),或调整业务设计为最终一致性。

Q3:能否与JPA共存? A:可以,但需注意JPA的EntityManagerFactoryTransactionManager需要分别绑定到不同数据源,配置复杂度会上升。

Q4:如何监控当前线程使用的数据源? A:在日志中打印DataSourceContextHolder.get()的值,或使用MDC记录。


性能优化与监控建议

  • 连接池调优:Druid可配置初始化大小、最大活跃数、空闲检测间隔等。
  • 读写分离策略:在服务层通过注解或API网关层做路由,避免底层SQL判断。
  • 使用@Transactional(readOnly = true):对只读操作,可提示底层驱动优化。
  • 集成监控:通过actuator暴露数据源连接池状态,结合Prometheus+Grafana可视化。

延伸建议:对于数量超过3个的数据源,考虑引入分布式数据库中间件(如ShardingSphere),它天然支持多数据源、读写分离和分库分表,减少手动维护成本。


本文基于实际项目经验及主流社区实践总结,适用于Spring Boot 2.x/3.x版本。

抱歉,评论功能暂时关闭!