1. 多数据源连接的核心场景与挑战
在企业级应用开发中,多数据源连接是刚需而非炫技。我最近在金融行业项目中就遇到这样的典型场景:核心交易数据存储在MySQL集群,客户信息放在SQL Server,而风控数据又存在Oracle。这种异构数据库并存的情况,在传统企业信息化建设中尤为常见。
SpringBoot的多数据源配置看似简单,但实际落地时会遇到几个关键问题:
- 事务管理如何保证跨库一致性
- MyBatisPlus的Mapper接口如何正确路由到不同数据源
- 连接池参数需要针对不同数据库单独优化
- 多数据源环境下SQL方言差异的处理
2. 基础环境搭建与依赖配置
2.1 必要依赖清单
在pom.xml中需要明确区分核心依赖和数据库驱动依赖:
xml复制<!-- 核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</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>
<version>9.4.1.jre11</version>
</dependency>
重要提示:SQL Server驱动版本需要与JDK版本严格匹配,我曾在JDK11环境使用旧版驱动导致连接异常。
2.2 多数据源配置类设计
建议采用继承AbstractRoutingDataSource的方式实现动态路由。以下是核心代码结构:
java复制public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource dynamicDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
// 添加其他数据源...
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);
dataSource.setDefaultTargetDataSource(masterDataSource());
return dataSource;
}
}
3. MyBatisPlus多数据源深度整合
3.1 Mapper接口与数据源绑定
需要在MyBatisPlus配置中明确指定每个Mapper对应的数据源:
java复制@Configuration
@MapperScan(basePackages = "com.example.mapper.mysql",
sqlSessionTemplateRef = "mysqlSqlSessionTemplate")
public class MybatisMysqlConfig {
@Bean
public SqlSessionFactory mysqlSqlSessionFactory(
@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/mysql/*.xml"));
return factory.getObject();
}
}
3.2 分页插件特殊处理
多数据源环境下需要为每个SqlSessionFactory单独配置分页插件:
java复制@Bean
public MybatisPlusInterceptor mysqlMybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// MySQL分页方言
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
4. 事务管理的陷阱与解决方案
4.1 分布式事务的折中方案
在无法使用Seata等分布式事务框架时,可以采用最终一致性方案:
java复制@Transactional(transactionManager = "mysqlTransactionManager")
public void crossDatabaseOperation() {
// 操作主库
mysqlMapper.update(params);
// 标记从库操作
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
// 主事务提交后执行
sqlServerMapper.insert(params);
}
});
}
4.2 连接泄露防护措施
必须为每个数据源配置独立的连接池监控:
yaml复制spring:
datasource:
master:
hikari:
leak-detection-threshold: 5000
max-lifetime: 1800000
slave:
hikari:
leak-detection-threshold: 10000
max-lifetime: 1200000
5. 性能优化实战经验
5.1 连接池参数调优
不同数据库的最佳连接池配置差异很大:
| 参数项 | MySQL推荐值 | SQL Server推荐值 | 说明 |
|---|---|---|---|
| maximumPoolSize | 20 | 10 | SQL Server连接更昂贵 |
| idleTimeout | 600000 | 300000 | SQL Server需要更快回收 |
| connectionTimeout | 30000 | 60000 | SQL Server连接更耗时 |
5.2 缓存策略设计
二级缓存需要按数据源隔离:
java复制@Bean
public Cache mysqlCache() {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)))
.entryTtl(Duration.ofMinutes(30));
return new RedisCache("mysqlCache", redisCacheWriter, config);
}
6. 生产环境踩坑实录
6.1 驱动兼容性问题
SQL Server的JTDS驱动与官方驱动在事务处理上有差异,我们遇到过:
java复制// 错误示例:混合使用不同驱动
@Bean
public DataSource sqlServerDataSource() {
// 错误:使用了JTDS的连接URL格式但官方驱动
return DataSourceBuilder.create()
.url("jdbc:sqlserver://host:port;databaseName=db")
.driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
.build();
}
6.2 MyBatisPlus自动填充失效
在多数据源环境下,需要为每个SqlSessionFactory单独配置MetaObjectHandler:
java复制@Bean
public MybatisPlusSqlSessionFactoryBean sqlServerSqlSessionFactory(
@Qualifier("sqlServerDataSource") DataSource dataSource) {
MybatisPlusSqlSessionFactoryBean factory = new MybatisPlusSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setGlobalConfig(new GlobalConfig()
.setMetaObjectHandler(new CustomMetaObjectHandler()));
return factory;
}
7. 监控与运维方案
7.1 多数据源健康检查
需要自定义健康检查端点:
java复制@Component
public class MultiDataSourceHealthIndicator implements HealthIndicator {
@Override
public Health health() {
Map<String, Object> details = new HashMap<>();
details.put("mysql", checkDataSource(mysqlDataSource));
details.put("sqlserver", checkDataSource(sqlServerDataSource));
return Health.up().withDetails(details).build();
}
}
7.2 SQL审计日志分离
建议为每个数据源配置独立的日志文件:
properties复制# MySQL日志
logging.level.com.example.mapper.mysql=DEBUG
logging.file.name=logs/mysql-sql.log
# SQL Server日志
logging.level.com.example.mapper.sqlserver=DEBUG
logging.file.name=logs/sqlserver-sql.log
经过多个项目的实战验证,这套方案在TPS 500+的生产环境中稳定运行了18个月。关键是要做好连接泄露防护和事务边界控制,特别是在批量操作时要注意分批提交。对于需要强一致性的场景,建议还是考虑引入分布式事务中间件。