1. 项目概述:SpringBoot多数据源连接实践
去年接手一个企业级数据中台项目时,我遇到了需要同时对接PostgreSQL和SQL Server两种数据库的场景。传统单数据源配置完全无法满足需求,经过多次踩坑和优化,最终形成了一套稳定可靠的多数据源解决方案。这种技术方案特别适合以下场景:
- 需要同时访问不同数据库系统的应用(如异构数据库迁移)
- 读写分离场景下的主从库配置
- 按业务模块隔离数据的微服务架构
重要提示:多数据源配置的核心难点不在于基础连接,而在于事务管理和连接池优化。我在生产环境中曾因忽视这点导致过严重事故。
2. 技术方案设计与选型
2.1 整体架构设计
采用SpringBoot + MyBatis组合实现多数据源管理,核心架构分为三层:
- 配置层:通过Java Config方式声明多个DataSource Bean
- 路由层:自定义AbstractRoutingDataSource实现动态切换
- 注解层:通过AOP切面实现方法级别的数据源切换
java复制// 典型的多数据源配置类结构示例
@Configuration
@MapperScan(basePackages = "com.example.mapper")
public class DataSourceConfig {
@Bean(name = "postgresqlDataSource")
@ConfigurationProperties(prefix = "spring.datasource.postgresql")
public DataSource postgresqlDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "sqlserverDataSource")
@ConfigurationProperties(prefix = "spring.datasource.sqlserver")
public DataSource sqlserverDataSource() {
return DataSourceBuilder.create().build();
}
}
2.2 关键技术选型对比
| 技术选项 | PostgreSQL适配方案 | SQL Server适配方案 |
|---|---|---|
| 连接池 | HikariCP | HikariCP |
| 驱动类 | org.postgresql.Driver | com.microsoft.sqlserver.jdbc.SQLServerDriver |
| URL格式 | jdbc:postgresql://host:port/dbname | jdbc:sqlserver://host:port;databaseName=dbname |
| 事务管理器 | DataSourceTransactionManager | DataSourceTransactionManager |
3. 详细实现步骤
3.1 基础环境准备
首先确保开发环境包含以下组件:
- JDK 1.8+
- SpringBoot 2.7.x
- PostgreSQL 12+ / SQL Server 2016+
- 对应的JDBC驱动依赖
Maven依赖配置示例:
xml复制<dependencies>
<!-- PostgreSQL驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.6</version>
</dependency>
<!-- SQL Server驱动 -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>9.4.1.jre8</version>
</dependency>
<!-- 其他SpringBoot标准依赖 -->
</dependencies>
3.2 多数据源配置实战
3.2.1 配置文件设置
application.yml中配置双数据源:
yaml复制spring:
datasource:
postgresql:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/mydb
username: pguser
password: pgpass
hikari:
maximum-pool-size: 10
connection-timeout: 30000
sqlserver:
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://localhost:1433;databaseName=mydb
username: sqluser
password: sqlpass
hikari:
maximum-pool-size: 15
connection-timeout: 40000
3.2.2 动态数据源路由实现
创建动态数据源路由类:
java复制public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
CONTEXT_HOLDER.set(key);
}
@Override
protected Object determineCurrentLookupKey() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceKey() {
CONTEXT_HOLDER.remove();
}
}
3.2.3 数据源切换注解
自定义数据源注解:
java复制@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "postgresql";
}
3.3 事务管理特别处理
多数据源环境下的事务管理需要特殊处理:
java复制@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DynamicDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
关键经验:跨数据源事务需要使用JTA分布式事务,但会显著降低性能。建议业务设计时避免跨库事务。
4. 性能优化与生产建议
4.1 连接池调优参数
针对不同数据库的推荐配置:
| 参数 | PostgreSQL推荐值 | SQL Server推荐值 | 说明 |
|---|---|---|---|
| maximumPoolSize | CPU核心数*2 | CPU核心数*3 | SQL Server连接创建开销更大 |
| connectionTimeout | 30000ms | 40000ms | SQL Server网络延迟通常更高 |
| idleTimeout | 600000ms | 300000ms | SQL Server连接更易超时 |
| maxLifetime | 1800000ms | 1200000ms | SQL Server连接稳定性稍差 |
4.2 监控指标配置
建议添加以下监控:
java复制@Bean
public DataSourcePoolMetrics postgresqlMetrics(
@Qualifier("postgresqlDataSource") DataSource dataSource) {
return new DataSourcePoolMetrics(dataSource, "postgresql");
}
@Bean
public DataSourcePoolMetrics sqlserverMetrics(
@Qualifier("sqlserverDataSource") DataSource dataSource) {
return new DataSourcePoolMetrics(dataSource, "sqlserver");
}
5. 典型问题排查指南
5.1 连接泄漏排查
症状:连接数持续增长不释放
解决方法:
- 检查是否忘记调用DynamicDataSource.clearDataSourceKey()
- 使用Druid的removeAbandoned功能
- 分析连接获取堆栈:jstack
| grep -A 20 "ConnectionPool"
5.2 事务失效场景
常见原因:
- 同类方法调用导致AOP失效
- 异常类型未被@Transactional捕获
- 多线程环境下ThreadLocal污染
解决方案示例:
java复制// 正确的事务传播设置示例
@Transactional(propagation = Propagation.REQUIRES_NEW)
@DataSource("sqlserver")
public void updateOrder(Order order) {
// 业务逻辑
}
6. 高级应用场景
6.1 读写分离实现
扩展动态数据源路由支持读写分离:
java复制protected Object determineCurrentLookupKey() {
String key = CONTEXT_HOLDER.get();
if(key == null) {
// 通过方法名判断读写
String methodName = getCurrentMethodName();
if(methodName.startsWith("get") || methodName.startsWith("query")) {
return "postgresql_slave";
}
return "postgresql_master";
}
return key;
}
6.2 多租户数据隔离
结合租户上下文实现动态数据源切换:
java复制@Before("execution(* com.example.service.*.*(..))")
public void beforeServiceMethod(JoinPoint jp) {
TenantContext tenant = TenantContextHolder.get();
if(tenant != null) {
String dsKey = tenant.getDataSourceKey();
DynamicDataSource.setDataSourceKey(dsKey);
}
}
在实际项目中,我发现PostgreSQL和SQL Server的联合使用需要特别注意类型映射问题。比如PostgreSQL的JSONB类型需要特殊处理才能与SQL Server的NVARCHAR兼容。建议在实体类中使用@ColumnTransformer注解处理类型转换:
java复制@ColumnTransformer(
read = "jsonb_column::text",
write = "?::jsonb"
)
private String jsonData;
经过多个项目的验证,这套方案在TPS 500+的生产环境中表现稳定。关键是要做好连接池监控和合理的超时设置,特别是SQL Server的连接需要更保守的配置参数。
