1. 多数据源架构的必要性与挑战
在企业级应用开发中,多数据源连接是常见的需求场景。我最近在金融行业的数据中台项目中就遇到了这样的需求:核心交易数据存储在SQL Server,而用户行为日志和分析数据则使用PostgreSQL。这种异构数据库并存的情况主要源于以下业务需求:
- 历史系统兼容:旧有系统基于SQL Server开发,新功能采用PostgreSQL实现
- 性能优化:PostgreSQL在复杂查询和JSON处理上表现优异
- 成本考虑:部分非核心业务采用开源方案降低授权成本
但实现多数据源连接时,开发者通常会面临几个技术难点:
- 连接池管理:每个数据源需要独立的连接池配置
- 事务一致性:跨库事务需要特殊处理
- ORM框架适配:MyBatis等框架需要针对不同数据源进行定制
- 方言差异:PostgreSQL和SQL Server的SQL语法存在差异
2. 项目环境搭建与依赖配置
2.1 基础环境准备
推荐使用以下环境组合,这是经过多个生产项目验证的稳定版本:
- JDK 17(LTS版本)
- Spring Boot 3.1.5
- MyBatis-Plus 3.5.7
- PostgreSQL 15
- SQL Server 2019
注意:SQL Server驱动对JDK版本有严格要求,JDK 17需要使用mssql-jdbc 10.2.x以上版本
2.2 Maven依赖配置详解
除了基本的数据库驱动,还需要特别注意以下依赖项:
xml复制<dependencies>
<!-- 必须包含的starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MyBatis-Plus核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>10.2.3.jre17</version>
<scope>runtime</scope>
</dependency>
<!-- 辅助工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2.3 配置文件优化建议
application.yml配置需要特别注意连接池参数的调优:
yaml复制spring:
datasource:
pg:
jdbc-url: jdbc:postgresql://localhost:5432/pg_db?currentSchema=public
username: admin
password: pgadmin123
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 20
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
sqlserver:
jdbc-url: jdbc:sqlserver://localhost:1433;databaseName=ms_db;encrypt=true;trustServerCertificate=true
username: sa
password: sqladmin123
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
hikari:
maximum-pool-size: 15
connection-timeout: 40000
idle-timeout: 300000
max-lifetime: 1200000
关键点:SQL Server连接字符串需要添加encrypt和trustServerCertificate参数,否则新版本驱动会报SSL错误
3. 多数据源核心配置实现
3.1 PostgreSQL数据源配置类
java复制@Configuration
@MapperScan(
basePackages = "com.example.mapper.pg",
sqlSessionFactoryRef = "pgSqlSessionFactory")
public class PgDataSourceConfig {
@Primary
@Bean(name = "pgDataSource")
@ConfigurationProperties(prefix = "spring.datasource.pg")
public DataSource pgDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "pgSqlSessionFactory")
public SqlSessionFactory pgSqlSessionFactory(
@Qualifier("pgDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setTypeAliasesPackage("com.example.entity.pg");
// 配置分页插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
factory.setPlugins(interceptor);
// 处理PostgreSQL特有的类型转换
factory.setTypeHandlers(new UUIDTypeHandler());
return factory.getObject();
}
@Primary
@Bean(name = "pgTransactionManager")
public PlatformTransactionManager pgTransactionManager(
@Qualifier("pgDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
3.2 SQL Server数据源配置类
java复制@Configuration
@MapperScan(
basePackages = "com.example.mapper.sqlserver",
sqlSessionFactoryRef = "sqlServerSqlSessionFactory")
public class SqlServerDataSourceConfig {
@Bean(name = "sqlServerDataSource")
@ConfigurationProperties(prefix = "spring.datasource.sqlserver")
public DataSource sqlServerDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "sqlServerSqlSessionFactory")
public SqlSessionFactory sqlServerSqlSessionFactory(
@Qualifier("sqlServerDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setTypeAliasesPackage("com.example.entity.sqlserver");
// SQL Server分页配置
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.SQL_SERVER);
pageInterceptor.setOptimizeJoin(false); // SQL Server分页需要关闭优化
interceptor.addInnerInterceptor(pageInterceptor);
factory.setPlugins(interceptor);
return factory.getObject();
}
@Bean(name = "sqlServerTransactionManager")
public PlatformTransactionManager sqlServerTransactionManager(
@Qualifier("sqlServerDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
3.3 关键配置解析
- @Primary注解:必须在一个数据源上标记为主数据源
- DbType指定:分页插件必须明确指定数据库类型
- Mapper扫描隔离:basePackages必须严格区分
- 事务管理器:每个数据源需要独立的事务管理器
4. 业务层实现与事务管理
4.1 基础CRUD操作示例
java复制@Service
@RequiredArgsConstructor
public class UserOrderService {
private final PgUserMapper pgUserMapper;
private final SqlServerOrderMapper sqlServerOrderMapper;
// 单数据源操作
public void addUser(User user) {
pgUserMapper.insert(user);
}
public List<Order> queryOrders(Long userId) {
return sqlServerOrderMapper.selectByUserId(userId);
}
}
4.2 跨数据源事务处理
对于需要跨库事务的场景,建议采用以下两种方案:
方案一:最终一致性(补偿事务)
java复制public void placeOrder(Order order, User user) {
try {
// 先操作主库
pgUserMapper.updateBalance(user);
// 再操作次库
sqlServerOrderMapper.insert(order);
} catch (Exception e) {
// 补偿逻辑
pgUserMapper.rollbackBalance(user.getId());
throw new RuntimeException("下单失败", e);
}
}
方案二:集成分布式事务框架(如Seata)
java复制@GlobalTransactional
public void crossDatabaseOperation() {
// 操作PostgreSQL
pgRepository.update(...);
// 操作SQL Server
sqlServerRepository.insert(...);
}
4.3 性能优化建议
-
连接池调优:
- PostgreSQL连接池可以适当调大(建议20-30)
- SQL Server连接池建议控制在15以内
-
批处理操作:
java复制@Transactional(transactionManager = "sqlServerTransactionManager") public void batchInsertOrders(List<Order> orders) { sqlServerOrderMapper.insertBatch(orders); } -
读写分离:
可以为每个数据源配置主从,进一步细化数据源配置
5. 常见问题排查与解决方案
5.1 驱动类加载问题
症状:启动时报Driver not found异常
解决方案:
- 检查驱动版本是否匹配JDK版本
- 确保scope为runtime
- SQL Server需要添加最新驱动:
xml复制<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>12.4.2.jre11</version>
</dependency>
5.2 事务不生效问题
症状:跨数据源操作时事务不回滚
解决方案:
- 确保在每个方法上正确指定transactionManager
- 检查@Transactional注解是否被AOP代理
- 对于自调用场景,需要通过AopContext获取代理对象
5.3 分页查询异常
症状:SQL Server分页查询报语法错误
解决方案:
java复制// 需要特别配置SQL Server分页
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.SQL_SERVER);
pageInterceptor.setOptimizeJoin(false);
5.4 连接泄漏问题
症状:运行一段时间后连接耗尽
解决方案:
- 配置合理的连接超时时间
- 添加连接泄漏检测:
yaml复制spring:
datasource:
pg:
hikari:
leak-detection-threshold: 60000
6. 生产环境最佳实践
经过多个项目的实践验证,我总结了以下经验:
-
命名规范:
- 数据源bean名称统一使用
xxxDataSource格式 - Mapper接口按数据库划分包结构
- 实体类使用
@TableName显式指定表名
- 数据源bean名称统一使用
-
监控配置:
java复制@Bean public DataSourceHealthIndicator pgHealthIndicator( @Qualifier("pgDataSource") DataSource dataSource) { return new DataSourceHealthIndicator(dataSource); } -
多环境配置:
使用Spring Profile区分不同环境的连接参数 -
故障转移:
配置备用数据源连接:yaml复制spring: datasource: pg: jdbc-url: jdbc:postgresql://primary:5432,secondary:5432/db -
安全建议:
- 使用Vault或配置中心管理密码
- 为每个应用创建独立的数据库账号
- 定期轮换数据库凭证
