☰
SpringBoot多数据源配置与动态路由实践
2026/10/14 13:06:26 网站建设 项目流程

1. 多数据源连接的核心场景与挑战

在现代企业级应用开发中,一个系统需要同时访问多个数据库的情况越来越普遍。比如你可能需要从MySQL读取用户基础数据,同时又要连接SqlServer获取业务订单信息,还可能涉及Oracle的数据仓库查询。这种多数据源访问的需求主要出现在以下典型场景:

  • 遗留系统整合:新旧系统交替阶段,需要同时访问老系统的SqlServer和新系统的MySQL
  • 数据隔离需求:核心业务数据与日志数据分别存储在不同数据库实例
  • 跨部门协作:不同部门使用不同的数据库技术栈但需要数据互通
  • 读写分离场景:写操作使用主库,读操作分散到多个从库

使用SpringBoot实现多数据源连接时,开发者通常会遇到几个关键挑战:

  1. 数据源配置冲突:多个DataSource实例如何避免Bean名称冲突
  2. 事务管理难题:跨数据源的事务一致性如何保证
  3. 会话隔离问题:如何确保不同DAO操作使用正确的数据源
  4. 性能优化:连接池配置与监控的复杂性成倍增加

2. 基础环境搭建与依赖配置

2.1 项目初始化与必要依赖

创建一个标准的SpringBoot项目(2.7.x版本推荐),在pom.xml中需要包含以下核心依赖:

<dependencies> <!-- SpringBoot基础starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatisPlus starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>mssql-jdbc</artifactId> <scope>runtime</scope> </dependency> <!-- 连接池推荐使用HikariCP --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> </dependencies>

注意:MyBatisPlus版本建议使用3.5.x以上以获得更好的多数据源支持特性

2.2 多数据源配置方案选型

实现多数据源有几种主流方案,各有优缺点:

  1. AbstractRoutingDataSource动态路由方案

    • 优点:配置灵活,运行时动态切换
    • 缺点:需要自行处理事务管理
  2. 独立配置+分包扫描方案

    • 优点:结构清晰,各数据源完全隔离
    • 缺点:代码冗余,扩展性差
  3. 第三方Starter方案(如dynamic-datasource)

    • 优点:开箱即用,功能完善
    • 缺点:黑盒逻辑,定制困难

本文选择第一种方案,因为它:

  • 更适合理解底层原理
  • 方便后续自定义扩展
  • 不引入额外依赖

3. 核心配置实现详解

3.1 数据源属性配置

在application.yml中配置两个数据源(以MySQL和SqlServer为例):

spring: datasource: master: jdbc-url: jdbc:mysql://localhost:3306/db1?useSSL=false username: root password: mysql123 driver-class-name: com.mysql.cj.jdbc.Driver hikari: pool-name: HikariPool-MySQL maximum-pool-size: 15 slave: jdbc-url: jdbc:sqlserver://localhost:1433;databaseName=db2 username: sa password: sqlserver123 driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver hikari: pool-name: HikariPool-SqlServer maximum-pool-size: 10

关键点:使用自定义前缀(master/slave)而非spring.datasource直接配置,避免自动装配冲突

3.2 Java配置类实现

创建多数据源配置类:

@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); } @Bean @ConfigurationProperties("spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); } @Primary @Bean public DataSource dynamicDataSource( @Qualifier("masterDataSource") DataSource master, @Qualifier("slaveDataSource") DataSource slave) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", master); targetDataSources.put("slave", slave); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSources); dataSource.setDefaultTargetDataSource(master); return dataSource; } }

3.3 动态数据源路由实现

创建DynamicDataSource类继承AbstractRoutingDataSource:

public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDataSourceKey(String key) { CONTEXT_HOLDER.set(key); } public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } @Override protected Object determineCurrentLookupKey() { return CONTEXT_HOLDER.get(); } }

4. MyBatisPlus集成与测试

4.1 MyBatisPlus多数据源配置

@Configuration @MapperScan(basePackages = "com.example.mapper") public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); sessionFactory.setTypeHandlersPackage("com.example.handler"); return sessionFactory.getObject(); } }

4.2 数据源切换AOP实现

@Aspect @Component @Order(1) public class DataSourceAspect { @Pointcut("@annotation(com.example.annotation.Master) " + "|| execution(* com.example.service..*.insert*(..)) " + "|| execution(* com.example.service..*.update*(..)) " + "|| execution(* com.example.service..*.delete*(..))") public void masterPointcut() {} @Before("masterPointcut()") public void beforeMaster() { DynamicDataSource.setDataSourceKey("master"); } @Pointcut("@annotation(com.example.annotation.Slave) " + "|| execution(* com.example.service..*.select*(..)) " + "|| execution(* com.example.service..*.get*(..)) " + "|| execution(* com.example.service..*.find*(..))") public void slavePointcut() {} @Before("slavePointcut()") public void beforeSlave() { DynamicDataSource.setDataSourceKey("slave"); } @AfterReturning("masterPointcut() || slavePointcut()") public void afterComplete() { DynamicDataSource.clearDataSourceKey(); } }

