动态数据源
来源:互联网 发布:剑网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
- 动态数据源
- 动态数据源
- 动态加载数据源
- 动态加载数据源
- 动态配置ODBC数据源
- 动态注册数据源格式
- 动态配置ODBC数据源
- report viewer 动态数据源
- reportviewer动态数据源
- 动态配置odbc数据源
- ODBC动态创建数据源
- Grid数据源动态变化
- ODBC 动态配置数据源
- birt 配置动态数据源
- Spring动态切换数据源
- spring动态数据源1
- reportviewer动态数据源
- 动态切换数据源
- jQuery实现页面滚动时顶部动态显示隐藏
- 基于JQuery EasyUI的WebMVC控件封装(含源码)
- 狗年出生的宝宝取名还可以借助诗经内容?看这里,有你需要的取名方法
- 浏览器缓存
- LeetCode-538. Convert BST to Greater Tree
- 动态数据源
- java.util.concurrent.CyclicBarrier
- phpStorm如何进行xDebug断点调试
- 关于Think3 配置邮箱发送遇到的问题总结
- Thinkphp5 常量设置问题
- windows下zk无法启动
- JavaScript关于继承
- mysql+php+ajax实现登录(无加密)
- Android 控制金额输入格式 保留两位小数