springboot整合mybatis配置多数据源(mysql/oracle)
'# Spring Boot整合MyBatis配置多数据源(MySQL/Oracle)
一、背景与问题
在分布式系统中,多数据源架构是常见需求。例如电商系统可能需要:
- 订单数据存储在MySQL
- 会员信息存储在Oracle
- 日志系统使用独立数据库
传统单数据源架构无法满足这种需求,需要通过多数据源配置实现:
- 数据隔离:不同业务模块使用独立数据库
- 数据分片:水平分库分表
- 混合架构:同时对接关系型和非关系型数据库
但多数据源架构也带来复杂性:
- 数据源切换逻辑
- 事务一致性问题
- 性能损耗
- 系统复杂度提升
二、基本原理
Spring Boot整合MyBatis的多数据源配置,本质是通过AbstractRoutingDataSource实现动态数据源切换。其核心机制如下:
- 数据源抽象层:通过
AbstractRoutingDataSource抽象多数据源 - 路由策略:实现
determineCurrentLookupKey()方法决定使用哪个数据源 - 数据源注册:通过
DataSourceProperties注册多个数据源 - 事务管理:使用
DataSourceTransactionManager管理事务
三、环境准备
# application.yml
spring:
datasource:
mysql:
url: jdbc:mysql://localhost:3306/mysql_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
oracle:
url: jdbc:oracle:thin:@localhost:1521:orcl
username: sys
password: oracle
driver-class-name: oracle.jdbc.OracleDriver四、核心实现
1. 动态数据源配置类
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.oracle")
public DataSource oracleDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource routingDataSource() {
// 创建抽象数据源
AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource();
// 设置目标数据源
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("mysql", mysqlDataSource());
targetDataSources.put("oracle", oracleDataSource());
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(mysqlDataSource());
return routingDataSource;
}
}关键代码解释:
- 使用
DataSourceBuilder创建数据源 - 通过
AbstractRoutingDataSource实现多数据源 setTargetDataSources注册多个数据源setDefaultTargetDataSource设置默认数据源
2. 数据源路由策略
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
CONTEXT.set(dataSource);
}
public static String getDataSource() {
return CONTEXT.get();
}
public static void clearDataSource() {
CONTEXT.remove();
}
}public class DynamicDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}关键点:
- 使用
ThreadLocal实现线程隔离 - 通过
determineCurrentLookupKey方法决定数据源 - 需要继承
AbstractRoutingDataSource实现路由逻辑
3. 事务管理配置
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}五、完整案例
1. 电商系统案例
场景:订单系统使用MySQL,库存系统使用Oracle
1.1 数据库配置
spring:
datasource:
mysql:
url: jdbc:mysql://localhost:3306/order_db
username: root
password: root
oracle:
url: jdbc:oracle:thin:@localhost:1521:inventory_db
username: sys
password: oracle1.2 数据源路由配置
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.oracle")
public DataSource oracleDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource routingDataSource() {
AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("mysql", mysqlDataSource());
targetDataSources.put("oracle", oracleDataSource());
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(mysqlDataSource());
return routingDataSource;
}
}1.3 业务逻辑
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public void createOrder(Order order) {
DataSourceContextHolder.setDataSource("mysql");
orderMapper.insert(order);
}
}@Service
public class InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
public void updateInventory(Inventory inventory) {
DataSourceContextHolder.setDataSource("oracle");
inventoryMapper.update(inventory);
}
}六、源码解析
1. AbstractRoutingDataSource原理
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
protected Map<Object, Object> targetDataSources = new LinkedHashMap<>();
protected Object defaultTargetDataSource;
protected abstract Object determineCurrentLookupKey();
}关键机制:
- 通过
determineCurrentLookupKey()获取当前数据源标识 - 使用
ThreadLocal实现线程隔离 - 支持动态切换数据源
2. 数据源切换过程
- 调用
setDataSource("mysql")设置当前数据源 - 在SQL执行时,
AbstractRoutingDataSource会根据当前线程的ThreadLocal获取数据源 - 实际执行时会从
targetDataSources中获取对应的数据源
七、进阶使用
1. 动态数据源切换策略
public class CustomDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String dataSource = getDataSourceFromRequest();
return dataSource != null ? dataSource : "mysql";
}
}2. 多数据源事务管理
@Transactional
public void transferMoney(String from, String to, BigDecimal amount) {
DataSourceContextHolder.setDataSource("mysql");
accountService.transfer(from, to, amount);
DataSourceContextHolder.setDataSource("oracle");
inventoryService.updateInventory();
}八、性能与工程实践
1. 性能优化策略
连接池配置:使用HikariCP优化连接池
spring: datasource: mysql: hikari: maximum-pool-size: 10 idle-timeout: 30000 connection-timeout: 30000- SQL优化:避免全表扫描,使用索引
- 缓存机制:对频繁访问的数据使用Redis缓存
2. 安全风险分析
- 数据库权限:避免使用高权限账户
- SQL注入:使用预编译语句
- 数据隔离:确保不同数据源的隔离性
九、常见问题与踩坑
1. 常见错误
错误示例:
public class MyService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void test() {
jdbcTemplate.execute("SELECT * FROM orders");
}
}问题分析:
- 未指定数据源
- 默认使用主数据源
- 可能导致数据错乱
解决方法:
public void test() {
DataSourceContextHolder.setDataSource("mysql");
jdbcTemplate.execute("SELECT * FROM orders");
}2. 事务管理问题
错误场景:
@Transactional
public void transfer() {
DataSourceContextHolder.setDataSource("mysql");
accountService.transfer();
DataSourceContextHolder.setDataSource("oracle");
inventoryService.update();
}问题分析:
- 事务未跨越数据源
- 可能导致部分操作失败
解决方法:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transfer() {
accountService.transfer();
inventoryService.update();
}十、最佳实践
1. 推荐方案
- 数据源隔离:每个业务模块使用独立数据源
- 路由策略:根据业务逻辑动态切换数据源
- 事务管理:使用
@Transactional保证事务一致性 - 连接池优化:配置合理的连接池参数
- 安全控制:限制数据库访问权限
2. 避免使用场景
- 数据源数量过多:超过3个数据源时应考虑其他方案
- 复杂事务场景:涉及多个数据源的事务需要特殊处理
- 频繁切换:避免不必要的数据源切换
十一、总结
Spring Boot整合MyBatis配置多数据源是构建复杂系统的重要技术。通过AbstractRoutingDataSource实现动态数据源切换,结合事务管理和连接池优化,可以有效应对多数据源场景下的业务需求。实际应用中应根据业务复杂度选择合适的实现方案,注意避免常见错误,合理进行性能优化和安全控制。多数据源架构虽然复杂,但合理的设计可以带来显著的系统扩展性和灵活性。
评论已关闭