1. SpringBoot多数据源连接方案概述
在企业级应用开发中,经常需要同时连接多个不同类型的数据库。最近我在一个金融数据分析项目中遇到了这样的需求:系统需要同时读取PostgreSQL中的客户交易数据和SQL Server中的风控规则数据。通过SpringBoot实现这种异构数据库的多数据源连接,可以完美解决业务需求。
传统单数据源配置在application.properties中简单定义一组连接参数即可,但多数据源场景需要更精细的控制。经过技术调研,我最终选择了基于Spring AbstractRoutingDataSource的方案,配合MyBatis-Plus和HikariCP连接池,实现了稳定可靠的双数据源连接。这种方案相比纯注解方式更灵活,比完全手动管理更规范。
重要提示:多数据源配置的核心挑战不在于连接建立本身,而在于事务管理、连接释放和会话隔离。不当的实现会导致连接泄漏或脏读问题。
2. 核心组件与技术选型
2.1 基础环境准备
我的开发环境如下:
- JDK 17(推荐LTS版本)
- SpringBoot 3.1.5(注意与JDK版本匹配)
- PostgreSQL 15.3(官方最新稳定版)
- SQL Server 2019(企业版)
- MyBatis-Plus 3.5.3.1(简化ORM操作)
在pom.xml中需要的关键依赖:
xml复制<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>12.2.0.jre11</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
2.2 连接池配置要点
我选择HikariCP作为连接池实现,相比Druid更轻量且性能优异。以下是两个数据源的典型配置参数对比:
| 参数项 | PostgreSQL推荐值 | SQL Server推荐值 | 说明 |
|---|---|---|---|
| maximumPoolSize | CPU核心数 * 2 + 1 | CPU核心数 * 2 | 避免过度连接 |
| idleTimeout | 600000(10分钟) | 300000(5分钟) | SQL Server建议更短 |
| connectionTimeout | 30000 | 30000 | 连接超时时间 |
| maxLifetime | 1800000(30分钟) | 1200000(20分钟) | SQL Server连接更易超时 |
| validationQuery | SELECT 1 | SELECT 1 | 连接有效性检测语句 |
3. 多数据源具体实现
3.1 数据源配置类实现
创建PostgresConfig和SqlServerConfig两个配置类,使用@ConfigurationProperties注入配置:
java复制@Configuration
@MapperScan(basePackages = "com.example.mapper.pg",
sqlSessionTemplateRef = "pgSqlSessionTemplate")
public class PostgresConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.pg")
public DataSource pgDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
public SqlSessionFactory pgSqlSessionFactory(
@Qualifier("pgDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/pg/*.xml"));
return factory.getObject();
}
@Bean
public SqlSessionTemplate pgSqlSessionTemplate(
@Qualifier("pgSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
SQL Server的配置类结构类似,主要区别在于:
- 扫描的package改为"com.example.mapper.mssql"
- Mapper XML路径改为"classpath:mapper/mssql/*.xml"
- Bean名称前缀改为"mssql"
3.2 动态数据源路由实现
创建DynamicDataSource继承AbstractRoutingDataSource:
java复制public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> CONTEXT_HOLDER =
new ThreadLocal<>();
public static void setDataSourceType(String dsType) {
CONTEXT_HOLDER.set(dsType);
}
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return getDataSourceType();
}
}
然后在主配置中注册这个数据源:
java复制@Bean
@Primary
public DataSource dynamicDataSource(
@Qualifier("pgDataSource") DataSource pgDataSource,
@Qualifier("mssqlDataSource") DataSource mssqlDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("pg", pgDataSource);
targetDataSources.put("mssql", mssqlDataSource);
DynamicDataSource ds = new DynamicDataSource();
ds.setTargetDataSources(targetDataSources);
ds.setDefaultTargetDataSource(pgDataSource); // 默认数据源
return ds;
}
4. 事务管理与使用实践
4.1 多数据源事务处理
多数据源场景下的事务管理需要特别注意。我采用两种方案:
-
单数据源事务:在每个方法上使用@Transactional(transactionManager = "pgTransactionManager")
-
分布式事务:对于需要跨库事务的场景,引入Atomikos或Seata实现JTA。以下是Atomikos配置示例:
java复制@Bean
public JtaTransactionManager transactionManager() {
UserTransactionManager utm = new UserTransactionManager();
UserTransaction ut = new UserTransactionImp();
return new JtaTransactionManager(ut, utm);
}
@Bean
public DataSource pgDataSource() {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setXaDataSourceClassName("org.postgresql.xa.PGXADataSource");
ds.setUniqueResourceName("pgXADataSource");
// 其他参数配置...
return ds;
}
4.2 实际使用示例
在Service层切换数据源:
java复制@Service
public class DataQueryService {
@Autowired
private PgUserMapper pgUserMapper;
@Autowired
private MssqlRuleMapper mssqlRuleMapper;
public UserData getCombinedData(Long userId) {
// 切换到PostgreSQL数据源
DynamicDataSource.setDataSourceType("pg");
User user = pgUserMapper.selectById(userId);
// 切换到SQL Server数据源
DynamicDataSource.setDataSourceType("mssql");
List<Rule> rules = mssqlRuleMapper.selectByUserType(user.getType());
// 清理数据源标记
DynamicDataSource.clearDataSourceType();
return new UserData(user, rules);
}
}
5. 性能优化与问题排查
5.1 连接池监控配置
在application.properties中添加:
properties复制# PostgreSQL监控
spring.datasource.pg.hikari.metrics-tracker=com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory
spring.datasource.pg.hikari.register-mbeans=true
# SQL Server监控
spring.datasource.mssql.hikari.metrics-tracker=com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory
spring.datasource.mssql.hikari.register-mbeans=true
management.endpoints.web.exposure.include=health,info,metrics,prometheus
通过Actuator可以获取以下关键指标:
- hikaricp.connections.active
- hikaricp.connections.idle
- hikaricp.connections.pending
- hikaricp.connections.max
- hikaricp.connections.min
5.2 常见问题解决方案
问题1:连接泄漏
症状:应用运行一段时间后出现连接耗尽
解决方案:
- 确保每次DynamicDataSource.setDataSourceType()后都有clear操作
- 添加连接泄漏检测:
properties复制spring.datasource.pg.hikari.leak-detection-threshold=60000 spring.datasource.mssql.hikari.leak-detection-threshold=60000
问题2:SQL方言冲突
症状:执行分页查询时报语法错误
解决方案:
- 为每个SqlSessionFactory配置独立方言:
java复制factory.setConfiguration(configuration); configuration.setDatabaseId("postgresql"); // 或sqlserver
问题3:事务不生效
症状:跨数据源操作无法回滚
解决方案:
- 使用JTA事务管理器
- 或拆分为多个@Transactional方法
6. 高级应用场景
6.1 动态添加数据源
运行时动态注册新数据源:
java复制public void addNewDataSource(String dsName, DataSourceProperties props) {
HikariDataSource newDs = props.initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
DynamicDataSource ds = (DynamicDataSource)applicationContext
.getBean("dynamicDataSource");
ds.addTargetDataSource(dsName, newDs);
// 注册新的Mapper接口
SqlSessionFactory factory = createSqlSessionFactory(newDs);
applicationContext.getBeanFactory()
.registerSingleton(dsName+"SqlSessionFactory", factory);
}
6.2 读写分离扩展
基于多数据源实现读写分离:
java复制@Around("@annotation(readOnly)")
public Object routeReadOnly(ProceedingJoinPoint jp, ReadOnly readOnly)
throws Throwable {
String current = DynamicDataSource.getDataSourceType();
try {
if(readOnly.value()) {
DynamicDataSource.setDataSourceType("slave");
}
return jp.proceed();
} finally {
DynamicDataSource.setDataSourceType(current);
}
}
在实际项目中,这套多数据源方案成功支撑了日均百万级的跨库查询操作。PostgreSQL负责处理复杂的分析查询,SQL Server则保证了事务型操作的高效执行。通过合理的连接池配置和严格的事务管理,系统运行半年多来未出现严重的连接问题。
