MyBatis动态数据源切换的完整方案
作者:墨瑾轩
在微服务架构和分布式系统中,动态数据源是应对多环境切换、读写分离、多租户等复杂场景的核心工具,MyBatis 与 Spring 结合后,可以灵活实现数据源的动态切换,以下是实现 MyBatis 动态数据源的完整方案,需要的朋友可以参考下
在微服务架构和分布式系统中,动态数据源是应对多环境切换、读写分离、多租户等复杂场景的核心工具。MyBatis 与 Spring 结合后,通过 AbstractRoutingDataSource 和 线程上下文管理(ThreadLocal),可以灵活实现数据源的动态切换。以下是实现 MyBatis 动态数据源的完整方案,涵盖核心步骤、代码示例和最佳实践。
1. 核心概念
- 动态数据源:根据业务需求自动选择不同的数据库连接(如主库、从库、测试库)。
AbstractRoutingDataSource:Spring 提供的抽象类,负责动态决定当前使用哪个数据源。ThreadLocal:线程安全的上下文管理工具,保存当前线程的数据源标识(如master、slave)。
2. 实现步骤
(1) 配置多个数据源
在 application.yml 或 application.properties 中定义多个数据源配置:
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
(2) 创建动态数据源类
继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey 方法:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 从线程上下文中获取当前数据源标识
return DataSourceContextHolder.getDataSource();
}
}
(3) 数据源上下文管理
使用 ThreadLocal 保存当前线程的数据源标识:
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
(4) 配置动态数据源 Bean
在 Spring 配置类中定义动态数据源,并绑定多个数据源:
@Configuration
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource dynamicDataSource(
@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave", slaveDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource); // 默认使用主数据源
return dynamicDataSource;
}
}
(5) 整合 MyBatis
配置 SqlSessionFactory 和 MapperScannerConfigurer,确保使用动态数据源:
@Configuration
@MapperScan(basePackages = "com.example.mapper")
public class MyBatisConfig {
@Autowired
private DataSource dynamicDataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dynamicDataSource);
return factoryBean.getObject();
}
}
(6) 动态切换数据源
在业务代码中通过 DataSourceContextHolder 设置数据源标识:
// 切换到从数据源(读操作)
DataSourceContextHolder.setDataSource("slave");
try {
// 执行查询操作
List<User> users = userMapper.selectAll();
} finally {
// 清除线程上下文
DataSourceContextHolder.clearDataSource();
}
// 切换到主数据源(写操作)
DataSourceContextHolder.setDataSource("master");
try {
userMapper.insert(user);
} finally {
DataSourceContextHolder.clearDataSource();
}
(7) 事务管理
为每个数据源配置独立的事务管理器,并通过 @Transactional 注解指定事务管理器:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean(name = "masterTransactionManager")
public PlatformTransactionManager masterTransactionManager(
@Qualifier("masterDataSource") DataSource masterDataSource) {
return new DataSourceTransactionManager(masterDataSource);
}
@Bean(name = "slaveTransactionManager")
public PlatformTransactionManager slaveTransactionManager(
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
return new DataSourceTransactionManager(slaveDataSource);
}
}
// 在 Service 层指定事务管理器
@Service
public class UserService {
@Transactional("masterTransactionManager")
public void insertUser(User user) {
userMapper.insert(user);
}
@Transactional("slaveTransactionManager")
public List<User> selectAllUsers() {
return userMapper.selectAll();
}
}
3. 使用场景
读写分离
- 写操作(
INSERT/UPDATE/DELETE)使用主库,读操作(SELECT)使用从库。 - 示例:订单写入主库,订单查询从库。
多环境切换
- 开发环境、测试环境、生产环境分别绑定不同的数据源。
- 示例:通过配置文件切换不同数据库地址。
多租户隔离
- 每个租户使用独立的数据源,通过租户标识动态切换。
- 示例:企业级 SaaS 系统,不同企业访问不同数据库。
4. 注意事项
线程安全
- 确保
ThreadLocal在异步任务中传递数据源标识(如使用InheritableThreadLocal或TransmittableThreadLocal)。
事务一致性
- 跨数据源的事务需谨慎处理,避免脏读或数据不一致。可使用分布式事务框架(如 Seata)。
性能优化
- 避免频繁切换数据源,合理设计读写分离策略。
异常处理
- 捕获数据源切换异常,提供回退机制(如默认使用主数据源)。
5. 总结
| 特性 | 实现方式 |
|---|---|
| 动态切换 | AbstractRoutingDataSource + ThreadLocal |
| 读写分离 | 通过业务逻辑判断操作类型,设置 master/slave |
| 多环境支持 | 通过配置文件区分不同环境的数据源 |
| 事务管理 | 为每个数据源配置独立的 PlatformTransactionManager |
| 线程安全 | 使用 ThreadLocal 保存上下文,异步场景需传递上下文 |
通过以上方案,MyBatis 动态数据源可以灵活适配多种业务场景,成为多环境切换的“神器”。结合 Spring 的强大生态,开发者可以轻松实现高可用、高性能的分布式系统。
以上就是MyBatis动态数据源切换的完整方案的详细内容,更多关于MyBatis动态数据源切换的资料请关注脚本之家其它相关文章!
