动态数据源

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值