动态数据源

来源:互联网 发布:剑网3炮哥捏脸数据 编辑:程序博客网 时间:2024/06/06 14:18

主要介绍动态切换数据源,相同表结构的不同数据源之间的切换
使用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

原创粉丝点击