天天看点

springboot springjpa 从数据库动态加载多数据源 并随意切换

背景:项目本来就是springboot+springJpa的框架,连接池用的德鲁伊DruidDataSource。

想要在数据库配置多数据源,用户可以随便新增数据源,根据请求带的不同参数,controller层随意切换数据源,这样就可以同一套部署的代码,操作不同的数据库,有点 SaaS的味道,不过是各自独立的库。

1.配置默认数据源

2.启动之后加载数据库的数据源 并且加载到bean交给spring管理(数据源增删改的时候也要执行一遍本方法)

3.controller里面指定数据源。

1.德鲁数据源常见的springboot配置

  原有的配置是只有primaryDataSource 这个 并且加了 Primary注解。那么我们要加一个,默认Primary数据源指向一个动态数据源 即 dynamicDataSource。并且 注入我们的primaryDataSource。这样 DynamicDataSource里就有一个数据源了。代码如下。

DynamicDataSource继承了AbstractRoutingDataSource,AbstractRoutingDataSource是springboot提供的数据源路由。      
@Configuration
public class DSConfig {
   
    @Bean(name = "primaryDataSource")
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")

   public DataSource primaryDataSource(){
        return DruidDataSourceBuilder.create().build();
    }


    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dynamicDataSource(@Qualifier("primaryDataSource")DataSource dataSource){
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.myMap = new HashMap<>();//保存我们有的数据源,方便后面动态增加
        dynamicDataSource.myMap.put("1",dataSource);
        dynamicDataSource.setTargetDataSources(dynamicDataSource.myMap);//父类的方法
        dynamicDataSource.setDefaultTargetDataSource(dataSource);//父类的方法
        return  dynamicDataSource;
    }
}      

2.启动后加载数据库配置的数据源,新建一个类,注入查询数据源表的service和ApplicationContext。

用@PostConstruct 注解 来默认程序启动即执行。(增删改的时候同样执行类似代码,此处省略)

@Resource
   private SaasDataBase saasDataBase;      
@Autowired
    private ApplicationContext ctx;


    /**
     * 初始化
     */
    @PostConstruct
    public void init() {
//获取BeanFactory
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory();
//创建bean信息.
//循环saasDataBase从数据库查出的数据源列表,此处省略
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DruidDataSource.class);
        beanDefinitionBuilder.addPropertyValue("url", "jdbc:oracle:thin:@ip:1521:orcl");
        beanDefinitionBuilder.addPropertyValue("username", "XX");
        beanDefinitionBuilder.addPropertyValue("password", "XX");
        beanDefinitionBuilder.addPropertyValue("initialSize", 5);
        beanDefinitionBuilder.addPropertyValue("maxActive", 20);
        beanDefinitionBuilder.addPropertyValue("minIdle", 5);
        beanDefinitionBuilder.addPropertyValue("maxWait", 30000);
        //动态注册bean. bean的名字为第一个参数、
        defaultListableBeanFactory.registerBeanDefinition("testDataSource",       beanDefinitionBuilder.getBeanDefinition());
//获取动态注册的bean.
        DataSource testDataSource =ctx.getBean("testDataSource",DruidDataSource.class);
        dynamicDataSource.myMap.put("2",testDataSource);//指定key为2的数据源
        dynamicDataSource.setTargetDataSources(dynamicDataSource.myMap);
        dynamicDataSource.afterPropertiesSet();//一定要执行这个方法 具体含义可以看父类代码

      

3.数据源已经都注入后,就剩下动态切换了,这个大家应该都看到过网上的案例  

DynamicDataSource 和 DynamicDataSourceContextHolder  实现动态切换数据源的路由      
public class DynamicDataSource extends AbstractRoutingDataSource {

    public Map<Object,Object> myMap = null;
    @Override

    protected Object determineCurrentLookupKey() {

        /*

         * DynamicDataSourceContextHolder代码中使用setDataSourceType

         * 设置当前的数据源,在路由类中使用getDataSourceType进行获取,

         *  交给AbstractRoutingDataSource进行注入使用。

         */

        return DynamicDataSourceContextHolder.getDataSourceType();

    }

}      
public class DynamicDataSourceContextHolder {



    /*

     * 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,

     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

     */

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();



    public static List<String> dataSourceIds = new ArrayList<String>();





    public static void setDataSourceType(String dataSourceType) {

        contextHolder.set(dataSourceType);

    }



    public static String getDataSourceType() {

        return contextHolder.get();

    }



    public static void clearDataSourceType() {

        contextHolder.remove();

    }





    public static boolean containsDataSource(String dataSourceId){

        return dataSourceIds.contains(dataSourceId);

    }

}      

以上两个类  就是针对AbstractRoutingDataSource的利用,根据我们指定的 key 1为primaryDataSource这个bean key2为我们指定的testDataSource 这个bean,在controller或者其他地方 直接set就可以了。

//            DynamicDataSourceContextHolder.setDataSourceType("1");
//            DynamicDataSourceContextHolder.setDataSourceType("2");      

这里可以根据需要,根据判断条件随意设置。

以上3步写好之后,就完美实现了spingboot对springjpa的动态数据源切换。

springboot内置的默认EntityManager等东西不需要再具体配置,还是默认方式就行,因为他是基于@Primary 指定的这个 dynamicDataSource了。dynamicDataSource动态的变,一切都会随着变。

继续阅读