1. 为什么需要同时连接MySQL和Oracle?
在企业级应用开发中,混合数据库环境非常常见。我最近接手的一个供应链管理系统就遇到了典型场景:核心业务数据存储在Oracle中(因为历史原因和企业级事务需求),而新开发的微服务模块使用MySQL(成本更低且更适合敏捷开发)。这种异构数据库并存的情况,就需要我们的Java应用能够同时操作两种数据库。
MyBatis-Plus作为MyBatis的增强工具,提供了开箱即用的动态数据源支持。通过简单的配置,我们可以在一个应用中无缝切换不同数据库连接。这比传统的多数据源方案(如手动获取Connection)更加优雅和安全。
实际案例:某电商系统将订单主表放在Oracle(保证ACID),而用户行为日志存在MySQL,通过动态数据源实现双写
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础环境准备
2.1 依赖配置
使用Maven构建项目时,需要同时引入两种数据库驱动。关键依赖如下:
xml复制<!-- MySQL连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- Oracle连接驱动 -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>21.5.0.0</version>
</dependency>
<!-- MyBatis-Plus核心 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- 动态数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
注意:Oracle驱动需要从官方仓库下载,建议配置阿里云镜像加速
2.2 数据库连接配置
在application.yml中配置多数据源(示例使用生产级配置):
yaml复制spring:
datasource:
dynamic:
primary: mysql # 设置默认数据源
strict: true # 严格匹配数据源
datasource:
mysql:
url: jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
connection-timeout: 30000
maximum-pool-size: 20
oracle:
url: jdbc:oracle:thin:@//localhost:1521/ORCLCDB
username: system
password: oracle
driver-class-name: oracle.jdbc.OracleDriver
hikari:
connection-timeout: 60000 # Oracle连接较慢需要延长超时
maximum-pool-size: 15
3. 动态数据源的核心实现
3.1 数据源切换原理
MyBatis-Plus的动态数据源通过AOP切面实现方法级别的数据源切换。其核心流程:
- 在方法调用前,通过@DS注解识别目标数据源
- 将对应数据源的Connection绑定到当前线程
- 执行SQL操作
- 清理线程绑定(无论成功与否)
3.2 实际应用示例
3.2.1 基础使用
在Service层方法上添加@DS注解:
java复制@Service
public class OrderServiceImpl implements OrderService {
@DS("mysql") // 使用MySQL数据源
public List<Order> getRecentOrders() {
return orderMapper.selectList(new QueryWrapper<>());
}
@DS("oracle") // 切换到Oracle数据源
public boolean saveFinancialRecord(FinancialRecord record) {
return financialRecordMapper.insert(record) > 0;
}
}
3.2.2 事务处理
跨数据源事务需要特殊处理(建议使用分布式事务方案):
java复制@DS("mysql")
@Transactional(rollbackFor = Exception.class)
public void crossDatabaseOperation() {
// 第一步:操作MySQL
mysqlMapper.update(...);
// 手动切换到Oracle
DynamicDataSourceContextHolder.push("oracle");
try {
oracleMapper.insert(...);
} finally {
DynamicDataSourceContextHolder.poll();
}
// 注意:这里的事务只对MySQL有效!
}
踩坑提醒:@Transactional和@DS注解同时存在时,事务只对初始数据源生效
4. 高级配置与性能优化
4.1 连接池调优
混合环境下的连接池配置建议:
| 参数 | MySQL建议值 | Oracle建议值 | 说明 |
|---|---|---|---|
| maximumPoolSize | 20 | 15 | Oracle连接更耗资源 |
| connectionTimeout | 30000ms | 60000ms | Oracle建立连接较慢 |
| idleTimeout | 600000ms | 900000ms | Oracle连接重建成本高 |
| maxLifetime | 1800000ms | 2700000ms | 同上原因 |
| validationTimeout | 5000ms | 10000ms | Oracle验证查询较慢 |
4.2 方言配置
在MyBatisPlusConfig中配置多方言支持:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 动态方言处理
interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor());
// 分页插件(自动识别数据库类型)
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL){
@Override
protected void optimizeType(IPage<?> page, String builderSql) {
// 根据当前数据源动态切换
if(DynamicDataSourceContextHolder.peek().contains("oracle")){
this.setDbType(DbType.ORACLE);
}
}
});
return interceptor;
}
5. 生产环境常见问题排查
5.1 连接泄漏检测
混合环境下特别容易出现连接未关闭的情况。推荐增加监控:
java复制@Slf4j
@Configuration
public class DataSourceMonitor {
@Autowired
private Map<String, DataSource> dataSourceMap;
@Scheduled(fixedRate = 300000) // 5分钟检测一次
public void checkConnectionLeak() {
dataSourceMap.forEach((name, ds) -> {
if(ds instanceof HikariDataSource) {
HikariDataSource hds = (HikariDataSource)ds;
log.info("数据源[{}]状态: 活跃={}, 空闲={}, 等待={}",
name,
hds.getHikariPoolMXBean().getActiveConnections(),
hds.getHikariPoolMXBean().getIdleConnections(),
hds.getHikariPoolMXBean().getThreadsAwaitingConnection());
}
});
}
}
5.2 跨库关联查询解决方案
虽然不推荐直接跨库JOIN,但有时业务必须时可以采用:
java复制public List<CombinedDTO> getCombinedData(Long orderId) {
// 1. 从Oracle获取主数据
@DS("oracle")
OrderMain main = orderMainMapper.selectById(orderId);
// 2. 从MySQL获取明细
@DS("mysql")
List<OrderDetail> details = orderDetailMapper.selectByOrderId(orderId);
// 3. 内存中组合
return details.stream()
.map(d -> new CombinedDTO(main, d))
.collect(Collectors.toList());
}
6. 最佳实践建议
经过多个项目的实践验证,我总结出以下经验:
-
命名规范:数据源名称使用
业务_数据库格式,如trade_mysql、report_oracle -
监控分离:为不同数据源配置独立的监控指标,特别是响应时间阈值应区别设置
-
故障隔离:使用Hystrix或Resilience4j为不同数据源操作配置独立的熔断策略
-
SQL优化:Oracle的批量插入语法与MySQL不同,需要编写适配器:
java复制@DS("oracle")
public void batchInsert(List<Entity> list) {
// Oracle需要使用UNION ALL方式
String sql = "INSERT ALL";
for(Entity e : list) {
sql += " INTO table(f1,f2) VALUES("+e.getF1()+","+e.getF2()+")";
}
sql += " SELECT 1 FROM DUAL";
jdbcTemplate.execute(sql);
}
- 测试策略:在单元测试中模拟数据库不可用场景,验证降级逻辑
这套方案已经在我们的生产环境稳定运行2年多,日均处理百万级跨库操作。关键是要做好连接管理和监控,避免因某个数据库故障导致整个应用不可用。
