在前面我写了《如何在 spring 框架中解决多数据源的问题 》,通过设计模式中的 Decorator 模式在 spring 框架中解决多数据源的问题,得到了许多网友的关注。在与网友探讨该问题的过程中,我发现我的方案并不完善,它只解决了一部分问题。
总结多数据源的问题,其实它需要分为以下三种情况:各个数据源的数据结构不同、各个数据源的数据结构相同、各个数据源的数据结构部分相同又有部分不同。对于第二种情况,各个数据源的数据结构相同,我们使用一个 sessionFactory ,而在 sessionFactory 中通过 MultiDataSource 来动态切换数据源,应当是一个不错的方案,既解决了多个 sessionFactory 对相同的值对象重复装载对内存的浪费,又使数据源的切换对客户程序透明,简化了代码的实现和对客户程序的影响。但是,对于第一种情况,各个数据源的数据结构不同,运用这样的方案存在潜在风险。
对于各个数据源的数据结构不同的情况,使用一个 sessionFactory 而在这个 sessionFactory 中动态切换数据源,可能造成数据访问的张冠李戴。譬如,数据源 A 有表 T 而数据源 B 没有,可能造成客户程序在访问表 T 的时候却尝试去连接数据源 B ,因为客户程序访问哪个数据源是在程序运行期间由客户程序决定的,因此这样的错误是很难发现的。也许客户程序的一个不经意的错误就可能造成错误。解决这个问题的方法有两个:一是严格要求客户程序不要写错,这当然是可以做到的,但作为框架设计者,另一个解决方法是在框架中就避免出现这样的情况。因此我祭出了 MultiSessionFactory 的方案来解决各个数据源的数据结构不同的多数据源问题。
问题的分析
与 MultiDataSource 的方案一样, MultiSessionFactory 同样是在 spring 框架下调用 ApplicationContext 的getBean() 方法而不会另外创建 beanFacoty ,也同样使用 Decorator 模式来处理切换的问题。MultiSessionFactory 的对象关系如图:
在该方案中, SessionFactory 就是 Hibernate 的 org.hibernate.SessionFactory 接口, Decorator 就是MultiSessionFactory , SessionFactory1 和 SessionFactory2 往往是 spring 的org.springframework.orm.hibernate3.LocalSessionFactoryBean 。细心的朋友可能会注意,实际上LocalSessionFactoryBean 并不是 SessionFactory 的实现,这个方案是否有问题呢?这个问题其实也一直困扰了我好久,最后我发现,我们通过 ApplicationContext 的 getBean() 得到一个 LocalSessionFactoryBean 的时候其实并不是真正地得到了它,而是得到了一个 SessionFactory ,因为 spring 为 LocalSessionFactoryBean 重写了getObject() ,使其返回的是 SessionFactory 。一个简单的明证就是, HibernateDaoSupport 的 sessionFactory属性的类型是 SessionFactory ,而我们在 spring 配置的时候注入的却是 LocalSessionFactoryBean 。
方案的实现
在整个这个方案中,我们需要实现的只有 MultiSessionFactory 类和我们可爱的 Spserver ,总共就两个类,然后呢就是一些 spring 的配置,就完成了。
MultiSessionFactory 实现了 SessionFactory ,同时为了得到 AplicationContext 而实现了ApplicationContextAware 。 MultiSessionFactory 的代码如下:
java 代码
- public class MultiSessionFactory implements SessionFactory, ApplicationContextAware {
- private static final long serialVersionUID = 2064557324203496378L;
- private static final Log log = LogFactory.getLog(MultiSessionFactory. class );
- private ApplicationContext applicationContext = null ;
- private SessionFactory sessionFactory = null ;
- public ApplicationContext getApplicationContext() {
- return applicationContext;
- }
- public void setApplicationContext(ApplicationContext applicationContext) {
- this .applicationContext = applicationContext;
- }
- public SessionFactory getSessionFactory(String sessionFactoryName) {
- log.debug( "sessionFactoryName:" +sessionFactoryName);
- try {
- if (sessionFactoryName== null ||sessionFactoryName.equals( "" )){
- return sessionFactory;
- }
- return (SessionFactory) this .getApplicationContext().getBean(sessionFactoryName);
- } catch (NoSuchBeanDefinitionException ex){
- throw new DaoException( "There is not the sessionFactory
- }
- }
-
- public SessionFactory getSessionFactory() {
- String sessionFactoryName = SpObserver.getSp();
- return getSessionFactory(sessionFactoryName);
- }
-
- public void setSessionFactory(SessionFactory sessionFactory) {
- this .sessionFactory = sessionFactory;
- }
-
- // SessionFactory接口需要实现的方法
-
- ......
-
- }
MultiSessionFactory的完整代码见我提供的附件。 setSessionFactory() 实际上是设定的默认sessionFactory ,它在 spring 装载的时候调用,其对应的数据源应当是主数据源,即项目初始化中需要读取初始化数据的数据源。在任何多数据源项目中,都应当有一个存放初始化数据、系统维护数据、用户权限数据的数据源,这就是主数据源。因此 MultiSessionFactory 的配置应当这样写:
xml 代码
- < bean id = "sessionFactory" class = "com.htxx.service.dao.MultiSessionFactory" >
- < property name = "sessionFactory" > < ref bean = "hostSessionFactory" />property >
- >
SpServer的写法与《如何在 spring 框架中解决多数据源的问题》 中的一样,我就不再累赘了。
另外,在 spring 配置中配置多个数据源,每个数据源对应一个 sessionFactory ,这个对应的sessionFactory 中的值对象应当是该数据源的值对象。客户程序在执行数据访问前,通过调用 SpServer 的putSp() 方法,告诉 MultiSessionFactory 需要切换到哪个 sessionFactory ,然后执行数据访问。这样,不同数据源的值对象通过放在不同的 sessionFactory 中,避免了张冠李戴的情况。具体的示例见附件的MultiSessionFactoryTest 。
另外的方案
也许有些朋友对以上方案还不满意,因为在执行数据访问前毕竟还要多做一步指定 sessionFactory 的工作。实际上,对于各个数据源的数据结构不同的项目,一个值对象应当使用哪个数据源有一个非常确定的对应关系。如果通过配置文件将值对象与它的 sessionFactory 对应起来,那么我们在执行数据访问的时候传递的是哪个值对象, MultiSessionFactory 马上就可以去找到对应的 sessionFactory 。这个方案你可以通过 AOP 来制作一个拦截器拦截所有诸如 save() 、 delete() 、 get() 、 load() 等方法来实现,也可以扩展 HibernateDaoSupport 来实现。这样的方案使客户程序甚至都不用知道他是在操作的一个多数据源系统。当然,这个方案感兴趣的朋友可以自己去实现。
另外,在这个方案中的核心是运用 Decorator 设计模式来解决切换 sessionFactory 的目的,即MultiSessionFactory 的实现。至于通过什么方式来通知 MultiSessionFactory 应当切换到哪个 SessionFactory ,可以根据不同项目的情况自由选择。我在这里给大家提供了通过 SpOberver 和建立值对象与 sessionFactory 关系的配置文件这两个方案,你也可以有自己的方案解决。
第三种情况的解决方案
前面我已经给出了第一种和第二种情况的解决方案:各个数据源的数据结构不同的情况用MultiSessionFactory 解决;各个数据源的数据结构相同的情况用 MultiDataSource 解决。那么第三种情况,各个数据源的数据结构部分相同又有部分不同,又应当如何解决呢?当然是将 MultiSessionFactory 和MultiDataSource 结合起来解决。对于数据结构不同的部分,其分别创建各自的 sessionFactory 然后通过MultiSessionFactory 来切换,而对于数据结构相同的部分,建立共同的 sessionFactory 和多个不同的dataSource 然后通过 MultiDataSource 来切换就可以了。
还有的朋友问到这样的方案其事务处理和二级缓存的情况。这个方案是在 spring 框架下的解决方案,其事务处理的能力也是由 spring 的能力来决定的。目前 spring 要处理跨数据库的事务处理是通过 JTA 来实现的,这种方式在该方案中同样可以实现,朋友们可以试一试。另外,本方案能使用二级缓存吗?当然可以。对于MultiSessionFactory 当然没有任何问题,它通过不同的 sessionFactory 分离开了不同的数据源和值对象,我们可以毫无顾忌地使用。对于 MultiDataSource 来说,就有点问题了。 MultiDataSource 使多个数据源使用共同的sessionFactory ,因此它仿佛就是将多个数据源在逻辑上合并为一个数据源。正因为如此,我们需要保证对于同一个表在所有数据源中都要主键唯一。什么意思呢?数据源 A 和数据源 B 都有表 T ,如果数据源 A 中的表 T 拥有 ID 为 001 的一条数据,那么在数据源 B 的表 T 中就不能有 ID 为 001 的记录。如果你总是通过MultiDataSource 来执行表的插入操作,并且使用uuid.hex生成主键,这当然不会有问题。但如果你有通过其它方式插入表的操作,你应当保证这样的唯一性。另外,对于查询的操作,缓存中存放的既可能是数据源 A 的数据,也可能是数据源 B 的数据,因此你应当对数据有一个规划。对于表 T 的数据,哪些应当插入到数据源 A 中,哪些应当插入到 B 中,应当有一个定义。假如是通过不同单位来决定插入哪个数据源,那么在查询数据源 A 的表 T是,应当增加条件只查询数据源 A 应当有的单位而排除调其它单位。如此这样,你只要注意到这两个问题,你就可以放心大胆地使用二级缓存。
作者:mixer_a 发表于2012-4-11 21:24:29
原文链接