如何将AbstractRoutingDataSource返回的数据源接入CrudRepository?
解决方案:将AbstractRoutingDataSource接入JPA/Crud Repository
核心思路是把AbstractRoutingDataSource作为唯一的DataSource实例交给JPA的EntityManagerFactory,所有Repository会通过这个代理数据源动态路由到目标库,无需按包拆分Repository或配置多EntityManager。
1. 配置动态路由数据源
先定义两个实际的数据源(SQL Server、PostgreSQL),再注册AbstractRoutingDataSource作为主数据源:
@Configuration public class DynamicDataSourceConfig { // 配置SQL Server数据源(读取application.yml中spring.datasource.sqlserver开头的配置) @Bean @ConfigurationProperties(prefix = "spring.datasource.sqlserver") public DataSource sqlServerDataSource() { return DataSourceBuilder.create().build(); } // 配置PostgreSQL数据源(读取application.yml中spring.datasource.postgresql开头的配置) @Bean @ConfigurationProperties(prefix = "spring.datasource.postgresql") public DataSource postgreSqlDataSource() { return DataSourceBuilder.create().build(); } // 注册动态路由数据源 @Bean public DataSource dynamicDataSource(DataSource sqlServerDataSource, DataSource postgreSqlDataSource) { AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() { @Override protected Object determineCurrentLookupKey() { // 从上下文获取当前要使用的数据源标识(这里用ThreadLocal存储请求级别的开关) return DataSourceContextHolder.getDataSourceKey(); } }; // 绑定数据源标识与实际数据源 Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("sqlserver", sqlServerDataSource); targetDataSources.put("postgresql", postgreSqlDataSource); routingDataSource.setTargetDataSources(targetDataSources); // 设置默认数据源(迁移初期默认用SQL Server) routingDataSource.setDefaultTargetDataSource(sqlServerDataSource); routingDataSource.afterPropertiesSet(); return routingDataSource; } }
2. 实现数据源上下文管理
用ThreadLocal存储当前线程的数据源标识,支持请求级别的动态切换:
public class DataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); // 设置当前线程的数据源标识 public static void setDataSourceKey(String dataSourceKey) { contextHolder.set(dataSourceKey); } // 获取当前线程的数据源标识 public static String getDataSourceKey() { return contextHolder.get(); } // 清除线程上下文,避免内存泄漏 public static void clearDataSourceKey() { contextHolder.remove(); } }
3. 配置JPA使用动态数据源
只需配置一个EntityManagerFactory,绑定动态数据源,并处理数据库方言的动态切换:
@Configuration @EnableJpaRepositories(basePackages = "com.yourproject.repository") // 指向你的Repository包 public class JpaConfig { @Autowired private DataSource dynamicDataSource; @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) { LocalContainerEntityManagerFactoryBean em = builder .dataSource(dynamicDataSource) .packages("com.yourproject.entity") // 指向你的实体类包 .persistenceUnit("dynamicPersistenceUnit") .build(); // 配置动态方言解析器,根据当前数据源标识返回对应方言 em.getJpaPropertyMap().put("hibernate.dialect_resolver", new DynamicDialectResolver()); return em; } // 自定义方言解析器 private static class DynamicDialectResolver implements DialectResolver { @Override public Dialect resolveDialect(DialectResolutionInfo info) throws HibernateException { String dataSourceKey = DataSourceContextHolder.getDataSourceKey(); if ("postgresql".equals(dataSourceKey)) { return new PostgreSQLDialect(); } else { // 根据你的SQL Server版本选择对应方言,比如SQLServer2012Dialect return new SQLServer2012Dialect(); } } } // 配置事务管理器,绑定同一个EntityManagerFactory @Bean public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { return new JpaTransactionManager(entityManagerFactory); } }
4. 在业务层切换数据源
通过设置DataSourceContextHolder的值,实现动态切换:
@Service public class UserService { @Autowired private UserRepository userRepository; // 从PostgreSQL查询数据 public List<User> getUsersFromPostgreSQL() { try { DataSourceContextHolder.setDataSourceKey("postgresql"); return userRepository.findAll(); } finally { // 必须清除上下文,避免影响后续请求 DataSourceContextHolder.clearDataSourceKey(); } } // 从SQL Server查询数据(默认数据源可省略设置) public List<User> getUsersFromSQLServer() { return userRepository.findAll(); } }
注意事项
- 事务限制:事务启动后无法切换数据源,必须在事务开始前设置数据源标识。
- SQL兼容性:尽量使用JPQL编写查询,避免数据库特定语法;若必须使用原生SQL,需根据数据源标识动态切换语句。
- 全局开关场景:如果是全局级别的功能开关(而非请求级别),可直接在
determineCurrentLookupKey中读取配置中心的开关值(如@Value("${feature.toggle.db}")),无需使用ThreadLocal。
内容的提问来源于stack exchange,提问作者sumek
相关产品推荐
相关产品推荐

