SpringBoot 多数据源配置
一. 创建动态数据源和数据源切换器
创建一个继承自
AbstractRoutingDataSource
的动态数据源类,用于根据当前线程的上下文选择数据源:import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceKey(); } }
创建一个
DataSourceContextHolder
类,用于保存当前线程使用的数据源标识:public class DataSourceContextHolder { private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<>(); public static void setDataSourceKey(String key) { dataSourceKey.set(key); } public static String getDataSourceKey() { return dataSourceKey.get(); } public static void clearDataSourceKey() { dataSourceKey.remove(); } }
二. 数据源注入
在Spring Boot的配置类中配置多个数据源,并将它们添加到
DynamicDataSource
中:import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; @Configuration public class DataSourceConfig { @Bean(name = "dataSource1") @ConfigurationProperties("spring.datasource.dataSource1") public DataSource dataSource1() { // 配置第一个数据源 // ... } @Bean(name = "dataSource2") @ConfigurationProperties("spring.datasource.dataSource2") public DataSource dataSource2() { // 配置第二个数据源 // ... } @Primary @Bean(name = "datasource") public AbstractRoutingDataSource routingDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("dataSource1", dataSource1()); dataSourceMap.put("dataSource2", dataSource2()); dynamicDataSource.setTargetDataSources(dataSourceMap); dynamicDataSource.setDefaultTargetDataSource(dataSource1()); return dynamicDataSource; } }
移除SpringBoot中关于DataSource的自动配置, 解决循环依赖问题
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
三. 在业务中切换数据源
在需要切换数据源的地方,使用
DataSourceContextHolder.setDataSourceKey("dataSource1")
来设置当前线程使用的数据源标识:DataSourceContextHolder.setDataSourceKey("dataSource1"); ds1Service.findAll(); DataSourceContextHolder.setDataSourceKey("dataSource2"); ds2Service.findAll(); DataSourceContextHolder.clearDataSourceKey();
确保在使用完数据源后,及时调用
DataSourceContextHolder.clearDataSourceKey()
来清除当前线程的数据源标识,以避免影响其他线程。
四. 拓展: 使用Interceptor
为请求动态注入数据源
定义
DataSourceInterceptor
拦截器, 用来设置当前线程的数据源.public class DataSourceInterceptor extends HandlerInterceptorAdapter { private DynamicDataSource dynamicDataSource; public DataSourceInterceptor(DynamicDataSource dynamicDataSource) { this.dynamicDataSource = dynamicDataSource; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String dataSourceKey = determineDataSourceKey(request); DataSourceContextHolder.setDataSourceKey(dataSourceKey); return true; } private String determineDataSourceKey(HttpServletRequest request) { // 根据请求信息来确定数据源的标识 // 这里可以根据需要自行实现逻辑,例如根据请求参数、请求头等来决定使用哪个数据源 // 返回的数据源标识需要与之前配置的数据源名称对应 return "dataSource1"; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { DataSourceContextHolder.clearDataSourceKey(); } }
在
preHandle()
方法中,可以根据请求信息来确定当前请求应该使用的数据源,然后通过DataSourceContextHolder.setDataSourceKey(dataSourceKey)
方法将数据源标识设置到DataSourceContextHolder
中。在afterCompletion()
方法中,清除了DataSourceContextHolder
中的数据源标识,以确保每个请求结束后都能正确地清除数据源设置。注册数据源切换拦截器
该拦截器用于在每个请求前设置当前线程的数据源。
@Configuration public class AppConfig implements WebMvcConfigurer { @Autowired private DynamicDataSource dynamicDataSource; @Bean public DataSourceTransactionManager transactionManager() { return new DataSourceTransactionManager(dynamicDataSource); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new DataSourceInterceptor(dynamicDataSource)); } }