`
fangang
  • 浏览: 861468 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
311c4c32-b171-3767-b974-d26acf661fb2
谈谈用例模型的那些事儿
浏览量:37694
767c50c5-189c-3525-a93f-5884d146ee78
一次迭代式开发的研究
浏览量:67797
03a3e133-6080-3bc8-a960-9d915ed9eabc
我们应当怎样做需求分析
浏览量:405928
753f3c56-c831-3add-ba41-b3b70d6d913f
重构,是这样干的
浏览量:85731
社区版块
存档分类
最新评论

如何在spring框架中解决多数据源的问题

阅读更多

在我们的项目中遇到这样一个问题:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在springhibernate框架中总是配置一个数据源,因而sessionFactorydataSource属性总是指向这个数据源并且恒定不变,所有DAO在使用sessionFactory的时候都是通过这个数据源访问数据库。但是现在,由于项目的需要,我们的DAO在访问sessionFactory的时候都不得不在多个数据源中不断切换,问题就出现了:如何让sessionFactory在执行数据持久化的时候,根据客户的需求能够动态切换不同的数据源?我们能不能在spring的框架下通过少量修改得到解决?是否有什么设计模式可以利用呢? 

 

问题的分析

我首先想到在springapplicationContext中配置所有的dataSource。这些dataSource可能是各种不同类型的,比如不同的数据库:OracleSQL ServerMySQL等,也可能是不同的数据源:比如apache 提供的org.apache.commons.dbcp.BasicDataSourcespring提供的org.springframework.jndi.JndiObjectFactoryBean等。然后sessionFactory根据客户的每次请求,将dataSource属性设置成不同的数据源,以到达切换数据源的目的。

 

但是,我很快发现一个问题:当多用户同时并发访问数据库的时候会出现资源争用的问题。这都是“单例模式”惹的祸。众所周知,我们在使用spring框架的时候,在beanFactory中注册的bean基本上都是采用单例模式,即spring在启动的时候,这些bean就装载到内存中,并且每个bean在整个项目中只存在一个对象。正因为只存在一个对象,对象的所有属性,更准确说是实例变量,表现得就如同是个静态变量(实际上“静态”与“单例”往往是非常相似的两个东西,我们常常用“静态”来实现“单例”)。拿我们的问题来说,sessionFactory在整个项目中只有一个对象,它的实例变量dataSource也就只有一个,就如同一个静态变量一般。如果不同的用户都不断地去修改dataSource的值,必然会出现多用户争用一个变量的问题,对系统产生隐患。

 

通过以上的分析,解决多数据源访问问题的关键,就集中在sessionFactory在执行数据持久化的时候,能够通过某段代码去根据客户的需要动态切换数据源,并解决资源争用的问题。

 

问题的解决

  • 采用Decorator设计模式

要解决这个问题,我的思路锁定在了这个dataSource上了。如果sessionFactory指向的dataSource可以根据客户的需求去连接客户所需要的真正的数据源,即提供动态切换数据源的功能,那么问题就解决了。那么我们怎么做呢?去修改那些我们要使用的dataSource源码吗?这显然不是一个好的方案,我们希望我们的修改与原dataSource代码是分离的。根据以上的分析,使用GoF设计模式中的Decorator模式(装饰者模式)应当是我们可以选择的最佳方案。

 

什么是“Decorator模式”?简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。当我们使用Decorator的时候与原类完全一样,当Decorator的某些功能却已经修改为了我们需要修改的功能。Decorator模式的结构如图。

 

 

我们本来需要修改图中所有具体的Component类的一些功能,但却并不是去直接修改它们的代码,而是在它们的外面增加一个DecoratorDecorator与具体的Component类都是继承的AbstractComponent,因此它长得和具体的Component类一样,也就是说我们在使用Decorator的时候就如同在使用ConcreteComponentA或者ConcreteComponentB一样,甚至那些使用ConcreteComponentA或者ConcreteComponentB的客户程序都不知道它们用的类已经改为了Decorator,但是Decorator已经对具体的Component类的部分方法进行了修改,执行这些方法的结果已经不同了。

 

  • 设计MultiDataSource

现在回到我们的问题,我们需要对dataSource的功能进行变更,但又不希望修改dataSource中的任何代码。我这里指的dataSource是所有实现javax.sql.DataSource接口的类,我们常用的包括apache 提供的org.apache.commons.dbcp.BasicDataSourcespring提供的org.springframework.jndi.JndiObjectFactoryBean等,这些类我们不可能修改它们本身,更不可能对它们一个个地修改以实现动态分配数据源的功能,同时,我们又希望使用dataSourcesessionFactory根本就感觉不到这样的变化。Decorator模式就正是解决这个问题的设计模式。

 

首先写一个Decorator类,我取名叫MultiDataSource,通过它来动态切换数据源。同时在配置文件中将sessionFactory的dataSource属性由原来的某个具体的dataSource改为MultiDataSource。如图:

 

 

对比原Decorator模式,AbstractComponent是一个抽象类,但在这里我们可以将这个抽象类用接口来代替,即DataSource接口,而ConcreteComponent就是那些DataSource的实现类,如BasicDataSourceJndiObjectFactoryBean等。MultiDataSource封装了具体的dataSource,并实现了数据源动态切换:

 

 

java 代码
  1. public class MultiDataSource implements DataSource {   
  2.     private DataSource dataSource = null;   
  3. public MultiDataSource(DataSource dataSource){   
  4.         this.dataSource = dataSource;   
  5.     }   
  6.     /* (non-Javadoc)  
  7.      * @see javax.sql.DataSource#getConnection()  
  8.      */  
  9.     public Connection getConnection() throws SQLException {   
  10.         return getDataSource().getConnection();   
  11.     }   
  12.     //其它DataSource接口应当实现的方法   
  13.   
  14.     public DataSource getDataSource(){   
  15.         return this.dataSource;   
  16.         }   
  17.     }   
  18.     public void setDataSource(DataSource dataSource) {   
  19.         this.dataSource = dataSource;   
  20.     }   
  21. }   

 

客户在发出请求的时候,将dataSourceName放到request中,然后把request中的数据源名通过调用new MultiDataSource(dataSource)时可以告诉MultiDataSource客户需要的数据源,就可以实现动态切换数据源了。但细心的朋友会发现这在单例的情况下就是问题的,因为MultiDataSource在系统中只有一个对象,它的实例变量dataSource也只有一个,就如同一个静态变量一般。正因为如此,单例模式让许多设计模式都不得不需要更改,这将在我的《“单例”更改了我们的设计模式》中详细讨论。那么,我们在单例模式下如何设计呢?

 

  • 单例模式下的MultiDataSource

在单例模式下,由于我们在每次调用MultiDataSource的方法的时候,dataSource都可能是不同的,所以我们不能将dataSource放在实例变量dataSource中,最简单的方式就是在方法getDataSource()中增加参数,告诉MultiDataSource我到底调用的是哪个dataSource

 

java 代码
  1. public DataSource getDataSource(String dataSourceName){   
  2.         log.debug("dataSourceName:"+dataSourceName);   
  3.         try{   
  4.             if(dataSourceName==null||dataSourceName.equals("")){   
  5.                 return this.dataSource;   
  6.             }   
  7.             return (DataSource)this.applicationContext.getBean(dataSourceName);   
  8.         }catch(NoSuchBeanDefinitionException ex){   
  9.             throw new DaoException("There is not the dataSource 
  10.         }   
  11.     }   

 

值得一提的是,我需要的数据源已经都在spring的配置文件中注册,dataSourceName就是其对应的id

 

xml 代码
  1. <bean id="dataSource1"  
  2.     class="org.apache.commons.dbcp.BasicDataSource">  
  3.     <property name="driverClassName">  
  4.         <value>oracle.jdbc.driver.OracleDrivervalue>  
  5.     property> 
  6.     ......   
  7. bean>  
  8. <bean id="dataSource2"  
  9.     class="org.apache.commons.dbcp.BasicDataSource">  
  10.     <property name="driverClassName">  
  11.         <value>oracle.jdbc.driver.OracleDrivervalue> 
  12.     property>   
  13.     ......   
  14. bean>   

为了得到springApplicationContextMultiDataSource类必须实现接口org.springframework.context.ApplicationContextAware,并且实现方法:

java 代码
  1. private ApplicationContext applicationContext = null;   
  2. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {   
  3.         this.applicationContext = applicationContext;   
  4.     }   

 

如此这样,我就可以通过this.applicationContext.getBean(dataSourceName)得到dataSource了。

 

  • 通过线程传递dataSourceName

查看以上设计,MultiDataSource依然无法运行,因为用户在发出请求时,他需要连接什么数据库,其数据源名是放在request中的,要将request中的数据源名传给MultiDataSource,需要经过BUSDAO,也就是说为了把数据源名传给MultiDataSourceBUSDAO的所有方法都要增加dataSourceName的参数,这是我们不愿看到的。写一个类,通过线程的方式跳过BUSDAO直接传递给MultiDataSource是一个不错的设计:

 

java 代码
  1. public class SpObserver {   
  2.     private static ThreadLocal local = new ThreadLocal();   
  3.     public static void putSp(String sp) {   
  4.         local.set(sp);   
  5.     }   
  6.     public static String getSp() {   
  7.         return (String)local.get();   
  8.     }   
  9. }   

 

做一个filter,每次客户发出请求的时候就调用SpObserver.petSp(dataSourceName),将request中的dataSourceName传递给SpObserver对象。最后修改MultiDataSource的方法getDataSource()

 

java 代码
  1. public DataSource getDataSource(){   
  2.         String sp = SpObserver.getSp();   
  3.         return getDataSource(sp);   
  4.     }   

 

完整的MultiDataSource代码在附件中。

 

  • 动态添加数据源

通过以上方案,我们解决了动态分配数据源的问题,但你可能提出疑问:方案中的数据源都是配置在springApplicationContext中,如果我在程序运行过程中动态添加数据源怎么办?这确实是一个问题,而且在我们的项目中也确实遇到。springApplicationContext是在项目启动的时候加载的。加载以后,我们如何动态地加载新的beanApplicationContext中呢?我想到如果用spring自己的方法解决这个问题就好了。所幸的是,在查看spring的源代码后,我找到了这样的代码,编写了DynamicLoadBean类,只要调用loadBean()方法,就可以将某个或某几个配置文件中的bean加载到ApplicationContext中(见附件)。不通过配置文件直接加载对象,在spring的源码中也有,感兴趣的朋友可以自己研究。

 

 

  • spring中配置

在完成了所有这些设计以后,我最后再唠叨一句。我们应当在spring中做如下配置:

 

xml 代码
  1. <bean id="dynamicLoadBean" class="com.htxx.service.dao.DynamicLoadBean">bean>  
  2. <bean id="dataSource" class="com.htxx.service.dao.MultiDataSource">  
  3.         <property name="dataSource">  
  4.             <ref bean="dataSource1" />  
  5.         property>  
  6.     bean>  
  7.     <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
  8.         <property name="dataSource">  
  9.             <ref bean="dataSource" />  
  10.         property>  
  11.         ......   
  12.     bean>  

 

其中dataSource属性实际上更准确地说应当是defaultDataSource,即spring启动时以及在客户没有指定数据源时应当指定的默认数据源。

 

该方案的优势

 

以上方案与其它方案相比,它有哪些优势呢?

 

首先,这个方案完全是在spring的框架下解决的,数据源依然配置在spring的配置文件中,sessionFactory依然去配置它的dataSource属性,它甚至都不知道dataSource的改变。唯一不同的是在真正的dataSourcesessionFactory之间增加了一个MultiDataSource

 

其次,实现简单,易于维护。这个方案虽然我说了这么多东西,其实都是分析,真正需要我们写的代码就只有MultiDataSourceSpObserver两个类。MultiDataSource类真正要写的只有getDataSource()getDataSource(sp)两个方法,而SpObserver类更简单了。实现越简单,出错的可能就越小,维护性就越高。

 

最后,这个方案可以使单数据源与多数据源兼容。这个方案完全不影响BUSDAO的编写。如果我们的项目在开始之初是单数据源的情况下开发,随着项目的进行,需要变更为多数据源,则只需要修改spring配置,并少量修改MVC层以便在请求中写入需要的数据源名,变更就完成了。如果我们的项目希望改回单数据源,则只需要简单修改配置文件。这样,为我们的项目将增加更多的弹性。

 

特别说明:实例中的DynamicLoadBean在web环境下运行会出错,需要将类中AbstractApplicationContext改为org.springframework.context.ConfigurableApplicationContext。

 

相关博客:再析在spring框架中解决多数据源的问题

 

  • example.rar (32.1 KB)
  • 描述: 源码及示例
  • 下载次数: 9942
分享到:
评论
65 楼 未来星___费 2015-09-15  
楼主能给一个完整的例子吗?真心的,现在对spring理解不太深!麻烦了!邮箱feiyuanxing@126.com (费元星)
64 楼 1peng 2012-09-27  
jianfeng008cn 写道
1peng 写道
首先,谢谢你提供这么好的方法给我们用,我正在用,但是遇到了一个新的问题,就是添加了 ServerFilter过滤器以后,导致STRUTS2的编码过滤器失效,输入中文搜索时会有乱码,看有没有好的解决方法

过滤器顺序不对吧?

过滤器只能放在struts的过滤器前,否则过滤器在加载的时候运行一次,再也不会运行,不过我刚才已经解决了这个问题,就是重写STRUTS的过滤器方法,把原方法拷贝进来,同时在适当的地方增加需要PUT数据源的方法,此方法测试通过,中文没有乱码问题
63 楼 jianfeng008cn 2012-09-27  
1peng 写道
首先,谢谢你提供这么好的方法给我们用,我正在用,但是遇到了一个新的问题,就是添加了 ServerFilter过滤器以后,导致STRUTS2的编码过滤器失效,输入中文搜索时会有乱码,看有没有好的解决方法

过滤器顺序不对吧?
62 楼 1peng 2012-09-27  
首先,谢谢你提供这么好的方法给我们用,我正在用,但是遇到了一个新的问题,就是添加了 ServerFilter过滤器以后,导致STRUTS2的编码过滤器失效,输入中文搜索时会有乱码,看有没有好的解决方法
61 楼 fangang 2012-04-11  
引用
DB2多数据源时,动态切换会有问题。


愿闻其详
60 楼 271287607 2012-04-10  
DB2多数据源时,动态切换会有问题。
59 楼 gaolinwu 2012-03-11  
多谢分享,现在正在用这方面知识架构项目
58 楼 idifei24 2011-08-12  
非常好的东西 , 今天要用到这功能!谢谢分享!
57 楼 fangang 2007-09-18  
ssuupv 写道
我觉得这样解决,有些问题还是解决不了,例如数据源是在使用过程中,不断增加(数据库极有可能达到上W个.不过每个数据库的结构都是一样),我想上面解决办法还是不行的.如果对多数据源感兴趣,可以加我QQQ:3801339


很抱歉今天才看见这条回复,实际上hamlet的方案也不错,但使用配置文件来解决这个问题似乎效率更高些,毕竟可以避免应用服务器因为获取数据库连接信息而访问数据库服务器的操作。

如何使用配置文件解决这个问题呢?在每增加一个数据库的时候,将该数据库的连接信息放到一个新创建的ApplicationContext-datasource-XXX.xml文件中。也就是说动态创建一个spring的配置文件,该文件中就只有一个datasource的bean,然后动态添加到spring中就可以了。如何动态添加,在该文最后的部分提到了,但遗憾的是我提供的这个功能的代码有一点儿问题。如果你需要,我可以帮助你解决这个问题。MSN:fan_gang2004@hotmail.com,我不用QQ。
56 楼 hamlet 2007-09-18  
ssuupv 写道
我觉得这样解决,有些问题还是解决不了,例如数据源是在使用过程中,不断增加(数据库极有可能达到上W个.不过每个数据库的结构都是一样),我想上面解决办法还是不行的.如果对多数据源感兴趣,可以加我QQQ:3801339
我们以前处理这个问题的方案是专门建一个数据库,用于存储w个数据库连接信息,和用户信息,并且用户信息和数据库连接信息是关联的。
用户登录后,程序就可以动态的取出该用户资源对应的数据库连接信息,进行连接。
55 楼 ssuupv 2007-09-15  
我觉得这样解决,有些问题还是解决不了,例如数据源是在使用过程中,不断增加(数据库极有可能达到上W个.不过每个数据库的结构都是一样),我想上面解决办法还是不行的.如果对多数据源感兴趣,可以加我QQQ:3801339
54 楼 yxlrock 2007-09-07  
请问搂主如何解决hibernate方言的问题,我使用db2和sqlserver连个数据库,配置两个数据源可以解决一些问题,而且搂主的解决方案还是很不错的!但是hibernate方言问题怎么办?????
53 楼 sker 2007-08-13  
很强啊。。
52 楼 fangang 2007-06-21  
sageheart 写道
我也遇到过同样的问题,解决方案和楼主类似,但是使用该方案有一个大前提就是一个用户同一时刻只能使用一个数据源,比如我遇到的实际需求就是用户在登陆的时候要选择是用测试库还是产品库,他只要选一个就可以了;而后台驻守的线程则配置死只用产品库的。但是一旦遇到需要两个数据源同时访问的需求,那么此方案就无能为力了,同时事务管理的问题也就出现了。比如用户需要在更新一个数据源中记录的同时,也更新另外一个数据源的某个记录,两者要在一个事务中进行,要么都成功,要么都失败。如果这样,有什么好的解决方案吗
这个问题我也考虑到了,也许我的另一篇文章《再析在spring框架中解决多数据源的问题》可以解决你的问题。
51 楼 sageheart 2007-06-20  
我也遇到过同样的问题,解决方案和楼主类似,但是使用该方案有一个大前提就是一个用户同一时刻只能使用一个数据源,比如我遇到的实际需求就是用户在登陆的时候要选择是用测试库还是产品库,他只要选一个就可以了;而后台驻守的线程则配置死只用产品库的。但是一旦遇到需要两个数据源同时访问的需求,那么此方案就无能为力了,同时事务管理的问题也就出现了。比如用户需要在更新一个数据源中记录的同时,也更新另外一个数据源的某个记录,两者要在一个事务中进行,要么都成功,要么都失败。如果这样,有什么好的解决方案吗
50 楼 fangang 2007-06-15  
programmer 写道
请问,使用多数据源,怎么支持cache
我看到你在论坛中提问怎样实现单实例多用户的情况下使用cache。对于spring+hibernate来说,单实例多用户的情况运用多数据源的方式就可以解决了,而cache如何解决呢?一级缓存就不必说了,hibernate自己管理,在大多数情况下你不必管它。二级缓存呢?如果你采用的是我提出的方案,你就可以如同使用单数据源一样使用多数据源,因为在这个方案中,就如同将多个数据源整合成一个数据源一般。你只要将需要二级缓存的表做好配置就可以了。但你必须注意一个细节:如果你的多个数据源有相同的表,那么你必须保证在所有数据源中的该表主键是唯一的。什么意思呢?如果你的数据源A和B都有表X,那么在数据源A中的表X有ID为001,在数据源B中的表X中就不能有这个ID,因为这里已经将多个数据源整合成一个数据源使用了。
如果你希望了解更详细的信息,我会随后在博客中讨论这个问题。谢谢
49 楼 programmer 2007-06-15  
请问,使用多数据源,怎么支持cache
48 楼 fangang 2007-06-13  
ljl 写道
我想楼主对于什么是“动态”理解欠缺,呵呵。这还不是依据用户的需求进行数据源切换啊,这算是什么“动态”啊,按照你这么说,用户每一次提交一个表单也是动态啦,呵呵。

还有就是,如果在一个request周期都要确定一次数据源的话,这样频繁的更换数据源,呵呵。。。
再者,用户很可能同时打开多个数据源,这时,该怎么办啊?

我所说的“动态”是指用户在提交数据的时候到底连接的是哪个数据源并没有写死,而是根据用户的需求,在运行期才确定连接什么数据,因此我称之为“动态”应当没有什么问题。至于为什么要这样切换,如果ljl朋友在自己的项目中有出现了这样的需求,你就可以理解为什么要这样做了。
你还问到用户很可能同时打开多个数据源的问题,如果是在不同的线程中打开的,用我的方案没有问题。如果是在同一个线程中打开,你需要稍微修改一下我的方案,关键是如何告诉MultiDataSource什么时候连接哪个数据源,就可以解决了。
47 楼 ljl 2007-06-13  
我想楼主对于什么是“动态”理解欠缺,呵呵。这还不是依据用户的需求进行数据源切换啊,这算是什么“动态”啊,按照你这么说,用户每一次提交一个表单也是动态啦,呵呵。

还有就是,如果在一个request周期都要确定一次数据源的话,这样频繁的更换数据源,呵呵。。。
再者,用户很可能同时打开多个数据源,这时,该怎么办啊?
46 楼 fangang 2007-06-12  
二十一 写道
我想知道,lz的这种方案能够保证open session in view的正常使用么?因为osiv模式是在
openSessionInViewFilter时就经确定了使用的数据源,而且以后的数据库操作都会保证使用同一数据连接,所以LZ的这种方法似乎对osiv模式没多大意义

谢谢“二十一”朋友的提醒,但我研究了一下openSessionInViewFilter,它使用我的方案是没有问题的。打开源码可以发现,openSessionInViewFilter通过request获得sessionFactory,在我们的配置中,sessionFactory.dataSource对应的是MultiDataSource,而MultiDataSource通过request得到真正的数据源。既然openSessionInViewFilter和MultiDataSource是在同一个request中获取信息,那么我的方案是不会有什么问题的。
这里着实有些不太好理解,过去我的的dataSource对应的都是一个确定的数据源,而MultiDataSource却不是一个确定的数据源,但当作一个dataSource使用,这就是我提到的对客户透明。
MultiDataSource对应的是多数据源,但对于客户程序来说就仿佛是单数据源来使用。

相关推荐

Global site tag (gtag.js) - Google Analytics