Spring Boot多数据源实现方案深度对比:优缺点分析与实战指南

目录

一、为什么需要多数据源?

二、5大主流实现方案对比

三、方案实现详解

方案1:手动配置多DataSource(基础版)

方案2:AbstractRoutingDataSource(动态路由)

方案3:MyBatis-Plus多数据源(推荐)

方案4:JPA多数据源配置

方案5:ShardingSphere(企业级方案)

四、事务管理解决方案

1. 分布式事务(XA协议)

2. BASE柔性事务

五、性能优化策略

1. 连接池配置优化

2. 读写分离权重配置

3. 多数据源监控

六、生产环境避坑指南

1. 连接泄露检测

2. 多数据源启动顺序

3. 跨库关联查询

七、方案选型决策树

八、总结与最佳实践


一、为什么需要多数据源?

在复杂业务场景下,多数据源成为刚需:

Spring Boot多数据源实现方案深度对比:优缺点分析与实战指南_第1张图片

二、5大主流实现方案对比

方案 核心思路 优点 缺点 适用场景
手动配置多DataSource 显式创建多个DataSource Bean 实现简单,直观可控 事务管理复杂,需手动切换 简单场景(2-3个数据源)
AbstractRoutingDataSource 动态路由数据源 灵活切换,无代码侵入 需解决事务传播问题 动态数据源场景(多租户)
MyBatis-Plus多数据源 注解驱动切换 开箱即用,集成度高 依赖特定框架 MyBatis技术栈项目
JPA多数据源 实体管理器工厂分离 符合JPA标准 配置繁琐,性能开销 纯JPA项目
ShardingSphere 分布式数据库中间件 功能强大,支持分库分表 学习曲线陡峭 大型分布式系统

三、方案实现详解

方案1:手动配置多DataSource(基础版)

实现步骤

@Configuration
public class DataSourceConfig {
    
    // 主数据源
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    // 从数据源
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
}

// 使用示例
@Service
public class UserService {
    private final JdbcTemplate masterJdbcTemplate;
    private final JdbcTemplate slaveJdbcTemplate;
    
    public UserService(
            @Qualifier("masterDataSource") DataSource master,
            @Qualifier("slaveDataSource") DataSource slave) {
        this.masterJdbcTemplate = new JdbcTemplate(master);
        this.slaveJdbcTemplate = new JdbcTemplate(slave);
    }
    
    public void updateUser(User user) {
        // 写操作使用主库
        masterJdbcTemplate.update("UPDATE user SET name=? WHERE id=?", 
                user.getName(), user.getId());
    }
    
    public User getUser(long id) {
        // 读操作使用从库
        return slaveJdbcTemplate.queryForObject(
                "SELECT * FROM user WHERE id=?", 
                new BeanPropertyRowMapper<>(User.class), 
                id);
    }
}

事务管理挑战

@Transactional // 默认只能使用主数据源
public void crossDbOperation() {
    // 操作主库
    masterRepo.save(entity);
    
    // 操作从库(将使用错误的数据源!)
    slaveRepo.query(...);
}

方案2:AbstractRoutingDataSource(动态路由)

核心实现

public class DynamicDataSource extends AbstractRoutingDataSource {
    
    // 线程安全的数据源上下文
    private static final ThreadLocal CONTEXT = 
        new ThreadLocal<>();
    
    public static void setDataSource(String ds) {
        CONTEXT.set(ds);
    }
    
    public static void clear() {
        CONTEXT.remove();
    }
    
    @Override
    protected Object determineCurrentLookupKey() {
        return CONTEXT.get();
    }
}

// 配置类
@Bean
public DataSource dynamicDataSource(
        @Qualifier("masterDataSource") DataSource master,
        @Qualifier("slaveDataSource") DataSource slave) {
    
    Map targetDataSources = new HashMap<>();
    targetDataSources.put("master", master);
    targetDataSources.put("slave", slave);
    
    DynamicDataSource ds = new DynamicDataSource();
    ds.setDefaultTargetDataSource(master);
    ds.setTargetDataSources(targetDataSources);
    return ds;
}

使用AOP自动切换

@Aspect
@Component
public class DataSourceAspect {
    
    @Before("@annotation(readOnly)")
    public void setReadOnly(ReadOnly readOnly) {
        DynamicDataSource.setDataSource("slave");
    }
    
    @After("@annotation(readOnly)")
    public void clearDataSource(ReadOnly readOnly) {
        DynamicDataSource.clear();
    }
}

// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {}

// 业务层使用
@Service
public class UserService {
    @ReadOnly
    public User getUser(long id) {
        // 自动使用从库
        return userMapper.selectById(id);
    }
}

方案3:MyBatis-Plus多数据源(推荐)

依赖引入


    com.baomidou
    dynamic-datasource-spring-boot-starter
    3.6.1

配置示例

spring:
  datasource:
    dynamic:
      primary: master
      strict: true
      datasource:
        master:
          url: jdbc:mysql://master:3306/db
          username: root
          password: master_pwd
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave1:
          url: jdbc:mysql://slave1:3306/db
          username: read_user
          password: read_pwd
        slave2:
          url: jdbc:mysql://slave2:3306/db

注解驱动切换

@Service
public class OrderService {
    
    // 默认使用主数据源
    public void createOrder(Order order) {
        orderMapper.insert(order);
    }
    
    @DS("slave1") // 指定数据源
    public Order getOrder(long id) {
        return orderMapper.selectById(id);
    }
    
    @DS("slave2") // 使用另一个从库
    public List listOrders() {
        return orderMapper.selectList(null);
    }
}

方案4:JPA多数据源配置

实体管理器分离

