使用AbstractRoutingDataSource实现动态切换数据源未生效问题

文章探讨了在使用AbstractRoutingDataSource进行数据源动态切换时遇到的一个问题,即JPA查询能正常切换,但通过EntityManager创建的查询无法进入determineCurrentLookupKey()方法。作者发现,问题可能在于数据源切换后,EntityManager仍使用初始的数据源。解决方案是在创建查询前调用entityManager.getEntityManagerFactory().createEntityManager(),这似乎能确保正确地切换数据源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近使用AbstractRoutingDataSource来实现动态切换数据源的功能,发现jpa实现的查询都可以正常实现数据源的动态切换,唯独使用EntityManager创建查询没有进入determineCurrentLookupKey()方法。
调试一番之后发现在创建查询前加上这一行代码即可解决问题。

entityManager.getEntityManagerFactory().createEntityManager();

猜测应该是数据源切换了之后,EntityManager还是使用的第一次初始化使用的数据源,导致切换查询还是查询的之前的数据源。

### Java 动态切换数据源的方法 在现代企业级应用中,使用多个数据源来满足不同需求非常普遍。为了提高性能、实现负载均衡、支持多租户架构或灾备恢复等功能,动态切换数据源成为一项重要技能[^1]。 #### 配置文件设置 通常情况下,可以通过配置文件定义多种数据源属性。例如,在 `application.yml` 或者 `application.properties` 文件中指定各个数据源的相关参数: ```yaml spring: datasource: primary: url: jdbc:mysql://localhost:3306/primary_db?useSSL=false&serverTimezone=UTC username: root password: secret driver-class-name: com.mysql.cj.jdbc.Driver secondary: url: jdbc:mysql://localhost:3306/secondary_db?useSSL=false&serverTimezone=UTC username: user password: pass driver-class-name: com.mysql.cj.jdbc.Driver ``` #### 创建自定义的数据源上下文管理器 创建一个用于管理和切换当前线程使用的具体数据源名称的工具类,比如名为 `DynamicDataSourceContextHolder.java`: ```java public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDataSourceKey(String key) { System.out.println("Switching DataSource to " + key); CONTEXT_HOLDER.set(key); } public static String getDataSourceKey() { return CONTEXT_HOLDER.get(); } public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } } ``` #### 定义抽象路由策略 编写继承自 `AbstractRoutingDataSource` 的类作为实际执行数据源选择逻辑的地方,命名为 `DynamicDataSourceRouter.java` : ```java import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSourceRouter extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceKey(); } } ``` #### 自动装配并注册所有已知的数据源到容器内 利用 Spring Boot 提供的强大功能自动扫描项目中的 Bean 并将其注入至所需位置。这里需要特别注意的是要确保所有的数据源都被正确加载并且能够被上述路由器识别出来。 ```java @Configuration @MapperScan(basePackages = {"com.example.mapper"}) @EnableTransactionManagement(proxyTargetClass=true) public class DataSourceConfig { @Bean(name="dataSourcePrimary") @ConfigurationProperties(prefix="spring.datasource.primary") public DataSource dataSourcePrimary(){ return DataSourceBuilder.create().build(); } @Bean(name="dataSourceSecondary") @ConfigurationProperties(prefix="spring.datasource.secondary") public DataSource dataSourceSecondary(){ return DataSourceBuilder.create().build(); } @Bean(name = "dynamicDataSource") public DynamicDataSourceRouter dynamicDataSource(@Qualifier("dataSourcePrimary") DataSource dataSourcePrimary, @Qualifier("dataSourceSecondary") DataSource dataSourceSecondary){ Map<Object,Object> targetDataSources=new HashMap<>(); targetDataSources.put("PRIMARY",dataSourcePrimary); targetDataSources.put("SECONDARY",dataSourceSecondary); DynamicDataSourceRouter dynamicDataSource =new DynamicDataSourceRouter(); dynamicDataSource.setDefaultTargetDataSource(dataSourcePrimary); // 默认指向主库 dynamicDataSource.setTargetDataSources(targetDataSources); return dynamicDataSource; } @Bean public PlatformTransactionManager transactionManager(DynamicDataSourceRouter dynamicDataSource) throws Exception{ return new DataSourceTransactionManager(dynamicDataSource); } } ``` #### 切换数据源的实际操作 当想要改变正在访问的目标数据库时,只需要简单地调用之前提到过的静态方法即可完成切换动作。下面给出一段简单的测试代码片段展示如何做到这一点: ```java @Service public class TestService { @Autowired private JdbcTemplate jdbcTemplate; public List<Map<String, Object>> queryFromPrimaryDb() { DynamicDataSourceContextHolder.setDataSourceKey("PRIMARY"); try { return jdbcTemplate.queryForList("SELECT * FROM table_in_primary LIMIT 5"); } finally { DynamicDataSourceContextHolder.clearDataSourceKey(); } } public int insertIntoSecondaryDb(Map<String, ?> params) { DynamicDataSourceContextHolder.setDataSourceKey("SECONDARY"); try { return jdbcTemplate.update("INSERT INTO table_in_secondary (...) VALUES (...)", params.toArray()); } finally { DynamicDataSourceContextHolder.clearDataSourceKey(); } } } ``` 需要注意的是,在同一个类内部调用带有 `@DS` 注解的方法可能会导致 Spring 的代理机制不生效,进而使得数据源切换失败。遇到这种情况可以尝试改用 Spring 的 `ApplicationContext` 进行间接调用来解决问题[^3]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值