背景:项目本来就是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动态的变,一切都会随着变。