1. 多数据源场景的本质需求
在企业级应用开发中,多数据源配置是典型的刚性需求。我经历过一个电商平台项目,需要同时对接MySQL商品库、Oracle财务系统以及MongoDB用户行为日志库。这种异构数据源并存的情况,正是SpringBoot多数据源方案要解决的核心问题。
与单数据源不同,多数据源配置需要解决以下几个关键问题:
- 不同数据源的连接池隔离(避免配置互相覆盖)
- 事务管理的边界划分(跨数据源事务的复杂性)
- MyBatis/SQLSessionFactory的实例隔离
- 运行时数据源切换的线程安全问题
2. 静态多数据源实现方案
2.1 基础配置方法
最直接的方式是为每个数据源创建独立的配置类。以下是一个典型的多数据源配置示例:
@Configuration @MapperScan(basePackages = "com.example.mapper.db1", sqlSessionFactoryRef = "db1SqlSessionFactory") public class Db1Config { @Bean @ConfigurationProperties("spring.datasource.db1") public DataSource db1DataSource() { return DataSourceBuilder.create().build(); } @Bean public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mapper/db1/*.xml")); return bean.getObject(); } // 同理配置TransactionManager... }关键点说明:
- 每个数据源需要独立的@Configuration类
- 使用@MapperScan的sqlSessionFactoryRef属性绑定对应的工厂
- 数据源前缀建议采用spring.datasource.[名称]的格式
2.2 配置隔离的实践经验
在实际项目中,我总结出几个配置要点:
- 连接池参数应该根据业务特点差异化配置。比如:
- 高频查询库:增大maxActive(建议20-50)
- 低频报表库:减小maxActive(建议5-10)
- 建议为每个数据源配置不同的连接池前缀:
spring: datasource: db1: druid: filters: stat,wall db2: hikari: connection-timeout: 30000
3. 动态数据源的核心原理
3.1 抽象路由数据源
动态数据源的核心是AbstractRoutingDataSource,它通过determineCurrentLookupKey()方法实现运行时切换。典型实现如下:
public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); @Override protected Object determineCurrentLookupKey() { return CONTEXT_HOLDER.get(); } public static void setDataSource(String name) { CONTEXT_HOLDER.set(name); } // 清理方法... }3.2 动态数据源与静态方案的对比
| 特性 | 静态多数据源 | 动态数据源 |
|---|---|---|
| 配置方式 | 显式声明多个@Bean | 单数据源包装多个实际数据源 |
| 切换成本 | 需要修改代码/配置 | 运行时动态切换 |
| 事务支持 | 各数据源独立事务 | 需要特殊处理跨库事务 |
| 适用场景 | 固定业务对应固定数据源 | 需要运行时灵活切换的场景 |
| 性能影响 | 无额外开销 | 有路由判断的微小开销 |
4. 实战中的典型问题解决方案
4.1 MyBatis拦截器冲突
当同时使用PageHelper等插件时,可能出现拦截器重复注册的问题。解决方案:
@Bean public SqlSessionFactory db1SqlSessionFactory(...) { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); // 其他配置... // 手动添加拦截器,避免重复 Properties properties = new Properties(); properties.setProperty("helperDialect", "mysql"); Interceptor interceptor = new PageInterceptor(); interceptor.setProperties(properties); bean.setPlugins(interceptor); return bean.getObject(); }4.2 多数据源事务管理
对于需要跨数据源的事务,推荐使用JTA方案。Spring Boot集成Atomikos的配置示例:
@Bean(initMethod = "init", destroyMethod = "close") public UserTransactionManager userTransactionManager() { UserTransactionManager manager = new UserTransactionManager(); manager.setForceShutdown(false); return manager; } @Bean public JtaTransactionManager transactionManager() { JtaTransactionManager manager = new JtaTransactionManager(); manager.setUserTransaction(userTransactionManager()); return manager; }5. 性能优化建议
连接池监控:为每个数据源配置独立的监控
@Bean public ServletRegistrationBean<StatViewServlet> druidServlet() { ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>( new StatViewServlet(), "/druid/*"); bean.addInitParameter("loginUsername", "admin"); bean.addInitParameter("loginPassword", "123456"); return bean; }SQL日志分离:建议不同数据源的SQL日志输出到不同文件
logging.level.com.example.mapper.db1=DEBUG logging.file.name=logs/db1.log logging.level.com.example.mapper.db2=DEBUG logging.file.name=logs/db2.log连接泄漏检测:对于Druid可以配置
spring: datasource: druid: remove-abandoned: true remove-abandoned-timeout: 180
6. 扩展思考:何时选择哪种方案
根据我的项目经验,给出以下决策参考:
选择静态多数据源当:
- 数据源与业务模块有明确对应关系
- 不需要运行时动态切换
- 对事务完整性要求较高
选择动态数据源当:
- 需要支持多租户场景
- 有读写分离需求
- 需要根据请求参数路由不同数据库
混合方案:对于特别复杂的场景,可以结合两种方式。比如:
- 静态配置主要业务库
- 动态数据源处理特殊路由需求
在微服务架构下,更推荐将不同数据源拆分为独立服务。但对于遗留系统改造或特定场景,多数据源方案仍然是实用选择。