0

0

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

高洛峰

高洛峰

发布时间:2017-02-08 10:44:25

|

2021人浏览过

|

来源于php中文网

原创

在我们的项目中遇到这样一个问题:我们的项目需要连接多个数据库, 而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在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模式的结构如图。

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

我们本来需要修改图中所有具体的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。如图:

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

对比原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 代码

    
        
        oracle.jdbc.driver.OracleDrivervalue>    
    property>   
    ......     
bean>    
    
        
        oracle.jdbc.driver.OracleDrivervalue>   
    property>     
    ......     
bean>

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

CoCo
CoCo

智谱AI推出的首个有记忆的企业自主Agent智能体

下载

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是一个不错的设计:

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

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>    
    
            
                
        property>    
    bean>    
        
            
                
        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。

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
2026春节习俗大全
2026春节习俗大全

本专题整合了2026春节习俗大全,阅读专题下面的文章了解更多详细内容。

68

2026.02.11

Yandex网页版官方入口使用指南_国际版与俄罗斯版访问方法解析
Yandex网页版官方入口使用指南_国际版与俄罗斯版访问方法解析

本专题全面整理了Yandex搜索引擎的官方入口信息,涵盖国际版与俄罗斯版官网访问方式、网页版直达入口及免登录使用说明,帮助用户快速、安全地进入Yandex官网,高效使用其搜索与相关服务。

200

2026.02.11

虫虫漫画网页版入口与免费阅读指南_正版漫画全集在线查看方法
虫虫漫画网页版入口与免费阅读指南_正版漫画全集在线查看方法

本专题系统整理了虫虫漫画官网及网页版最新入口,涵盖免登录观看、正版漫画全集在线阅读方式,并汇总稳定可用的访问渠道,帮助用户快速找到虫虫漫画官方页面,轻松在线阅读各类热门漫画内容。

40

2026.02.11

Docker容器化部署与DevOps实践
Docker容器化部署与DevOps实践

本专题面向后端与运维开发者,系统讲解 Docker 容器化技术在实际项目中的应用。内容涵盖 Docker 镜像构建、容器运行机制、Docker Compose 多服务编排,以及在 DevOps 流程中的持续集成与持续部署实践。通过真实场景演示,帮助开发者实现应用的快速部署、环境一致性与运维自动化。

4

2026.02.11

Rust异步编程与Tokio运行时实战
Rust异步编程与Tokio运行时实战

本专题聚焦 Rust 语言的异步编程模型,深入讲解 async/await 机制与 Tokio 运行时的核心原理。内容包括异步任务调度、Future 执行模型、并发安全、网络 IO 编程以及高并发场景下的性能优化。通过实战示例,帮助开发者使用 Rust 构建高性能、低延迟的后端服务与网络应用。

1

2026.02.11

Spring Boot企业级开发与MyBatis Plus实战
Spring Boot企业级开发与MyBatis Plus实战

本专题面向 Java 后端开发者,系统讲解如何基于 Spring Boot 与 MyBatis Plus 构建高效、规范的企业级应用。内容涵盖项目架构设计、数据访问层封装、通用 CRUD 实现、分页与条件查询、代码生成器以及常见性能优化方案。通过完整实战案例,帮助开发者提升后端开发效率,减少重复代码,快速交付稳定可维护的业务系统。

6

2026.02.11

包子漫画网页版入口与全集阅读指南_正版免费漫画快速访问方法
包子漫画网页版入口与全集阅读指南_正版免费漫画快速访问方法

本专题汇总了包子漫画官网和网页版入口,提供最新章节抢先看方法、正版免费阅读指南,以及稳定访问方式,帮助用户快速直达包子漫画页面,无广告畅享全集漫画内容。

159

2026.02.10

MC.JS网页版快速畅玩指南_MC.JS官网在线入口及免安装体验方法
MC.JS网页版快速畅玩指南_MC.JS官网在线入口及免安装体验方法

本专题汇总了MC.JS官网入口和网页版快速畅玩方法,提供免安装访问、不同版本(1.8.8、1.12.8)在线体验指南,以及正版网页端操作说明,帮助玩家轻松进入MC.JS世界,实现即时畅玩与高效体验。

89

2026.02.10

谷歌邮箱网页版登录与注册全指南_Gmail账号快速访问与安全操作教程
谷歌邮箱网页版登录与注册全指南_Gmail账号快速访问与安全操作教程

本专题汇总了谷歌邮箱网页版的最新登录入口和注册方法,详细提供官方账号快速访问方式、网页版操作教程及安全登录技巧,帮助用户轻松管理Gmail邮箱账户,实现高效、安全的邮箱使用体验。

78

2026.02.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Spring中文手册
Spring中文手册

共0课时 | 0人学习

马士兵spring视频教程
马士兵spring视频教程

共25课时 | 9.1万人学习

PostgreSQL 教程
PostgreSQL 教程

共48课时 | 8.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号