`

用设计模式解决多数据源问题

阅读更多
无意间看了网游一片解决多数据源方法的博文,觉得不错体现了设计模式的功能,转一下下

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

问题的分析

我首先想到在spring的applicationContext中配置所有的dataSource。这些dataSource可能是各种不同类型的,比如不同的数据库:Oracle、SQL Server、MySQL等,也可能是不同的数据源:比如apache 提供的org.apache.commons.dbcp.BasicDataSource、spring提供的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类的一些功能,但却并不是去直接修改它们的代码,而是在它们的外面增加一个Decorator。Decorator与具体的Component类都是继承的AbstractComponent,因此它长得和具体的Component类一样,也就是说我们在使用Decorator的时候就如同在使用ConcreteComponentA或者ConcreteComponentB一样,甚至那些使用ConcreteComponentA或者ConcreteComponentB的客户程序都不知道它们用的类已经改为了Decorator,但是Decorator已经对具体的Component类的部分方法进行了修改,执行这些方法的结果已经不同了。
(二)    设计MultiDataSource类
现在回到我们的问题,我们需要对dataSource的功能进行变更,但又不希望修改dataSource中的任何代码。我这里指的dataSource是所有实现javax.sql.DataSource接口的类,我们常用的包括apache 提供的org.apache.commons.dbcp.BasicDataSource、spring提供的org.springframework.jndi.JndiObjectFactoryBean等,这些类我们不可能修改它们本身,更不可能对它们一个个地修改以实现动态分配数据源的功能,同时,我们又希望使用dataSource的sessionFactory根本就感觉不到这样的变化。Decorator模式就正是解决这个问题的设计模式。

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

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

java 代码

public class MultiDataSource implements DataSource {   
   private DataSource dataSource = null;   
   public MultiDataSource(DataSource dataSource){   
       this.dataSource = dataSource;   
   }   
       /* (non-Javadoc)  
        * @see javax.sql.DataSource#getConnection()  
        */  
   public Connection getConnection() throws SQLException {   
        return getDataSource().getConnection();   
   }   
     //其它DataSource接口应当实现的方法   
     
   public DataSource getDataSource(){   
      return this.dataSource;   
   }   
  }   
  public void setDataSource(DataSource dataSource) {   
      this.dataSource = dataSource;   
  }   
}   


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

(三)  单例模式下的MultiDataSource
在单例模式下,由于我们在每次调用MultiDataSource的方法的时候,dataSource都可能是不同的,所以我们不能将dataSource放在实例变量dataSource中,最简单的方式就是在方法getDataSource()中增加参数,告诉MultiDataSource我到底调用的是哪个dataSource:
public DataSource getDataSource(String dataSourceName){   
		log.debug("dataSourceName:"+dataSourceName);   
		try{   
			if(dataSourceName==null||dataSourceName.equals("")){   
				return this.dataSource;   
            }   
			return (DataSource)this.applicationContext.getBean(dataSourceName);   
		}catch(NoSuchBeanDefinitionException ex){   
			throw new DaoException("There is not the dataSource 
		}   
	} 

值得一提的是,我需要的数据源已经都在spring的配置文件中注册,dataSourceName就是其对应的id。
xml 代码
 <bean id="dataSource1"  
     class="org.apache.commons.dbcp.BasicDataSource">  
     <property name="driverClassName">  
         <value>oracle.jdbc.driver.OracleDrivervalue>  
     property> 
     ......   
 bean>  
 <bean id="dataSource2"  
     class="org.apache.commons.dbcp.BasicDataSource">  
     <property name="driverClassName">  
         <value>oracle.jdbc.driver.OracleDrivervalue> 
     property>   
     ......   
 bean> 

为了得到spring的ApplicationContext,MultiDataSource类必须实现接口org.springframework.context.ApplicationContextAware,并且实现方法:
java 代码
private ApplicationContext applicationContext = null;   
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {   
         this.applicationContext = applicationContext;   
     } 

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


java 代码
    public class SpObserver {   
        private static ThreadLocal local = new ThreadLocal();   
        public static void putSp(String sp) {   
            local.set(sp);   
        }   
        public static String getSp() {   
            return (String)local.get();   
        }   
    }   

做一个filter,每次客户发出请求的时候就调用SpObserver.petSp(dataSourceName),将request中的dataSourceName传递给SpObserver对象。最后修改MultiDataSource的方法getDataSource():
java 代码
 public DataSource getDataSource(){   
      String sp = SpObserver.getSp();   
      return getDataSource(sp);   
 }  

完整的MultiDataSource代码在附件中。
(五)    动态添加数据源
通过以上方案,我们解决了动态分配数据源的问题,但你可能提出疑问:方案中的数据源都是配置在spring的ApplicationContext中,如果我在程序运行过程中动态添加数据源怎么办?这确实是一个问题,而且在我们的项目中也确实遇到。spring的ApplicationContext是在项目启动的时候加载的。加载以后,我们如何动态地加载新的bean到ApplicationContext中呢?我想到如果用spring自己的方法解决这个问题就好了。所幸的是,在查看spring的源代码后,我找到了这样的代码,编写了DynamicLoadBean类,只要调用loadBean()方法,就可以将某个或某几个配置文件中的bean加载到ApplicationContext中(见附件)。不通过配置文件直接加载对象,在spring的源码中也有,感兴趣的朋友可以自己研究。
(六)    在spring中配置
在完成了所有这些设计以后,我最后再唠叨一句。我们应当在spring中做如下配置:
 <bean id="dynamicLoadBean" class="com.htxx.service.dao.DynamicLoadBean">bean>  
 <bean id="dataSource" class="com.htxx.service.dao.MultiDataSource">  
         <property name="dataSource">  
             <ref bean="dataSource1" />  
         property>  
     bean>  
     <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
         <property name="dataSource">  
             <ref bean="dataSource" />  
         property>  
         ......   
     bean>

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

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

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

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

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

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

http://www.iteye.com/topic/91667
---------------------------------------------------------------------------------
  • 大小: 12.1 KB
  • 大小: 16.4 KB
  • 大小: 9.7 KB
分享到:
评论

相关推荐

    异构数据源数据交换工具 DataX.zip

     设计理念 为了解决异构数据源同步问题,DataX 将复杂的网状的同步链路变成了星型数据链路,DataX 作为中间传输载体负责连接各种数据源。当需要接入一个新的数据源的时候,只需要将此数据源对接到 DataX,便能跟已...

    论文研究 - 开源软件中的设计模式和软件缺陷的案例研究

    设计模式是用于解决常见设计问题的面向对象的软件设计实践,它们会影响软件质量。 在这项研究中,我们调查了许多开源软件项目中设计模式与软件缺陷之间的关系。 设计模式实例是从这些开源软件项目的源代码存储库中...

    EAI中本体异构数据集成中间件的设计与实现

    提出了在企业应用集成EAI(Enterprise Application Integration)中基于本体的异构...此外,通过引入本体来描述数据模式,为解决集成中的语义异构问题提供了支持,解决了数据源异构性问题,以实现灵活的 数据源集成能力。

    二十三种设计模式【PDF版】

    这些模式解决特定的设计问题,使面向对象设计更灵活、优雅,最终复用性更 好。它们帮助设计者将新的设计建立在以往工作的基础上,复用以往成功的设计方案。 一个熟悉这些模式的设计者不需要再去发现它们,而能够...

    基于Matlab的量化投资回测和实盘交易开源平台,提供一致的回测及实时交易解决方案,遵循现代设计模式,例如事件驱动,多线程并发式

    它遵循现代设计模式,例如事件驱动,多线程并发式, 服务器/客户端架构和松散耦合的强大稳定的分布式系统。它遵循与其他EliteQuant产品线相同的结构和绩效评估值,这使得与使用其他语言的交易者分享变得更容易。 ...

    领域驱动设计与模式实战

    7.7.9 是否适合用模式来解决此问题 7.7.10 复杂规则又是什么情况 7.8 绑定到持久化抽象 7.8.1 使验证接口成为可插入的 7.8.2 在保存方面实现被动验证的替代解决方案 7.8.3 重用映射元数据 7.9 使用泛型和匿名方法 ...

    PHP设计模式之数据访问对象模式(DAO)原理与用法实例分析

    这种设计模式对于同时学过PHP和MySQL的人来说,是一个全新的和令人兴奋的概念,并且这种设计模式的目的就是为了解决重复与数据源抽象化这两种特定的问题。 我们可以先创建一个数据访问对象设计模式的对象,这个数据...

    分布式事务实践 解决数据一致性

    还有XA以及两阶段提交,并通过实例演示了使用JTA,通过两阶段提交,实现多数据源的事务实现。... 4-1 Spring事务机制_基本接口 4-2 Spring事务机制_实现 4-3 Jpa事务实例 4-4 Jms事务原理 4-5 Jms-session事务实例 4...

    基于SW6201芯片的多协议双向快充移动电源解决方案1硬件参考设计(原理图+PCB)+DEMO评估板资料+数据手册.zip

    基于SW6201芯片的多协议双向快充移动电源解决方案1硬件参考设计(原理图+PCB)+DEMO评估板资料+数据手册 SW6201 是一款高集成度的多协议双向快充移动电源专用多合一芯片,支持 A+A+B+C 口任意 口快充。其集成了 5A ...

    Excel 2007数据透视表完全剖析 4/7

    然后逐步介绍创建数据透视表、自定义透视表、查看视图数据、在透视表内进行计算、使用数据透视图等可视化工具、分析数据源、共享数据表、使用和分析OLAP数据、在透视表中使用宏和VBA等内容,并集中解答了一些常见的...

    数据仓库与数据挖掘实验报告.doc

    一、上机目的及内容 目的: 1.理解数据挖掘的基本概念及其过程;... 系统将打开数据源视图设计器,显示 Adventure Works DW 数据源视图。 创建用于目标邮件方案的挖掘结构 1. 在解决方案资源管理器中,右键单击

    asp.net知识库

    ASP.NET 2.0 中的数据源控件 使用 ASP.NET 2.0 ObjectDataSource 控件 ASP.NET 2.0 的内部变化 使用SQL Cache Dependency 代替 Ibatisnet 提供的CacheModel ASP.NET 2.0中小心Profile命名冲突 使用ASP.NET 2.0 ...

    基于 Saber 的开关电源设计 ----电源变换器设计实例.pdf

    本例说明了如何设计一个两开关、电压模式正激变换器,并且利用仿真数据作为选择元 器件和验证仿真结果准确性的手段。 设计过程被分解为以下四步,每一步都关联着一个电路,该电路是对特定设计阶段的供 电电源的建模...

    设计数据密集型应用

    第三部分,主要针对产生派生数据的系统,所谓派生数据主要指在异构系统中,如果无法用一个数据源来解决所有问题,那么一种自然的方式就是集成多个不同的数据库、缓存模块以及索引模块等。首先第10章以批处理开始来...

    js-data-structures:我的个人学习资源。 使用JavaScript编程语言实现的数据结构,算法和设计模式

    我的目标是使用JavaScript编程语言实现流行的数据结构,算法和设计模式。 我正在一个永无止境的学习旅程中,本质上是“锯锯”。 我还将努力为每种数据结构,算法和设计模式提供文档,使用JSDoc进行测试和实践。 ...

    基于使用信息和聚类方法的多模式集成

    数据集成是解决多数据源整合问题的有效手段.如何准确高效地集成多数据源模式具有重要研究意义.关于模式集成已有大量的研究工作,但均忽略了用户使用信息.在用户使用信息的基础上提出一种新颖的基于聚类技术的多模式...

    基于 MVP 设计模式实现 Android 框架 .zip

    针对这些问题,用户可以尝试一些基本的解决方法,如清除应用缓存和数据、降低屏幕亮度、关闭没有使用的连接和传感器、限制后台运行的应用、删除不需要的文件和应用等。 随着Android系统的不断发展,其功能和性能也...

    Spring攻略(第二版 中文高清版).part2

    8.11 用向导表单控制器处理多页表单 331 8.11.1 问题 331 8.11.2 解决方案 331 8.11.3 工作原理 332 8.12 使用注解(JSR-303)的Bean校验 341 8.12.1 问题 341 8.12.2 解决方案 342 8.12.3 工作原理 ...

Global site tag (gtag.js) - Google Analytics