springboot利用AbstractRoutingDataSource实现动态切换数据源

需求:

编写一个代码生成器,前端下拉选择需要自动生成代码的数据库名。后端切换数据库并且生成对应的代码。

动态切换数据源:

springboot提供了一个AbstractRoutingDataSource类。我们可以实现一个类继承AbstractRoutingDataSource并且determineCurrentLookUpKey()方法。

具体步骤:

数据源配置

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    #MySQL配置
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/graduate?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: root
    #MySQL配置
    pqmanager:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/hfb?useUnicode=true&characterEncoding=UTF-8&useSSL=false
      username: root
      password: root
    pq37:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/sys?useUnicode=true&characterEncoding=UTF-8&useSSL=false
      username: root
      password: root
    pqmanager37:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/srb_core?useUnicode=true&characterEncoding=UTF-8&useSSL=false
      username: root
      password: root

编写 DataSourceContextHolder设置和保存当前线程使用的数据源

public class DataSourceContextHolder {

    //默认数据源
    private static final String DEFAULT_DATASOURCE = "pq";

    //保存线程连接的数据源
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }

    public static void setDataSource(String key) {
        CONTEXT_HOLDER.set(key);
    }

    public static void cleanDataSource() {
        CONTEXT_HOLDER.remove();
    }
}

编写DynamicDataSource 

public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * @return 切换数据源的时候该方法会被调用
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }


}

配置类

/**
 * 多数据源配置类
 */
@Configuration
public class DataSourceConfig {

    //@Primary
    @Bean(name = "pq")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSourcePq() {
        return new DruidDataSource();
    }

    @Bean("pq_37")
    @ConfigurationProperties("spring.datasource.pq37")
    public DataSource dataSourcePq37() {
        return new DruidDataSource();
    }

    @Bean("pq_manager_37")
    @ConfigurationProperties("spring.datasource.pqmanager37")
    public DataSource dataSourcePqManager37() {
        return new DruidDataSource();
    }

    @Primary //必须有一个数据源标记为Primary
    @Bean("pq_manager")
    @ConfigurationProperties("spring.datasource.pqmanager")
    public DataSource dataSourcePqManager() {
        return new DruidDataSource();
    }

    /**
     * 数据源选择器 如果此处标记@Primary会导致循环依赖问题
     *
     * @return
     */
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource(@Qualifier("pq") DataSource PqSource,
                                        @Qualifier("pq_manager") DataSource PqManagerSource,
                                        @Qualifier("pq_37") DataSource PqSource37,
                                        @Qualifier("pq_manager_37") DataSource PqManagerSource37) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //配置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(PqSource);
        //保存所有可切换的数据源
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("pq", PqSource);
        dataSourceMap.put("pq_manager", PqManagerSource);
        dataSourceMap.put("pq_37", PqSource37);
        dataSourceMap.put("pq_manager_37", PqManagerSource37);
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }


}
@EnableTransactionManagement
@Configuration
public class MyBatisConfig {

    @Resource(name = "dynamicDataSource")
    private DataSource dynamicDataSource;

    @Autowired
    private MybatisProperties mybatisProperties;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource);
        sqlSessionFactoryBean.setMapperLocations(mybatisProperties.resolveMapperLocations());
        sqlSessionFactoryBean.setConfiguration(mybatisProperties.getConfiguration());
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource);
    }

}

利用拦截器拦截请求,查看请求参数是否存在某种参数(代表需要切换数据源,不存在则为默认数据源)。也可以使用AOP的方法作用于某个方法,

利用自定义注解配置需要切换的数据源,在切面那里只需利用反射得到对应的数据源在进行切换。

/**
 * 拦截请求切换数据源
 */
public class DataSourceInterceptor extends HandlerInterceptorAdapter {

    /**
     * 拦截请求
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String dataSourceKey = request.getParameter("dataSourceKey");
        System.out.println(dataSourceKey);
        if (StringUtils.isNotEmpty(dataSourceKey)) {
            DataSourceContextHolder.setDataSource(dataSourceKey);//重点
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {


        // System.out.println(DataSourceContextHolder.getDataSource());
        DataSourceContextHolder.cleanDataSource();
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        super.afterCompletion(request, response, handler, ex);
    }
}

配置拦截器

@Configuration
public class InterceptorsConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration registration = registry.addInterceptor(new DataSourceInterceptor());
        //拦截所有路径
        registration.addPathPatterns("/sys/**");
    }
}

执行自动生成代码逻辑。

上一篇:Epic Transformation(思维 + 优先队列 + 贪心)


下一篇:leetcode学习02