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动态数据源切换的资料请关注脚本之家其它相关文章!