主要介绍动态切换数据源,相同表结构的不同数据源之间的切换
使用springmvc+mybatis maven构建项目
<bean id="dynamicDataSource" class="com.locatech.dynamic.datasource.DynamicDataSource" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="defaultDataSource" value-ref="dataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" lazy-init="false">
<property name="driverClassName" value="${database.jdbc.driverClass}" />
<property name="url" value="${database.jdbc.connectionURL}" />
<property name="username" value="${database.jdbc.username}" />
<property name="password" value="${database.jdbc.password}" />
<property name="initialSize" value="${database.jdbc.initialSize}" />
<property name="maxActive" value="${database.jdbc.maxActive}" />
<property name="maxIdle" value="${database.jdbc.maxIdle}" />
<property name="maxWait" value="${database.jdbc.maxWait}" />
<property name="validationQuery" value="${database.jdbc.validationQuery}" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" scope="singleton">
<property name="dataSource" ref="dynamicDataSource" />
<property name="configLocation" value="classpath:mybatis-configuration.xml"></property>
<property name="mapperLocations" value="classpath:com/locatech/dynamic/entity/*.xml"></property>
</bean>
如上,applicationContext.xml文件的主要配置,与一般的springmvc+mybatis多了如下一段代码,这段就是动态数据源的只要配置
<bean id="dynamicDataSource" class="com.locatech.dynamic.datasource.DynamicDataSource" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="defaultDataSource" value-ref="dataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource"/>
</bean>
创建了一个com.locatech.dynamic.datasource.DynamicDataSource类的bean,主要有二个属性 <property name="targetDataSources">
和 <property name="defaultTargetDataSource"
这二个都来自于springmvc提供的abstract class AbstractRoutingDataSource。这个类就是springmvc提供给我们实现动态数据源的。 我们实现了一个DynamicDataSource类来继承AbstractRoutingDataSource 类。public final class DynamicDataSource extends AbstractRoutingDataSource implements ApplicationContextAware{
所以上面的bean是关于DynamicDataSource类的。
二个属性 都设置了默认值,默认值对应你默认的数据源。
看看DynamicDataSource类
public final class DynamicDataSource extends AbstractRoutingDataSource implements ApplicationContextAware{
private static final String DATA_SOURCES_NAME = "targetDataSources";
private static ApplicationContext applicationContext ;
@Override
protected Object determineCurrentLookupKey() {
DataSourceBeanBuilder dataSourceBeanBuilder = DataSourceHolder.getDataSource();
System.out.println("----进入determineCurrentLookupKey---"+dataSourceBeanBuilder);
if (dataSourceBeanBuilder == null) {
return null;
}
DataSourceBean dataSourceBean = new DataSourceBean(dataSourceBeanBuilder);
//查看当前容器中是否存在
try {
//得到targetDataSources中Map<Object,Object>
Map<Object,Object> map=getTargetDataSources();
//System.out.println(map.keySet());
synchronized (this) {
//如果不存在,则把DataSourceHolder中的得到dataSourceBean放入TargetDataSources
if (!map.keySet().contains(dataSourceBean.getBeanName())) {
map.put(dataSourceBean.getBeanName(), createDataSource(dataSourceBean));
super.afterPropertiesSet();//通知spring有bean更新
}
System.out.println("map.keySet():"+map.keySet());
}
return dataSourceBean.getBeanName();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException();
}
}
private Object createDataSource(DataSourceBean dataSourceBean) throws IllegalAccessException {
//在spring容器中创建并且声明bean
ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(BasicDataSource.class);
//将dataSourceBean中的属性值赋给目标bean
Map<String, Object> properties = getPropertyKeyValues(DataSourceBean.class, dataSourceBean);
for (Map.Entry<String, Object> entry : properties.entrySet()) {
beanDefinitionBuilder.addPropertyValue((String) entry.getKey(), entry.getValue());
}
beanFactory.registerBeanDefinition(dataSourceBean.getBeanName(), beanDefinitionBuilder.getBeanDefinition());
return applicationContext.getBean(dataSourceBean.getBeanName());
}
//得到已经创建的targetDataSources
@SuppressWarnings("unchecked")
private Map<Object, Object> getTargetDataSources() throws NoSuchFieldException, IllegalAccessException {
Class ards = AbstractRoutingDataSource.class;
Field field = ards.getDeclaredField(DATA_SOURCES_NAME);//dynamicDataSource.getClass().getDeclaredField(DATA_SOURCES_NAME);
field.setAccessible(true);
return (Map<Object, Object>) field.get(this);
}
private <T> Map<String, Object> getPropertyKeyValues(Class<T> clazz, Object object) throws IllegalAccessException {
Field[] fields = clazz.getDeclaredFields();
Map<String, Object> result = new HashMap<>();
for (Field field : fields) {
field.setAccessible(true);
result.put(field.getName(), field.get(object));
}
result.remove("beanName");
return result;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println(applicationContext);
this.applicationContext=applicationContext;
}
}
它 @Override
protected Object determineCurrentLookupKey() { }
这是父类的一个方法。看看具体实现。(方法名字面意思理解即可)
1、得到dataSourceBeanBuilder (使用了构造器模式,DataSourceHolder.getDataSource();可以得到你放入DataSourceHolder的数据源)
2、查看当前容器中是否存在1取得的bean
(1)getTargetDataSources(); //得到dynamicDataSource bean中的targetDataSources属性。
(2)判断是否存在
不存在执行3、4
存在直接返回dataSourceBeanBuilder对应的beanName
3、createDataSource(dataSourceBean) //在spring容器中创建并且声明bean
4、 map.put(dataSourceBean.getBeanName(), createDataSource(dataSourceBean)); //装进targetDataSources的map里面
super.afterPropertiesSet();//通知spring有bean更新 ,只有通知了才知道。
那为什么实现了这个类就可以切换数据源了呢?
看看 AbstractRoutingDataSource
可以明显看到bean对应的二个属性
在applicationContext.xml文件中,我们把sqlSessionFactory bean的DataSource属性关联到了dynamicDataSource bean 。
因为
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
继承 AbstractDataSource类
而AbstractDataSource类实现DataSource
public abstract class AbstractDataSource implements DataSource {
故dynamicDataSource可以传入。
扯远了。。。继续看AbstractRoutingDataSource是一个DataSource ,并用他的之类初始化了sqlSessionFactory
那么看看会被掉用的getConnection方法
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
使用了determineTargetDataSource()返回的datasource的getConnection()方法。
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
这一段的逻辑就很明显了,得到了determineCurrentLookupKey()返回的lookupKey 作为key的datasource
如果返回的lookupKey 为null且dataSource == null则调用默认的DataSource。
private Map<Object, DataSource> resolvedDataSources;
这里就讲完了主要的实现。
看看如何使用。
public class mytest{
InstanceDao ds;
Instance myds;
@Test
public void test(){
//改变数据库前
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
ds = applicationContext.getBean(InstanceDao.class);
System.out.println(ds);
myds =ds.getInstanceById("");
System.out.println(myds);
//改变数据库后
DataSourceBeanBuilder dataSourceBeanBuilder = new DataSourceBeanBuilder("localhost",
"127.0.0.1","3306","dynamic","root","root");
DataSourceContext.setDataSource(dataSourceBeanBuilder);
ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
ds = applicationContext1.getBean(InstanceDao.class);
System.out.println(ds);
myds =ds.getInstanceById("");
System.out.println(myds);
}
}
这是一个测试类,主要看看改变数据库之后
DataSourceBeanBuilder dataSourceBeanBuilder = new DataSourceBeanBuilder("localhost",
"127.0.0.1","3306","databaseName","usename","password");
DataSourceContext.setDataSource(dataSourceBeanBuilder);
这段代码改变了数据库。
public class DataSourceContext {
//使用该方法设置数据源
public static void setDataSource(DataSourceBeanBuilder dataSourceBeanBuilder) {
DataSourceHolder.setDataSource(dataSourceBeanBuilder);
}
//使用该方法清除数据源,清除后将使用默认数据源
public static void clearDataSource() {
DataSourceHolder.clearDataSource();
}
}
public final class DataSourceHolder {
private static ThreadLocal<DataSourceBeanBuilder> threadLocal=new ThreadLocal<DataSourceBeanBuilder>(){
@Override
protected DataSourceBeanBuilder initialValue() {
return null;
}
};
static DataSourceBeanBuilder getDataSource(){
return threadLocal.get();
}
public static void setDataSource(DataSourceBeanBuilder dataSourceBeanBuilder){
threadLocal.set(dataSourceBeanBuilder);
}
public static void clearDataSource(){
threadLocal.remove();
}
}
用ThreadLocal保证了线程安全。看到这里可以回想起DynamicDataSource类重写的determineCurrentLookupKey() 方法的第一句 DataSourceBeanBuilder dataSourceBeanBuilder = DataSourceHolder.getDataSource();
所以只要执行上面二行代买就可以用了。
具体的数据库和 DataSourceBeanBuilder 类和DataSource类可以看我传到git上面的代码。https://gitee.com/kewensi/DynamicDataSource