@Configuration
@EnableJpaRepositories(
    basePackages = "com.example.repository.master",
    entityManagerFactoryRef = "masterEntityManager",
    transactionManagerRef = "masterTransactionManager"
)
public class MasterConfig {
    
    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean masterEntityManager(
            EntityManagerFactoryBuilder builder,
            @Qualifier("masterDataSource") DataSource ds) {
        return builder
            .dataSource(ds)
            .packages("com.example.entity.master")
            .persistenceUnit("master")
            .build();
    }
    
    @Primary
    @Bean
    public PlatformTransactionManager masterTransactionManager(
            @Qualifier("masterEntityManager") EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}

// 从数据源配置类似(省略@Primary注解)

方案5:ShardingSphere(企业级方案)

分库分表示例

spring:
  shardingsphere:
    datasource:
      names: ds0, ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://db0:3306/db
        username: root
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        jdbc-url: jdbc:mysql://db1:3306/db
    rules:
      sharding:
        tables:
          orders:
            actualDataNodes: ds$->{0..1}.orders_$->{0..15}
            tableStrategy:
              standard:
                shardingColumn: order_id
                shardingAlgorithmName: table-inline
            keyGenerateStrategy:
              column: order_id
              keyGeneratorName: snowflake
        shardingAlgorithms:
          table-inline:
            type: INLINE
            props:
              algorithm-expression: orders_$->{order_id % 16}
 
  

四、事务管理解决方案

1. 分布式事务(XA协议)

@Bean
public JtaTransactionManager transactionManager(
        UserTransaction userTransaction,
        TransactionManager txManager) {
    return new JtaTransactionManager(userTransaction, txManager);
}

@Bean
public UserTransaction userTransaction() throws SystemException {
    UserTransactionImpl userTx = new UserTransactionImpl();
    userTx.setTransactionTimeout(300);
    return userTx;
}

@Bean
public TransactionManager txManager() {
    return new TransactionManagerImpl();
}

// 使用注解
@Transactional // 支持跨数据源事务
public void crossDatabaseOperation() {
    masterRepo.save(entity1);
    slaveRepo.save(entity2); // 两个操作要么全成功,要么全回滚
}

2. BASE柔性事务

// 使用Seata实现
@GlobalTransactional // Seata全局事务注解
public void placeOrder(Order order) {
    // 1. 扣减库存(库存服务)
    stockService.deduct(order.getProductId(), order.getCount());
    
    // 2. 创建订单(订单服务)
    orderService.create(order);
    
    // 3. 扣减余额(账户服务)
    accountService.debit(order.getUserId(), order.getAmount());
}
 
  

五、性能优化策略

1. 连接池配置优化

spring:
  datasource:
    dynamic:
      datasource:
        master:
          hikari:
            maximum-pool-size: 20
            minimum-idle: 5
            connection-timeout: 3000
            idle-timeout: 600000
            max-lifetime: 1800000
        slave:
          hikari:
            maximum-pool-size: 30 # 读库可配置更大连接池

2. 读写分离权重配置

spring:
  datasource:
    dynamic:
      datasource:
        master:
          weight: 10 # 写权重
        slave1:
          weight: 30 # 读权重
        slave2:
          weight: 30

3. 多数据源监控

@Bean
public MeterRegistryCustomizer metrics() {
    return registry -> {
        // 监控主库连接池
        HikariDataSource masterDs = ...;
        new HikariDataSourceMetrics(
            masterDs, 
            "datasource.master", 
            Tags.empty()
        ).bindTo(registry);
        
        // 监控从库连接池
        HikariDataSource slaveDs = ...;
        new HikariDataSourceMetrics(
            slaveDs, 
            "datasource.slave", 
            Tags.empty()
        ).bindTo(registry);
    };
}
 
  

六、生产环境避坑指南

1. 连接泄露检测

// 在DynamicDataSource中增加监控
public class MonitoredDataSource extends AbstractDataSource {
    private final DataSource targetDataSource;
    
    @Override
    public Connection getConnection() throws SQLException {
        Connection conn = targetDataSource.getConnection();
        // 记录连接获取堆栈
        ConnectionTracker.track(conn, new Exception("Connection opened at:"));
        return conn;
    }
    
    private static class ConnectionTracker {
        static void track(Connection conn, Exception openTrace) {
            // 存储打开堆栈信息
        }
        
        static void checkLeaks() {
            // 定时检查未关闭连接
        }
    }
}

2. 多数据源启动顺序

@Bean
@DependsOn("dynamicDataSource") // 确保先初始化数据源
public SqlSessionFactory sqlSessionFactory(
        DynamicDataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    return factory.getObject();
}

3. 跨库关联查询

解决方案

  1. 使用内存关联(小数据量)

  2. 建立数据同步管道(CDC工具)

  3. 使用分布式查询引擎(Presto/Drill)

七、方案选型决策树

Spring Boot多数据源实现方案深度对比:优缺点分析与实战指南_第2张图片

 
  

八、总结与最佳实践

核心结论

  • ️ 中小项目:MyBatis-Plus多数据源 > 手动配置

  •  动态路由场景:AbstractRoutingDataSource

  •  分布式系统:ShardingSphere

最佳实践

  1. 连接池分离:每个数据源独立配置连接池

  2. 读写分离:写操作路由到主库,读操作负载均衡到从库

  3. 事务边界:避免跨数据源事务,必要时使用Seata

  4. 监控告警:对每个数据源建立健康检查

  5. 失败转移:实现从库故障自动切换

黄金法则
在满足业务需求的前提下,选择最简洁的实现方案,避免过度设计

你可能感兴趣的:(java,#,springboot,spring,boot,后端,java)