spring框架中多数据源创建加载并且实现动态切换的配置实例代码

原文:spring框架中多数据源创建加载并且实现动态切换的配置实例代码

源代码下载地址:http://www.zuidaima.com/share/1774074130205696.htm

在我们的项目中遇到这样一个问题:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在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中的数据源名通过调用newMultiDataSource(dataSource)时可以告诉MultiDataSource客户需要的数据源,就可以实现动态切换数据源了。但细心的朋友会发现这在单例的情况下就是问题的,因为MultiDataSource在系统中只有一个对象,它的实例变量dataSource也只有一个,就如同一个静态变量一般。正因为如此,单例模式让许多设计模式都不得不需要更改,这将在我的《“单例”更改了我们的设计模式》中详细讨论。那么,我们在单例模式下如何设计呢?

  • 单例模式下的MultiDataSource

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

java 代码

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中做如下配置:

xml 代码

<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://dl.iteye.com/topics/download/11b45eb4-9732-4fcb-85de-dd88238958b2

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

时间: 2025-01-02 18:44:33

spring框架中多数据源创建加载并且实现动态切换的配置实例代码的相关文章

Spring框架整合WEB解决配置文件加载多次的问题

1. 创建JavaWEB项目,引入Spring的开发包.编写具体的类和方法. * 环境搭建好后,启动服务器来测试项目,发送每访问一次都会加载一次配置文件,这样效率会非常非常慢!! 2. 解决上面的问题 * 将工厂创建好了以后放入到ServletContext域中.使用工厂的时候,从ServletContext中获得. * ServletContextListener:用来监听ServletContext对象的创建和销毁的监听器. * 当ServletContext对象创建的时候:创建工厂 , 将

再析在spring框架中解决多数据源的问题

在前面我写了<如何在spring框架中解决多数据源的问题>,通过设计模式中的Decorator模式在spring框架中解决多数据源的问题,得到了许多网友的关注.在与网友探讨该问题的过程中,我发现我的方案并不完善,它只解决了一部分问题. 总结多数据源的问题,其实它需要分为以下三种情况:各个数据源的数据结构不同.各个数据源的数据结构相同.各个数据源的数据结构部分相同又有部分不同.对于第二种情况,各个数据源的数据结构相同,我们使用一个sessionFactory,而在sessionFactory中通

3.Spring框架中的标签与配置文件分离

1.Spring框架中标签的配置 1. id属性和name属性的区别 * id -- Bean起个名字,在约束中采用ID的约束,唯一 * 取值要求:必须以字母开始,可以使用字母.数字.连字符.下划线.句话.冒号 id:不能出现特殊字符 * name -- Bean起个名字,没有采用ID的约束(基本不在使用) * 取值要求:name:出现特殊字符.如果<bean>没有id的话 , name可以当做id使用 * Spring框架在整合Struts1的框架的时候,Struts1的框架的访问路径是以/

Apache MINA框架整合到Spring框架中

毕业设计用到了Apache公司的MINA框架作为服务端与安卓客户端的通信框架. 问题:服务端分为两个部分,一个是基于javaEE平台的后台管理系统,另一个是基于MINA框架的服务,整个项目中需求是当tomcat服务器启动的时候,将MINA服务也一起启动起来,相当于服务端程序运行起来后,开启两个服务. 分析:服务端的后台管理系统是采用Spring+Hibernate框架整合搭建的,而当tomcat服务器启动后,会加载spring的配置文件,而spring配置文件中可以自定义监听器,将启动MINA服

Spring源码解析-applicationContext.xml加载和bean的注册

applicationContext文件加载和bean注册流程? Spring对于从事Java开发的boy来说,再熟悉不过了,对于我们这个牛逼的框架的介绍就不在这里复述了,Spring这个大杂烩,怎么去使用怎么去配置,各种百度谷歌都能查到很多大牛教程,但是,当我们按着教程一步步的把spring的开发框架搭建起来的时候,有没有一种想搞明白spring的冲动,万事开头难,就要从开头开始,而我认为spring开头就是如何加载配置文件,并初始化配置文件里面的bean当然也包括了我们用注解Service.

Spring学习(一)tomcat加载web.xml、以及项目集成Spring支持

tomcat容器加载web.xml 一. 1 .启动一个 WEB 项目的时候, WEB 容器会去读取它的配置文件 web.xml ,读取 <listener> 和 <context-param> 两个结点. 2 .紧急着,容创建一个 ServletContext ( servlet 上下文),这个 web 项目的所有部分都将共享这个上下文. 3 .容器将 <context-param> 转换为键值对,并交给 servletContext . 4 .容器创建 <li

Spring源码解析和配置文件加载

Spring类的继承结构图: Spring运用了大量的模板方法模式和策略模式,所以各位看源码的时候,务必留意,每一个继承的层次都有不同的作用,然后将相同的地方抽取出来,依赖抽象将不同的处理按照不同的策略去处理. 步骤A. 读取 Resource 文件形成 Document 模型  类图: XmlBeanFactory -> XmlBeanDefinitionReader Spring 使用 XmlBeanDefinitionReader 来读取并解析 xml 文件,XmlBeanDefiniti

SSH 之 Spring的源码(一)——Bean加载过程

看看Spring的源码,看看巨人的底层实现,拓展思路,为了更好的理解原理,看看源码,深入浅出吧.本文基于Spring 4.0.8版本. 首先Web项目使用Spring是通过在web.xml里面配置 org.springframework.web.context.ContextLoaderListener初始化IOC容器的 <span style="font-family:SimSun;font-size:18px;"><listener> <listene

Spring5源码解析-Spring框架中的事件和监听器

事件和平时所用的回调思想在与GUI(JavaScript,Swing)相关的技术中非常流行.而在Web应用程序的服务器端,我们很少去直接使用.但这并不意味着我们无法在服务端去实现一个面向事件的体系结构. 在本文中,我们将重点介绍Spring框架中的事件处理.首先,会先介绍下事件驱动编程这个概念.接着,我们会将精力放在专门用于Spring框架中的事件处理之上.然后我们会看到实现事件调度和监听的主要方法.最后,我们将在Spring应用程序中展示如何使用基本的监听器. 事件驱动编程 在开始讨论事件驱动