4.3 测试用例编写

@SpringBootTest class MultiDataSourceTest { @Autowired private UserMapper userMapper; // 使用MySQL数据源 @Autowired private OrderMapper orderMapper; // 使用SqlServer数据源 @Test void testCrossDataSource() { // 默认使用主数据源 User user = userMapper.selectById(1); assertNotNull(user); // 切换数据源查询 DynamicDataSource.setDataSourceKey("slave"); Order order = orderMapper.selectById(1); assertNotNull(order); // 验证事务 try { userMapper.insert(new User(...)); orderMapper.insert(new Order(...)); // 跨数据源操作 fail("Should throw transaction exception"); } catch (Exception e) { assertTrue(e instanceof TransactionException); } } }

5. 高级特性与生产级优化

5.1 多数据源事务管理

跨数据源的事务需要引入JTA或Seata等分布式事务解决方案。以Atomikos为例:

@Bean public PlatformTransactionManager transactionManager() throws Exception { UserTransactionManager userTransactionManager = new UserTransactionManager(); userTransactionManager.setForceShutdown(false); UserTransaction userTransaction = new UserTransactionImp(); userTransaction.setTransactionTimeout(300); return new JtaTransactionManager(userTransaction, userTransactionManager); }

5.2 连接池监控配置

@Bean public DataSourcePoolMetadataProvider dataSourcePoolMetadataProvider() { return (dataSource) -> { if (dataSource instanceof HikariDataSource) { return new HikariDataSourcePoolMetadata((HikariDataSource) dataSource); } return null; }; } // 在Actuator中暴露监控端点 management.endpoints.web.exposure.include=health,info,datasource

5.3 动态数据源扩展

实现动态添加/移除数据源:

public void addDataSource(String key, DataSource dataSource) { DynamicDataSource dynamicDataSource = (DynamicDataSource) applicationContext.getBean("dynamicDataSource"); Map<Object, Object> targetDataSources = new HashMap<>(dynamicDataSource.getTargetDataSources()); targetDataSources.put(key, dataSource); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.afterPropertiesSet(); }

6. 常见问题排查指南

6.1 数据源切换不生效

可能原因:

  1. AOP顺序问题:确保@Order值小于Spring事务拦截器(通常为Ordered.LOWEST_PRECEDENCE)
  2. 方法内部调用:自调用不会触发AOP,需要通过代理对象调用
  3. 注解未正确使用:检查@Master/@Slave注解是否被正确继承

解决方案:

// 显式指定AOP顺序 @EnableTransactionManagement(order = Ordered.LOWEST_PRECEDENCE) public class AppConfig {}

6.2 事务回滚异常

典型错误:

org.springframework.transaction.TransactionSystemException: Could not roll back JDBC transaction; nested exception is java.sql.SQLException: Connection is closed

解决方案:

  1. 配置事务超时时间
  2. 确保连接池testOnBorrow/testWhileIdle配置正确
  3. 避免在finally块中手动清理数据源上下文

6.3 MyBatisPlus分页失效

排查步骤:

  1. 检查拦截器是否正确注入
  2. 确认不同数据源方言配置正确
  3. 验证SQL日志是否包含分页语句
// 为不同数据源配置不同方言 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.SQL_SERVER));

7. 性能优化建议

  1. 连接池参数调优:

    • MySQL推荐配置:maximumPoolSize = (core_count * 2) + effective_spindle_count
    • SqlServer推荐配置:maximumPoolSize = 50以内(受限于License)
  2. 监控指标收集:

    HikariDataSource master = (HikariDataSource)masterDataSource(); log.info("MySQL连接池状态: Active={}, Idle={}, Total={}", master.getHikariPoolMXBean().getActiveConnections(), master.getHikariPoolMXBean().getIdleConnections(), master.getHikariPoolMXBean().getTotalConnections());
  3. SQL执行监控:

    <settings> <setting name="logImpl" value="SLF4J"/> <setting name="proxyLogLevel" value="debug"/> </settings>
  4. 多数据源负载均衡:

    @Slave @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ReadOnly { String value() default "slave"; boolean loadBalance() default true; }

在实际项目中,我们团队发现当数据源超过3个时,建议引入动态数据源管理中间件。对于读写分离场景,可以结合ShardingSphere实现更复杂的路由策略。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询